通过多个联接对不同行求和


10

架构

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
);

资料

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)
;

因此,我们有:

  • PL中1中的CZ中有3个项目
  • 捷克(CZ)和波兰(PL)获得25和370
  • CZ的费用为350,PL的费用为20
  • CZ可多赚11点,PL可多赚5点

现在,我想获得以下问题的答案:

  1. 我们上个月在每个国家/地区有几件商品?
  2. 每个国家的总收入金额(付款总额。金额)是多少?
  3. 每个国家的总成本(商品价格总和)是多少?
  4. 每个国家的总额外收入(总金额的总和)是多少?

使用以下查询(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;

结果是错误的:

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

CZ的Cost和extra_earned无效-450(而不是350)和16(而不是11)无效。PL的成本和收入也无效-它们加倍。

我了解,在LEFT OUTER JOIN有item.id = 1的item会有2行的情况下(对于其他匹配,依此类推),但是我不知道如何构建适当的查询。

问题

  1. 如何避免在多个表的查询中聚合导致错误的结果?
  2. 在不同值上计算和的最佳方法是什么(在这种情况下为items.id)?

PostgreSQL版本:9.6.1


请参阅我的答案中的选项3:dba.stackexchange.com/questions/17012/help-with-this-query / ...您也可以通过重写OUTER APPLY和使用LATERAL连接来执行选项4 。
ypercubeᵀᴹ

选项3可以使用,但在这种情况下,它将需要Seq Scan付款,这意味着将重新计算所有项目的统计信息。我没有在问题中提到这一点,但我也想按创建时间过滤项目,因此我只需要聚合数据的特定子集。我会更新的问题
Stranger6667

您可以WHERE在子查询中添加子句或联接。但也请使用来检查选项4 LATERAL
ypercubeᵀᴹ

您是要加入JOIN paymentsitems在子查询中添加WHERE 它吗?我需要对所有选项进行基准测试:)
Stranger6667

如果要基于限制子集items.created_at,可以。
ypercubeᵀᴹ

Answers:


9

由于可以有多个payments和多种extrasitem,你碰上了“代理交叉联接”这两个表之间。item_id 加入之前汇总每行item,它应该都是正确的:

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;

考虑“鱼市场”示例:

确切地说,SUM(i.price)在连接到单个n表后,这是不正确的,该表将每个价格乘以相关行的数量。重复执行两次只会使情况变得更糟-并且潜在地在计算上也很昂贵。

哦,由于我们现在不增加行数items,因此我们可以使用便宜的count(*)代替count(DISTINCT i.id)。(idNOT NULL PRIMARY KEY。)

SQL提琴。

但是,如果我要过滤items.created

解决您的评论。

这取决于。我们可以将相同的过滤器应用于payments.createdextras.created吗?

如果是,则只需在子查询中添加过滤器。(在这种情况下似乎不太可能。)

如果不是,但我们仍选择大多数项目,则上述查询仍将是最有效的。子查询中的某些聚合在联接中被消除了,但这仍然比更复杂的查询便宜。

如果否,并且我们只选择一小部分项目,则建议相关子查询或LATERAL联接。例子:


谢谢您的回答!但是,如果我想通过items.created哪种最有效的方法进行过滤?我应该额外添加JOINitems子查询(pe在你的例子)来执行这样的过滤如@ypercubeᵀᴹ提到?
Stranger6667

@ Stranger6667:这取决于。确实,这是一个不同的问题。我在上面添加了一个答案。
Erwin Brandstetter

LATERAL JOIN为我工作!谢谢您的
Stranger6667
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.