我在表acs_objects
中有350万行,我需要检索具有年份格式且不同的列creation_date
。
我的第一次尝试:180~200 Sec (15 Rows Fetched)
SELECT DISTINCT to_char(creation_date,'YYYY') FROM acs_objects
我的第二次尝试:35~40 Sec (15 Rows Fetched)
SELECT DISTINCT to_char(creation_date,'YYYY')
FROM (SELECT DISTINCT creation_date FROM acs_objects) AS distinct_date
有没有办法让它更快? - “我需要在ADP网站中使用它”
答案 0 :(得分:14)
我认为你不应该从这张巨大的桌子中选择distinct
。相反,尝试生成一个简短的years sequence(例如从1900年到2100年),并从这个序列中选择仅存在于acs_objects
表中的年份。结果集将是相同的,但我认为它会更快。 EXISTS子查询必须在索引字段creation_date
上快速运行。
SELECT y
FROM
(
select generate_series(1900,2100) as y
) as t
WHERE EXISTS (SELECT 1 FROM acs_objects
WHERE creation_date >= DATE (''||t.y||'-01-01')
AND creation_date < DATE (''||t.y + 1||'-01-01'))
答案 1 :(得分:7)
在第二次尝试中,您将从子查询中获取不同的日期,然后您将所有日期转换为字符串表示形式,然后选择不同的日期。这是相当低效的。最好首先从子查询中的creation_date
中提取不同年份,然后将它们转换为主查询中的文本:
SELECT year::text
FROM (
SELECT DISTINCT extract(year FROM creation_date) AS year FROM acs_objects
) AS distinct_years;
如果您在表格上创建INDEX
,则查询的运行速度应该更快:
CREATE INDEX really_fast ON acs_objects((extract(year FROM creation_date)));
但是,这可能会影响表的其他用途,特别是如果您有许多修改语句(插入,更新,删除)。只有当creation_date
的数据类型为date
或timestamp
(特别是timestamp with timezone
)时,此功能才有效。
下面的选项看起来很有希望,因为它不使用子查询,但事实上它要慢得多(参见下面的注释),可能是因为DISTINCT
子句应用于字符串:
SELECT DISTINCT extract(year FROM creation_date)::text
FROM acs_objects;
答案 2 :(得分:4)
我不确定你用它做什么。我可能会考虑使用物化视图。
现在,您可以在需要时刷新视图,并使用非常快速方式检索(不同)年份列表(因为数据基本上是静态存储的)。
看看这里:
答案 3 :(得分:4)
creation_date
是数据类型timestamp
(同样适用于date
或timestamptz
。如果您需要经常且快速地使用此功能,materialized view将是一个好主意,如@Rogier建议的那样。但是你仍然需要一个查询来实现MV。以下查询速度非常快,您可以跳过MV ...
在相关案例中,通常会有一个候选值的查找表,允许更多更快的查询:
@valex's brilliant idea将使用派生表模拟缺少的查找表,因为我们可以使用generate_series()
猜测一小组可能的候选值。
所有你需要的是creation_date
的基本索引,没有专门的表达式索引 - 对于这里讨论的三种变体中的任何一种:
CREATE INDEX foo ON acs_objects (creation_date);
如果您既没有查找表也没有候选值的派生表,那么仍然有一个非常快的替代方案。基本上,您需要模拟“松散索引扫描”。此查询无论如何 :
WITH RECURSIVE cte AS (
(
SELECT creation_date AS y
FROM acs_objects
ORDER BY creation_date
LIMIT 1
)
UNION ALL
SELECT u.creation_date
FROM cte c
, LATERAL (
SELECT creation_date
FROM acs_objects
WHERE creation_date >= date_trunc('year', c.y + interval '1 year')
ORDER BY creation_date
LIMIT 1
) u
)
SELECT to_char(y, 'YYYY') AS year
FROM cte;
详细说明:
generate_series()
为了完整起见,可以使用generate_series()
producing timestamp
values的替代形式以及一些调整来更有效地实现valex的想法:
SELECT to_char(y, 'YYYY') AS year
FROM generate_series(timestamp '1900-1-1 0:0'
, timestamp '2020-1-1 0:0'
, interval '1 year') t(y)
WHERE EXISTS (
SELECT 1 FROM acs_objects
WHERE creation_date >= y
AND creation_date < y + interval '1 year'
);
SQL Fiddle 展示两者。
如果你无法可靠地猜出可能的年龄范围,你可以提取min(creation_date)
和max(creation_date)
:
我对一个基本的临时表进行了快速测试,其中包含100k行和第9.4页中提到的索引。 EXPLAIN (ANALYZE, TIMING OFF)
的最佳5分:
总查询执行时间:
valex generate_series: 3.193 ms
erwin generate_series: 1.360 ms
erwin rCTE: 1.044 ms
你应该看到类似的结果。