Tổng số / số lượng / trung bình trong khoảng thời gian


20

Trong cơ sở dữ liệu về các giao dịch kéo dài 1.000 thực thể trong vòng 18 tháng, tôi muốn chạy truy vấn để nhóm mỗi khoảng thời gian 30 ngày có thể bằng entity_idSUM với số tiền giao dịch của họ và COUNT giao dịch của họ trong khoảng thời gian 30 ngày đó và trả về dữ liệu theo cách mà sau đó tôi có thể truy vấn. Sau rất nhiều thử nghiệm, mã này hoàn thành phần lớn những gì tôi muốn:

SELECT id, trans_ref_no, amount, trans_date, entity_id,
    SUM(amount) OVER(PARTITION BY entity_id, date_trunc('month',trans_date) ORDER BY entity_id, trans_date ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS trans_total,
    COUNT(id)   OVER(PARTITION BY entity_id, date_trunc('month',trans_date) ORDER BY entity_id, trans_date ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS trans_count
  FROM transactiondb;

Và tôi sẽ sử dụng trong một truy vấn lớn hơn có cấu trúc như:

SELECT * FROM (
  SELECT id, trans_ref_no, amount, trans_date, entity_id,
      SUM(amount) OVER(PARTITION BY entity_id, date_trunc('month',trans_date) ORDER BY entity_id, trans_date ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS trans_total,
      COUNT(id)   OVER(PARTITION BY entity_id, date_trunc('month',trans_date) ORDER BY entity_id, trans_date ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS trans_count
    FROM transactiondb ) q
WHERE trans_count >= 4
AND trans_total >= 50000;

Trường hợp truy vấn này không bao gồm là khi số lượng giao dịch sẽ kéo dài nhiều tháng, nhưng vẫn trong vòng 30 ngày của nhau. Là loại truy vấn có thể với Postgres? Nếu vậy, tôi hoan nghênh bất kỳ đầu vào. Nhiều chủ đề khác thảo luận về tập hợp " chạy ", không lăn .

Cập nhật

Các CREATE TABLEkịch bản:

CREATE TABLE transactiondb (
    id integer NOT NULL,
    trans_ref_no character varying(255),
    amount numeric(18,2),
    trans_date date,
    entity_id integer
);

Dữ liệu mẫu có thể được tìm thấy ở đây . Tôi đang chạy PostgreSQL 9.1.16.

Sản lượng lý tưởng sẽ bao gồm SUM(amount)COUNT()tất cả các giao dịch trong khoảng thời gian 30 ngày. Xem hình ảnh này, ví dụ:

Ví dụ về các hàng lý tưởng sẽ được bao gồm trong một "bộ" nhưng không phải vì bộ của tôi là tĩnh theo tháng.

Đánh dấu ngày màu xanh lá cây cho biết những gì được bao gồm bởi truy vấn của tôi. Tô sáng hàng màu vàng cho biết bản ghi những gì tôi muốn trở thành một phần của tập hợp.

Đọc trước:


1
Ý every possible 30-day period by entity_idbạn là khoảng thời gian có thể bắt đầu bất kỳ ngày nào , vậy 365 giai đoạn có thể trong một năm (không nhảy)? Hay bạn chỉ muốn coi ngày với một giao dịch thực tế là bắt đầu một khoảng thời gian riêng lẻ cho bất kỳ entity_id ? Dù bằng cách nào, vui lòng cung cấp định nghĩa bảng của bạn, phiên bản Postgres, một số dữ liệu mẫu và kết quả mong đợi cho mẫu.
Erwin Brandstetter

Về lý thuyết, tôi có nghĩa là bất kỳ ngày nào, nhưng trong thực tế không cần phải xem xét những ngày không có giao dịch. Tôi đã đăng dữ liệu mẫu và định nghĩa bảng.
tufelkinder 20/07/2015

Vì vậy, bạn muốn tích lũy các hàng giống nhau entity_idtrong cửa sổ 30 ngày bắt đầu từ mỗi giao dịch thực tế. Có thể có nhiều giao dịch cho cùng một (trans_date, entity_id)hoặc là sự kết hợp được xác định duy nhất? Định nghĩa bảng của bạn không có UNIQUEhoặc ràng buộc PK, nhưng dường như thiếu các ràng buộc ...
Erwin Brandstetter

Hạn chế duy nhất là trên idkhóa chính. Có thể có nhiều giao dịch cho mỗi thực thể mỗi ngày.
tufelkinder

Về phân phối dữ liệu: có các mục (mỗi entity_id) trong hầu hết các ngày không?
Erwin Brandstetter

Câu trả lời:


26

Truy vấn bạn có

Bạn có thể đơn giản hóa truy vấn của mình bằng cách sử dụng một WINDOWmệnh đề, nhưng đó chỉ là rút ngắn cú pháp, không thay đổi kế hoạch truy vấn.

SELECT id, trans_ref_no, amount, trans_date, entity_id
     , SUM(amount) OVER w AS trans_total
     , COUNT(*)    OVER w AS trans_count
FROM   transactiondb
WINDOW w AS (PARTITION BY entity_id, date_trunc('month',trans_date)
             ORDER BY trans_date
             ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING);
  • Cũng sử dụng nhanh hơn một chút count(*), vì idchắc chắn được xác định NOT NULL?
  • Và bạn không cần phải ORDER BY entity_idkể từ khi bạn đãPARTITION BY entity_id

Mặc dù vậy, bạn có thể đơn giản hóa hơn nữa:
Đừng thêm ORDER BYvào định nghĩa cửa sổ, nó không liên quan đến truy vấn của bạn. Sau đó, bạn không cần xác định khung cửa sổ tùy chỉnh:

SELECT id, trans_ref_no, amount, trans_date, entity_id
     , SUM(amount) OVER w AS trans_total
     , COUNT(*)    OVER w AS trans_count
FROM   transactiondb
WINDOW w AS (PARTITION BY entity_id, date_trunc('month',trans_date);

Đơn giản hơn, nhanh hơn, nhưng vẫn chỉ là một phiên bản tốt hơn của những gì bạn , với các tháng tĩnh .

Truy vấn bạn có thể muốn

... không được xác định rõ ràng, vì vậy tôi sẽ dựa trên các giả định sau:

Đếm các giao dịch và số tiền cho mỗi khoảng thời gian 30 ngày trong giao dịch đầu tiên và cuối cùng của bất kỳ giao dịch nào entity_id. Không bao gồm các giai đoạn hàng đầu và dấu vết mà không có hoạt động, nhưng bao gồm tất cả các khoảng thời gian 30 ngày có thể trong các giới hạn bên ngoài.

SELECT entity_id, trans_date
     , COALESCE(sum(daily_amount) OVER w, 0) AS trans_total
     , COALESCE(sum(daily_count)  OVER w, 0) AS trans_count
FROM  (
   SELECT entity_id
        , generate_series (min(trans_date)::timestamp
                         , GREATEST(min(trans_date), max(trans_date) - 29)::timestamp
                         , interval '1 day')::date AS trans_date
   FROM   transactiondb 
   GROUP  BY 1
   ) x
LEFT JOIN (
   SELECT entity_id, trans_date
        , sum(amount) AS daily_amount, count(*) AS daily_count
   FROM   transactiondb
   GROUP  BY 1, 2
   ) t USING (entity_id, trans_date)
WINDOW w AS (PARTITION BY entity_id ORDER BY trans_date
             ROWS BETWEEN CURRENT ROW AND 29 FOLLOWING);

Điều này liệt kê tất cả các khoảng thời gian 30 ngày cho mỗi giai đoạn entity_idvới tổng hợp của bạn và trans_datelà ngày đầu tiên (bao gồm) của giai đoạn. Để nhận giá trị cho từng hàng riêng lẻ, hãy tham gia vào bảng cơ sở một lần nữa ...

Khó khăn cơ bản giống như được thảo luận ở đây:

Định nghĩa khung của cửa sổ không thể phụ thuộc vào giá trị của hàng hiện tại.

Và thay vì gọi generate_series()với timestampđầu vào:

Truy vấn bạn thực sự muốn

Sau khi cập nhật câu hỏi và thảo luận:
Tích lũy các hàng giống nhau entity_idtrong cửa sổ 30 ngày bắt đầu tại mỗi giao dịch thực tế.

Vì dữ liệu của bạn được phân phối thưa thớt, nên sẽ hiệu quả hơn khi chạy tự tham gia với điều kiện phạm vi , tất cả đều hơn vì Postgres 9.1 chưa LATERALtham gia:

SELECT t0.id, t0.amount, t0.trans_date, t0.entity_id
     , sum(t1.amount) AS trans_total, count(*) AS trans_count
FROM   transactiondb t0
JOIN   transactiondb t1 USING (entity_id)
WHERE  t1.trans_date >= t0.trans_date
AND    t1.trans_date <  t0.trans_date + 30  -- exclude upper bound
-- AND    t0.entity_id = 114284  -- or pick a single entity ...
GROUP  BY t0.id  -- is PK!
ORDER  BY t0.trans_date, t0.id

Câu đố SQL.

Một cửa sổ cuộn chỉ có thể có ý nghĩa (liên quan đến hiệu suất) với dữ liệu trong hầu hết các ngày.

Điều này không tổng hợp các bản sao (trans_date, entity_id)mỗi ngày, nhưng tất cả các hàng trong cùng một ngày luôn được bao gồm trong cửa sổ 30 ngày.

Đối với một bảng lớn, một chỉ số bao phủ như thế này có thể giúp ích khá nhiều:

CREATE INDEX transactiondb_foo_idx
ON transactiondb (entity_id, trans_date, amount);

Cột cuối cùng amountchỉ hữu ích nếu bạn nhận được quét chỉ mục từ nó. Khác bỏ nó.

Nhưng nó sẽ không được sử dụng trong khi bạn chọn toàn bộ bảng. Nó sẽ hỗ trợ các truy vấn cho một tập hợp nhỏ.


Điều này có vẻ thực sự tốt, kiểm tra dữ liệu ngay bây giờ và cố gắng hiểu mọi thứ mà truy vấn của bạn thực sự đang thực hiện ...
tufelkinder 20/07/2015

@tufelkinder: Đã thêm một giải pháp cho câu hỏi cập nhật.
Erwin Brandstetter

Xem lại nó bây giờ. Tôi tò mò rằng nó chạy trong SQL Fiddle ... Khi tôi cố chạy nó trực tiếp trên giao dịch của mình, nó bị lỗi vớicolumn "t0.amount" must appear in the GROUP BY clause...
tufelkinder 20/07/2015

@tufelkinder: Tôi cắt trường hợp thử nghiệm xuống còn 100 hàng. sqlfiddle giới hạn kích thước của dữ liệu thử nghiệm. Jake (tác giả) đã giảm giới hạn giới hạn một vài tháng trước để trang web ít bị đình trệ hơn.
Erwin Brandstetter

1
Xin lỗi vì sự chậm trễ, cần thiết để kiểm tra nó trên cơ sở dữ liệu đầy đủ. Câu trả lời của bạn là tuyệt vời sâu sắc và giáo dục, như mọi khi. Cảm ơn bạn!
tufelkinder 21/07/2015
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.