Postgres - 如何返回缺失数据计数为 0 的行?
Posted
技术标签:
【中文标题】Postgres - 如何返回缺失数据计数为 0 的行?【英文标题】:Postgres - how to return rows with 0 count for missing data? 【发布时间】:2010-09-25 15:48:01 【问题描述】:我有几年(2003-2008)的数据分布不均(日期)。我想查询一组给定的开始和结束日期的数据,并按 PostgreSQL 8.3 (http://www.postgresql.org/docs/8.3/static/functions-datetime.html#FUNCTIONS-DATETIME-TRUNC) 中支持的任何时间间隔(日、周、月、季度、年)对数据进行分组。
问题在于,某些查询会在要求的时间段内给出连续的结果, 作为这个:
select to_char(date_trunc('month',date), 'YYYY-MM-DD'),count(distinct post_id)
from some_table where category_id=1 and entity_id = 77 and entity2_id = 115
and date <= '2008-12-06' and date >= '2007-12-01' group by
date_trunc('month',date) order by date_trunc('month',date);
to_char | count
------------+-------
2007-12-01 | 64
2008-01-01 | 31
2008-02-01 | 14
2008-03-01 | 21
2008-04-01 | 28
2008-05-01 | 44
2008-06-01 | 100
2008-07-01 | 72
2008-08-01 | 91
2008-09-01 | 92
2008-10-01 | 79
2008-11-01 | 65
(12 rows)
但他们中的一些人错过了一些间隔,因为没有数据存在,就像这个:
select to_char(date_trunc('month',date), 'YYYY-MM-DD'),count(distinct post_id)
from some_table where category_id=1 and entity_id = 75 and entity2_id = 115
and date <= '2008-12-06' and date >= '2007-12-01' group by
date_trunc('month',date) order by date_trunc('month',date);
to_char | count
------------+-------
2007-12-01 | 2
2008-01-01 | 2
2008-03-01 | 1
2008-04-01 | 2
2008-06-01 | 1
2008-08-01 | 3
2008-10-01 | 2
(7 rows)
所需的结果集在哪里:
to_char | count
------------+-------
2007-12-01 | 2
2008-01-01 | 2
2008-02-01 | 0
2008-03-01 | 1
2008-04-01 | 2
2008-05-01 | 0
2008-06-01 | 1
2008-07-01 | 0
2008-08-01 | 3
2008-09-01 | 0
2008-10-01 | 2
2008-11-01 | 0
(12 rows)
缺失条目的计数为 0。
我已经看到早期关于 Stack Overflow 的讨论,但它们似乎并没有解决我的问题,因为我的分组期是(日、周、月、季度、年)之一,并由应用程序决定运行时。因此,我猜像左连接与日历表或序列表这样的方法无济于事。
我目前的解决方案是使用日历模块在 Python(在 Turbogears 应用中)填补这些空白。
有没有更好的方法来做到这一点。
【问题讨论】:
【参考方案1】:这个问题很老了。但是由于其他用户选择它作为新副本的主人,我正在添加一个正确的答案。
适当的解决方案
SELECT *
FROM (
SELECT day::date
FROM generate_series(timestamp '2007-12-01'
, timestamp '2008-12-01'
, interval '1 month') day
) d
LEFT JOIN (
SELECT date_trunc('month', date_col)::date AS day
, count(*) AS some_count
FROM tbl
WHERE date_col >= date '2007-12-01'
AND date_col <= date '2008-12-06'
-- AND ... more conditions
GROUP BY 1
) t USING (day)
ORDER BY day;
当然是LEFT JOIN
。
generate_series()
可以动态生成时间戳表,而且速度非常快。
在您加入之前聚合通常会更快。我最近在这个相关答案中在 sqlfiddle.com 上提供了一个测试用例:
PostgreSQL - order by an array将timestamp
转换为date
(::date
) 以获得基本格式。更多使用to_char()
。
GROUP BY 1
是引用第一个输出列的语法简写。也可以是GROUP BY day
,但这可能与现有的同名列冲突。或者GROUP BY date_trunc('month', date_col)::date
,但这对我来说太长了。
使用date_trunc()
的可用间隔参数。
count()
never produces NULL
(0
表示没有行),但 LEFT JOIN
可以。
要在外部SELECT
中返回0
而不是NULL
,请使用COALESCE(some_count, 0) AS some_count
。 The manual.
对于更通用的解决方案或任意时间间隔,请考虑以下密切相关的答案:
Best way to count records by arbitrary time intervals in Rails+Postgres【讨论】:
是否可以使用 CTE 来完成此任务? @zam6ak:当然,以各种方式。但你为什么想要? CTE 会更慢。只有在 Postgres 中需要它们时才使用 CTE。如果您有用例,我建议您提出一个新问题。您可以随时链接到此答案的上下文,并在此处发表评论,链接到相关问题并通知我。 谢谢,我发了一个新的question 这个查询正是我要找的,谢谢你的回答。有没有办法让 some_count 返回 0 而不是空数据? @Paul:是的,你错过了重要的部分。COALESCE(count(*), 0)
从不 有意义。我写道:在外部SELECT
使用COALESCE(some_count, 0) AS some_count
。 count(*)
发生在 LEFT JOIN
之前的子查询中。【参考方案2】:
您可以使用
创建去年(比如说)所有第一天的列表select distinct date_trunc('month', (current_date - offs)) as date
from generate_series(0,365,28) as offs;
date
------------------------
2007-12-01 00:00:00+01
2008-01-01 00:00:00+01
2008-02-01 00:00:00+01
2008-03-01 00:00:00+01
2008-04-01 00:00:00+02
2008-05-01 00:00:00+02
2008-06-01 00:00:00+02
2008-07-01 00:00:00+02
2008-08-01 00:00:00+02
2008-09-01 00:00:00+02
2008-10-01 00:00:00+02
2008-11-01 00:00:00+01
2008-12-01 00:00:00+01
然后你就可以加入那个系列了。
【讨论】:
我试过这个:pastebin.com/f6f44e58b 仍然是 7 行。加入有什么问题吗 虽然我认为 (start, stop , step) 必须在进行查询之前仔细填写,并且我怀疑在足够长的时间内 28 的步骤可能会失败,你怎么看? 以28天为步长,每个月都会至少命中一次,因为最短的月份是28天;不过,请随意使用任何较小的步骤。它偶尔会在同一个月出现两次,因此使用了 DISTINCT 过滤器。 我希望你没有删除 pastebin 以获得正确答案:(【参考方案3】:您可以在运行时创建一个临时表并在其上留下连接。这似乎是最有意义的。
【讨论】:
对不起,我没有时间研究实际可行性以上是关于Postgres - 如何返回缺失数据计数为 0 的行?的主要内容,如果未能解决你的问题,请参考以下文章