【问题标题】:Fastest way to PostgreSQL Distinct and FormatPostgreSQL 区分和格式化的最快方法
【发布时间】:2015-07-27 00:46:17
【问题描述】:

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

【问题讨论】:

  • 你试过extract吗?
  • 您是否为该列编制索引?
  • @Rogier 我还不知道如何使用索引。
  • @Leonel 好吧,你不要“使用”它。但我可以想象如果列被索引,排序会更快,这可以提高性能。在我的 MySQL 表(325.000 行)上,索引后性能提升为 +/- 20%。
  • extract(year FROM creation_date) 上的索引对于这个特定的查询来说确实很好,而且速度很快,但它可能会对表的其他用途产生负面影响。

标签: sql postgresql distinct aggregate postgresql-performance


【解决方案1】:

我认为你不应该从这个巨大的表格中选择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

【讨论】:

  • 哇,这 12~15 毫秒太棒了,但问题是关于“区分和格式”,所以我不能接受这个作为答案,但我可以给你 +1,谢谢!
  • @Leonel:当您提出问题时,您应该很高兴得到跳出框框思考的答案并且仍然返回正确答案(当然您可以格式化y)。在 SQL 中获取结果总是有多种方法。
  • 很好的答案,跳出框框思考的好例子!
  • 正是这个!!!!在 SQL 中,您应该始终考虑您想知道的内容——您想知道“此表中存在哪些年份”——并且您知道可能的年份范围。所以这是合乎逻辑的方法:检查每年是否至少有一个匹配项。 -- 我认为最好的是,如果你可以在表格中创建从 MIN(year) 到 MAX(year) 的 Range Dynamic,它甚至可以更快——并且一些糟糕的程序员不必在 85 年内更改它.
  • WHERE creation_date &gt;= DATE (''||t.y||'-01-01') AND creation_date &lt; DATE (''||t.y + 1||'-01-01') 将是我的选择,如果我不确定该列是日期还是日期时间,或者如果我知道它是日期,它是否永远不能成为日期时间。
【解决方案2】:

在您的第二次尝试中,您从子查询中获得不同的日期,然后您将其全部转换为字符串表示,然后选择不同的日期。那是相当低效的。最好先在子查询中从 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)时才有效。

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

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

【讨论】:

  • 必须使用派生表吗? SELECT DISTINCT extract(year FROM creation_date)::text AS year FROM acs_objects; 会产生不同的计划吗?我问是因为我没有安装 PostgreSQL,也无法检查自己。
  • 我只是把它写成一个编辑。最好不要有子查询,是的。但运行时间差异可能很小。
  • 祝你好运,进一步调整这一点!我很想听听您对我的第二个选项的看法。
  • @Patrick 第二个给我 80~90 秒,第一个更好。
  • 这确实是一个很大的不同。更新了答案。
【解决方案3】:

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

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

看看这里:

【讨论】:

    【解决方案4】:

    有什么方法可以让它更快?

    哦,是的,快得多。 (2021 年更新。)

    基本评估

    如果您经常快速地需要这个,并且对表的写入很少或可预测(例如:新行总是有当前时间),materialized view 将是最快的,就像建议的@Roger。但是你仍然需要一个查询来实现它。而且我要建议的查询是如此之快,以至于您可能会跳过 MV ...

    在相关情况下,通常会有一个包含候选值的查找表,以实现更快更快的查询:

    假设

    • Postgres 9.4 或更高版本。
    • creation_date 是数据类型 timestamp(也适用于 datetimestamptz)。
    • 时间戳的实际范围未知。
    • acs_objects(creation_date) 上有一个 btree 索引。

    使用 rCTE 模拟松散索引扫描

    如果您既没有查找表也没有包含候选值的派生表,那么仍然有一个非常快速的替代方案。基本上,您需要模拟“索引跳过扫描”,a.k. a.“松散索引扫描”。此查询在任何情况下都有效

    WITH RECURSIVE cte AS (
       SELECT date_trunc('year', max(creation_date)) AS y
       FROM   acs_objects
    
       UNION ALL
       SELECT (SELECT date_trunc('year', max(creation_date))
               FROM   acs_objects
               WHERE  creation_date < cte.y)
       FROM   cte
       WHERE  cte.y IS NOT NULL
       )
    SELECT to_char(y, 'YYYY') AS year
    FROM   cte
    WHERE  cte.y IS NOT NULL;
    

    可能最快:自上而下,将每个时间戳截断到年初,然后找到较早的行中的最新行;重复。

    此技术的详细信息:

    基于generate_series()

    valex的想法可以通过generate_series() producing timestamp values based on the actual range of existing years更高效地实现:

    SELECT to_char(y, 'YYYY') AS year
    FROM  (
       SELECT generate_series(date_trunc('year', min(creation_date))
                            , max(creation_date)
                            , interval  '1 year')
       FROM   acs_objects
       ) t(y)
    WHERE  EXISTS (
       SELECT FROM acs_objects 
       WHERE creation_date >= y
       AND   creation_date <  y + interval '1 year'
       );
    

    dbfiddle here 演示了两者。
    slfiddle

    如果年份范围内的差距很小,那么这可能会更快。但无论表大小如何,都只需要几毫秒或更短的时间。

    相关:

    【讨论】:

    • +1。我认为使用@Falco 评论我关于使用表中的MIN(year)MAX(year) 而不是1900 和2020)的答案,并且您的generate_series 会运行得更快。但我想这已经取决于表中的年份分布。
    • @valex:是的,从 min 到 max 的范围更清晰。我添加了一个链接。值范围内仍可能存在重大差距,这将增加成本。我会使用在任何情况下都有效的 rCTE - 比完美的查找表稍慢,但比提供多个不相关值的派生表快。
    猜你喜欢
    • 1970-01-01
    • 2011-11-13
    • 1970-01-01
    • 1970-01-01
    • 2017-12-11
    • 2019-02-11
    • 2010-10-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多