【问题标题】:Count columns of joined table计算联接表的列数
【发布时间】:2015-12-24 20:00:25
【问题描述】:

我正在编写一个查询来汇总 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

【问题讨论】:

  • 您的 Postgres 版本? count(f) 中的f 来自哪里?当你写how many listings时,如果同一listing的两个选择包含相同的产品,你是否要计算listing两次?

标签: sql postgresql join left-join aggregate-functions


【解决方案1】:

无论listings.local_date_time如何,您都可以选择LEFT JOIN

有解释的余地​​,我们需要查看具有所有约束和数据类型的实际表定义。冒个险,我有根据的猜测是,您可以通过在 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 的选择和警告,然后然后加入产品会更便宜。 (除非您只检索一小部分产品,否则先减少相关行会更便宜。)

相关:


另外,我真的不明白为什么 Postgres 要求我在这里添加一个组。

自 Postgres 9.1 起,GROUP BY 中的 PK 列涵盖了 same 表的所有列。这覆盖其他表的列,即使它们在功能上是依赖的。如果您不想汇总它们,则需要在 GROUP BY 中明确列出它们。

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


除此之外:很可能,这并不能满足您的要求:

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!),您将包含“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'

【讨论】:

  • 很好的答案——非常感谢您抽出宝贵的时间来解释它。接受。
猜你喜欢
  • 1970-01-01
  • 2013-01-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-19
  • 2020-07-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多