几点注意事项:
简单的表查询day
SELECT COUNT(DISTINCT day)
FROM days
WHERE day BETWEEN '2010-01-01' AND '2011-01-01';
While day
定义为 PK,DISTINCT
只是昂贵的噪音。
具有相关子查询的递归 CTE
如果有的话,这是替代方案no day
table具有独特的条目。如果每天有多行到多行,则该技术是有价值的,因此相当于松散索引扫描实际上比简单的索引扫描更快DISTINCT
在基表上:
WITH RECURSIVE cte AS (
( -- parentheses required because of LIMIT
SELECT day
FROM data
WHERE day >= '2010-01-01' -- exclude irrelevant rows early
ORDER BY 1
LIMIT 1
)
UNION ALL
SELECT (SELECT day FROM data
WHERE day > c.day
AND day < '2011-01-01' -- see below
ORDER BY 1
LIMIT 1)
FROM cte c
WHERE day IS NOT NULL -- necessary because corr. subq. always returns row
)
SELECT count(*) AS ct
FROM cte
WHERE day IS NOT NULL;
Index
仅与匹配索引结合使用才有意义data
:
CREATE INDEX data_day_idx ON data (day);
day
必须是领先的列。您在问题中的索引(id, day)
也可以使用,但效率低得多:
- PostgreSQL 中索引的工作
- 复合索引也适合第一个字段的查询吗?
Notes
尽早排除不相关的行要便宜得多。我将您的谓词集成到查询中。
详细解释:
- 优化 GROUP BY 查询以检索每个用户的最新行
手头的情况甚至更简单——实际上是最简单的。
您原来的时间范围是day BETWEEN '2010-01-01' AND '2011-01-01'
. But BETWEEN .. AND ..
includes上限和下限,因此您将获得 2010 年的全部数据以及 2011-01-01 的数据。你可能想要exclude上限。使用d.day < '2011-01-01'
(not <=
). See:
EXISTS
对于这种特殊情况
由于您正在测试一系列可枚举的天数(而不是具有无限数量可能值的范围),因此您可以使用EXISTS半连接:
SELECT count(*) AS ct
FROM generate_series(timestamp '2010-01-01'
, timestamp '2010-12-31'
, interval '1 day') AS d(day)
WHERE EXISTS (SELECT FROM data WHERE day = d.day::date);
为什么是这种形式generate_series()
最佳的?
- 在 PostgreSQL 中生成两个日期之间的时间序列
同样的简单索引再次必不可少。
db<>fiddle here demonstrating both with big test table.
Old sqlfiddle