计算加入的列 table

Count columns of joined table

我正在编写一个查询来汇总 Postgres 数据库中的数据:

SELECT products.id, 
   products.NAME, 
   product_types.type_name AS product_type, 
   delivery_types.delivery, 
   products.required_selections, 
   Count(s.id)                AS selections_count, 
   Sum(CASE 
         WHEN ss.status = 'WARNING' THEN 1 
         ELSE 0 
       END)                AS warning_count 
FROM   products 
   JOIN product_types 
     ON product_types.id = products.product_type_id 
   JOIN delivery_types 
     ON delivery_types.id = products.delivery_type_id 
   LEFT JOIN selections_products sp 
          ON products.id = sp.product_id 
   LEFT JOIN selections s 
          ON s.id = sp.selection_id 
   LEFT JOIN selection_statuses ss 
          ON ss.id = s.selection_status_id 
   LEFT JOIN listings l 
          ON ( s.listing_id = l.id 
               AND l.local_date_time BETWEEN 
                   To_timestamp('2014/12/01', 'YYYY/mm/DD' 
                   ) AND 
                   To_timestamp('2014/12/30', 'YYYY/mm/DD') ) 
GROUP  BY products.id, 
      product_types.type_name, 
      delivery_types.delivery 

基本上我们有一个带有选择的产品,这些选择有列表,列表有一个 local_date。我需要一份所有产品的清单以及它们在两个日期之间有多少清单。无论我做什么,我都会计算所有选择(总计)。我觉得我忽略了一些东西。同样的概念也适用于 warning_count。另外,我真的不明白为什么 Postgres 要求我在这里添加 group by

架构如下所示(无论如何您都会关心的部分):

products
  name:string
, product_type:fk
, required_selections:integer
, deliver_type:fk

selections_products
  product_id:fk
, selection_id:fk

selections
  selection_status_id:fk
, listing_id:fk

selection_status
  status:string

listing
 local_date:datetime

无论 listings.local_date_time.

,您都可以 LEFT JOIN 选择所有选项

有解释的空间,我们需要查看具有所有约束和数据类型的实际table定义才能确定。走出困境,我有根据的猜测是你可以通过在 FROM 子句中使用括号来确定连接的优先级来修复你的查询:

SELECT p.id
     , p.name
     , pt.type_name AS product_type
     , dt.delivery
     , p.required_selections
     , count(s.id) AS selections_count
     , sum(CASE WHEN ss.status = 'WARNING' THEN 1 ELSE 0 END) AS warning_count
FROM   products       p
JOIN   product_types  pt ON pt.id = p.product_type_id
JOIN   delivery_types dt ON dt.id = p.delivery_type_id
LEFT   JOIN (  -- LEFT JOIN!
          selections_products sp
   JOIN   selections s  ON s.id  = sp.selection_id  -- INNER JOIN!
   JOIN   listings   l  ON l.id  = s.listing_id     -- INNER JOIN!
                       AND l.local_date_time >= '2014-12-01'
                       AND l.local_date_time <  '2014-12-31'
   LEFT   JOIN selection_statuses ss ON ss.id = s.selection_status_id
   ) ON sp.product_id = p.id
GROUP  BY p.id, pt.type_name, dt.delivery;

这样,您首先用 [INNER] JOIN 消除给定时间范围之外的所有选择,然后 LEFT JOIN 到产品,从而保持 所有 结果中的产品,包括那些不在任何适用选择中的产品。

相关:

选择所有或大部分产品时,这可以重写为更快

SELECT p.id
     , p.name
     , pt.type_name AS product_type
     , dt.delivery
     , p.required_selections
     , COALESCE(s.selections_count, 0) AS selections_count
     , COALESCE(s.warning_count, 0)    AS warning_count
FROM   products       p
JOIN   product_types  pt ON pt.id = p.product_type_id
JOIN   delivery_types dt ON dt.id = p.delivery_type_id
LEFT   JOIN (
   SELECT sp.product_id
        , count(*) AS selections_count
        , count(*) FILTER (WHERE ss.status = 'WARNING') AS warning_count
   FROM   selections_products sp
   JOIN   selections          s  ON s.id  = sp.selection_id
   JOIN   listings            l  ON l.id  = s.listing_id
   LEFT   JOIN selection_statuses ss ON ss.id = s.selection_status_id
   WHERE  l.local_date_time >= '2014-12-01'
   AND    l.local_date_time <  '2014-12-31'
   GROUP  BY 1
   ) s ON s.product_id = p.id;

首先按 product_id 汇总和计算选择和警告,然后 然后 加入产品,这样会更便宜。 (除非您只检索一小部分产品,否则先减少相关行会更便宜。)

相关:

  • Why does the following join increase the query time significantly?

Also, I don't really understand why Postgres requires me to add a group by here.

从 Postgres 9.1 开始,GROUP BY 中的 PK 列覆盖了 相同 table 的所有列。 not 涵盖了 other table 的列,即使它们在功能上是相关的。如果您不想聚合它们,则需要在 GROUP BY 中明确列出它们。

我的第二个查询通过在连接之前聚合从一开始就避免了这个问题。


旁白:很有可能,这不是你想要的:

l.local_date_time BETWEEN To_timestamp('2014/12/01', 'YYYY/mm/DD')
                      AND To_timestamp('2014/12/30', 'YYYY/mm/DD')

因为 date_time 似乎是 timestamp 类型(不是 timestamptz!),你会 include '2014-12-30 00:00',但 排除 当天剩余时间 '2014-12-30'。最好对日期和时间戳使用 ISO 8601 格式,这与 every 语言环境和 datestyle 设置相同。因此:

WHERE  l.local_date_time >= '2014-12-01'
AND    l.local_date_time <  '2014-12-31'

这包括“2014-12-30”的全部,除此之外别无其他。不知道您为什么选择排除“2014-12-31”。也许您真的想包括 2014 年 12 月的所有时间?

WHERE  l.local_date_time >= '2014-12-01'
AND    l.local_date_time <  '2015-01-01'