SUM trên các hàng riêng biệt có nhiều liên kết


10

Lược đồ :

CREATE TABLE "items" (
  "id"            SERIAL                   NOT NULL PRIMARY KEY,
  "country"       VARCHAR(2)               NOT NULL,
  "created"       TIMESTAMP WITH TIME ZONE NOT NULL,
  "price"         NUMERIC(11, 2)           NOT NULL
);
CREATE TABLE "payments" (
  "id"      SERIAL                   NOT NULL PRIMARY KEY,
  "created" TIMESTAMP WITH TIME ZONE NOT NULL,
  "amount"  NUMERIC(11, 2)           NOT NULL,
  "item_id" INTEGER                  NULL
);
CREATE TABLE "extras" (
  "id"      SERIAL                   NOT NULL PRIMARY KEY,
  "created" TIMESTAMP WITH TIME ZONE NOT NULL,
  "amount"  NUMERIC(11, 2)           NOT NULL,
  "item_id" INTEGER                  NULL
);

Dữ liệu :

INSERT INTO items VALUES
  (1, 'CZ', '2016-11-01', 100),
  (2, 'CZ', '2016-11-02', 100),
  (3, 'PL', '2016-11-03', 20),
  (4, 'CZ', '2016-11-04', 150)
;
INSERT INTO payments VALUES
  (1, '2016-11-01', 60, 1),
  (2, '2016-11-01', 60, 1),
  (3, '2016-11-02', 100, 2),
  (4, '2016-11-03', 25, 3),
  (5, '2016-11-04', 150, 4)
;
INSERT INTO extras VALUES
  (1, '2016-11-01', 5, 1),
  (2, '2016-11-02', 1, 2),
  (3, '2016-11-03', 2, 3),
  (4, '2016-11-03', 3, 3),
  (5, '2016-11-04', 5, 4)
;

Vì vậy chúng tôi có:

  • 3 mặt hàng trong CZ trong 1 ở PL
  • 370 kiếm được ở CZ và 25 ở PL
  • 350 chi phí ở CZ và 20 ở PL
  • 11 kiếm thêm ở CZ và 5 kiếm thêm ở PL

Bây giờ tôi muốn nhận câu trả lời cho các câu hỏi sau:

  1. Có bao nhiêu mặt hàng chúng tôi đã có tháng trước ở mỗi quốc gia?
  2. Tổng số tiền kiếm được (tổng số thanh toán.amounts) ở mỗi quốc gia là bao nhiêu?
  3. Tổng chi phí (tổng các mặt hàng.price) ở mỗi quốc gia là bao nhiêu?
  4. Tổng thu nhập thêm (tổng của Extra.amount) ở mỗi quốc gia là bao nhiêu?

Với truy vấn sau ( SQLFiddle ):

SELECT
  country                  AS "group_by",
  COUNT(DISTINCT items.id) AS "item_count",
  SUM(items.price)         AS "cost",
  SUM(payments.amount)     AS "earned",
  SUM(extras.amount)       AS "extra_earned"
FROM items
  LEFT OUTER JOIN payments ON (items.id = payments.item_id)
  LEFT OUTER JOIN extras ON (items.id = extras.item_id)
GROUP BY 1;

Kết quả sai:

 group_by | item_count |  cost  | earned | extra_earned
----------+------------+--------+--------+--------------
 CZ       |          3 | 450.00 | 370.00 |        16.00
 PL       |          1 |  40.00 |  50.00 |         5.00

Chi phí và chi phí thêm cho CZ không hợp lệ - 450 thay vì 350 và 16 thay vì 11. Chi phí và kiếm được cho PL cũng không hợp lệ - chúng được nhân đôi.

Tôi hiểu rằng trong trường hợp LEFT OUTER JOINsẽ có 2 hàng cho mục có mục.id = 1 (và cứ thế cho các trận đấu khác), nhưng tôi không biết cách tạo một truy vấn phù hợp.

Câu hỏi :

  1. Làm thế nào để tránh kết quả sai trong tổng hợp trong các truy vấn trên nhiều bảng?
  2. Cách tốt nhất để tính tổng trên các giá trị riêng biệt (items.id trong trường hợp đó) là gì?

Phiên bản PostgreSQL : 9.6.1


