PostgreSQL区别和格式最快的方法

Leo*_*nto 9 sql postgresql aggregate distinct postgresql-performance

我在表中有350万行acs_objects,我需要检索creation_date具有年份格式和不同的列.

我的第一次尝试:180~200 Sec (15 Rows Fetched)

SELECT DISTINCT to_char(creation_date,'YYYY') FROM acs_objects
Run Code Online (Sandbox Code Playgroud)

我的第二次尝试:35~40 Sec (15 Rows Fetched)

SELECT DISTINCT to_char(creation_date,'YYYY')
FROM (SELECT DISTINCT creation_date FROM acs_objects) AS distinct_date
Run Code Online (Sandbox Code Playgroud)

有没有办法让它更快? - "我需要在ADP网站上使用它"

val*_*lex 14

我想你不应该distinct从这张巨大的桌子中选择.而是尝试生成短年序列(例如从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'))
Run Code Online (Sandbox Code Playgroud)

SQLFiddle demo

  • 正是这个!!!! 在SQL中,您应该始终考虑您想知道的内容 - 您想知道"此表中存在哪些年份" - 并且您知道可能的年份范围.所以这是合乎逻辑的方式:检查每年是否至少有一场比赛. - 我认为最好的情况是,如果你可以在表格中创建从MIN(年份)到MAX(年份)的范围动态,它甚至可以更快 - 而且一些糟糕的程序员不必在85年内改变它. (5认同)
  • 伟大的答案,在盒子外思考的精彩例子! (4认同)
  • @Leonel:当你提出一个问题时,你应该很高兴能得到开箱即用的答案并仍然回答正确的答案(当然你可以格式化'y`).在SQL中总有多种方法可以获得结果. (3认同)

Pat*_*ick 7

在第二次尝试中,您将从子查询中获取不同的日期,然后您将所有日期转换为字符串表示,然后选择不同的日期.这是相当低效的.最好先从creation_date子查询中提取不同的年份,然后将它们转换为主查询中的文本:

SELECT year::text
FROM (
  SELECT DISTINCT extract(year FROM creation_date) AS year FROM acs_objects
) AS distinct_years;
Run Code Online (Sandbox Code Playgroud)

如果INDEX在表上创建一个,查询应该运行得更快:

CREATE INDEX really_fast ON acs_objects((extract(year FROM creation_date)));
Run Code Online (Sandbox Code Playgroud)

但是,这可能会影响表的其他用途,特别是如果您有许多修改语句(插入,更新,删除).这只有creation_date在数据类型为datetimestamp(特别是不是timestamp with timezone)的情况下才有效.

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

SELECT DISTINCT extract(year FROM creation_date)::text
FROM acs_objects;
Run Code Online (Sandbox Code Playgroud)

  • 你必须使用派生表吗?`SELECT DISTINCT extract(year FROM creation_date):: text AS year FROM acs_objects;`会产生不同的计划吗?我问,因为我没有安装PostgreSQL,无法检查自己. (2认同)

Rog*_*ger 5

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

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

看看这里:


Erw*_*ter 5

有什么办法可以让它更快吗?

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

基础评估

如果您经常且快速地需要此操作,并且对表的写入很少或可预测(例如:新行始终具有当前时间),那么物化视图将是最快的,就像@Roger建议的那样。但您仍然需要一个查询来实现它。我要建议的查询太快了,你可能会跳过 MV ...

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

本案例的假设:

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

使用 rCTE 模拟松散索引扫描

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

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;
Run Code Online (Sandbox Code Playgroud)

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

该技术的详细信息:

基于generate_series()

valex 的想法可以通过根据现有年份的实际范围generate_series()生成timestamp值来更有效地实现:

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'
   );
Run Code Online (Sandbox Code Playgroud)

db<>fiddle在这里演示了两者。
骗子

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

有关的: