PostgreSQL区别和格式最快的方法

时间:2015-05-15 07:13:23

标签: sql postgresql distinct aggregate postgresql-performance

我在表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网站中使用它”

4 个答案:

答案 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'))

SQLFiddle demo

答案 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的数据类型为datetimestamp(特别是timestamp with timezone)时,此功能才有效。

下面的选项看起来很有希望,因为它不使用子查询,但事实上它要慢得多(参见下面的注释),可能是因为DISTINCT子句应用于字符串:

SELECT DISTINCT extract(year FROM creation_date)::text
FROM acs_objects;

答案 2 :(得分:4)

我不确定你用它做什么。我可能会考虑使用物化视图

现在,您可以在需要时刷新视图,并使用非常快速方式检索(不同)年份列表(因为数据基本上是静态存储的)。

看看这里:

答案 3 :(得分:4)

假设

  • Current Postgres 9.4
  • creation_date是数据类型timestamp(同样适用于datetimestamptz
  • 1990年到2020年之间的可能时间戳(但rCTE不需要假设)。

基本评估

如果您需要经常且快速地使用此功能,materialized view将是一个好主意,如@Rogier建议的那样。但是你仍然需要一个查询来实现MV。以下查询速度非常快,您可以跳过MV ...

在相关案例中,通常会有一个候选值的查找表,允许更多更快的查询:

@valex's brilliant idea将使用派生表模拟缺少的查找表,因为我们可以使用generate_series()猜测一小组可能的候选值。

索引

所有你需要的是creation_date的基本索引,没有专门的表达式索引 - 对于这里讨论的三种变体中的任何一种:

CREATE INDEX foo ON acs_objects (creation_date);

使用rCTE

模拟松散索引扫描

如果您既没有查找表也没有候选值的派生表,那么仍然有一个非常快的替代方案。基本上,您需要模拟“松散索引扫描”。此查询无论如何

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;

详细说明:

Postgres Wiki.

基于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

你应该看到类似的结果。