Xem tùy chọn 3 trong câu trả lời của tôi ở đây: dba.stackexchange.com/questions/17012/help-with-this-query/ trộm Bạn cũng có thể thực hiện tùy chọn 4 bằng cách viết lại OUTER APPLYvà sử dụng các phép LATERALnối thay thế.
ypercubeᵀᴹ

Tùy chọn 3 sẽ hoạt động nhưng trong trường hợp đó sẽ yêu cầu Seq Scanthanh toán, điều đó có nghĩa là thống kê sẽ được tính toán lại trên tất cả các mục. Tôi đã không đề cập đến vấn đề này trong câu hỏi nhưng tôi cũng muốn lọc các mục theo thời gian tạo, vì vậy tôi sẽ chỉ cần tập hợp con cụ thể của dữ liệu tổng hợp. Tôi sẽ cập nhật câu hỏi
Stranger6667

Bạn có thể thêm WHEREcác mệnh đề hoặc tham gia trong các truy vấn con. Nhưng kiểm tra tùy chọn 4, quá, sử dụng LATERAL.
ypercubeᵀᴹ

Bạn có nghĩa là THAM GIA paymentsitemstrong truy vấn con và thêm WHERE vào nó? Tôi sẽ cần điểm chuẩn tất cả các tùy chọn :)
Stranger6667

Nếu bạn muốn hạn chế tập hợp con dựa trên items.created_at, có.
ypercubeᵀᴹ

Câu trả lời:


9

Vì có thể có nhiều paymentsvà nhiều extrascho mỗi item, bạn chạy vào một "liên kết chéo proxy" giữa hai bảng đó. Tổng hợp các hàng mỗi item_id trước khi tham gia itemvà tất cả phải chính xác:

SELECT i.country         AS group_by
     , COUNT(*)          AS item_count
     , SUM(i.price)      AS cost
     , SUM(p.sum_amount) AS earned
     , SUM(e.sum_amount) AS extra_earned
FROM  items i
LEFT  JOIN (
   SELECT item_id, SUM(amount) AS sum_amount
   FROM   payments
   GROUP  BY 1
   ) p ON p.item_id = i.id
LEFT  JOIN (
   SELECT item_id, SUM(amount) AS sum_amount
   FROM   extras
   GROUP  BY 1
   ) e ON e.item_id = i.id
GROUP BY 1;

Hãy xem xét ví dụ "fishmarket":

Nói chính xác, SUM(i.price)sẽ không chính xác sau khi tham gia vào một bảng n duy nhất, nhân mỗi giá với số lượng hàng liên quan. Làm điều đó hai lần chỉ làm cho nó tồi tệ hơn - và cũng có khả năng tính toán tốn kém.

Ồ, và vì hiện tại chúng tôi không nhân nhiều hàng items, chúng tôi chỉ có thể sử dụng rẻ hơn count(*)thay vì count(DISTINCT i.id). ( idNOT NULL PRIMARY KEY).

Câu đố SQL.

Nhưng nếu tôi muốn lọc theo items.created?

Giải quyết bình luận của bạn.

Nó phụ thuộc. Chúng ta có thể áp dụng cùng một bộ lọc cho payments.createdextras.created?

Nếu có, thì chỉ cần thêm các bộ lọc trong các truy vấn con là tốt. (Có vẻ như không có khả năng trong trường hợp này.)

Nếu không, nhưng chúng tôi vẫn đang chọn hầu hết các mục , truy vấn trên vẫn sẽ hiệu quả nhất. Một số tập hợp trong các truy vấn con được loại bỏ trong các phép nối, nhưng nó vẫn rẻ hơn các truy vấn phức tạp hơn.

Nếu không, và chúng tôi đang chọn một phần nhỏ của các mục, tôi đề xuất các truy vấn con hoặc LATERALtham gia tương quan . Ví dụ:


Cảm ơn bạn đã trả lời! Nhưng nếu tôi muốn lọc theo cách items.createdhiệu quả nhất để làm điều này là gì? Tôi có nên thêm phụ JOINvào itemscác truy vấn con ( petrong ví dụ của bạn) để thực hiện quá trình lọc như @ ypercubeᵀᴹ đã đề cập không?
Stranger6667

@ Stranger6667: Nó phụ thuộc. Và đó là một câu hỏi khác nhau, thực sự. Tôi đã thêm một câu trả lời ở trên.
Erwin Brandstetter

LATERAL JOINlàm việc cho tôi Cảm ơn bạn đã giải thích rõ ràng :)
Stranger6667
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.