PostgreSQL Distinct 和 Format 的最快方法

Fastest way to PostgreSQL Distinct and Format

我在 table 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

有什么方法可以让它更快吗? -"I need to use this in ADP Website"

在您的第二次尝试中,您从子查询中获得了不同的日期,然后您将其全部转换为字符串表示形式,然后 select 不同的日期。那是相当低效的。最好首先从子查询中的 creation_date 中提取不同的年份,然后简单地将它们转换为主查询中的文本:

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

如果您在 table 上创建 INDEX,查询应该 运行 更快:

CREATE INDEX really_fast ON acs_objects((extract(year FROM creation_date)));

但是,这可能会影响您 table 的其他用途,尤其是当您有许多修改语句(插入、更新、删除)时。这仅在 creation_date 的数据类型为 datetimestamp 时有效(特别是 timestamp with timezone)。

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

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

我不知道你用它做什么。我可能会考虑使用 物化视图.

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

看这里:

我认为你不应该 select distinct 这个巨大的 table。取而代之的是尝试生成一个短的 years sequence(比如从 1900 到 2100)和 select 从这个序列中只存在 acs_objects table 中的年份。结果集将是相同的,但我认为它会更快。 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

Is there any way to make it faster?

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

基本评估

如果您经常且快速地需要它,并且写入 table 的次数很少或 predictable(例如:新行总是有当前时间),建议 materialized view would be fastest, like .但是您仍然需要一个查询来实现它。我要建议的查询非常快,您可能会跳过 MV ...

在相关情况下,通常会查找 table 候选值,从而实现 更快的查询:

  • Optimize groupwise maximum query

本案例的假设

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

使用 rCTE 模拟松散索引扫描

如果您既没有查找 table 也没有带有候选值的派生 table,仍然有一个非常快速的替代方案。基本上,您需要 模拟“索引跳过扫描”,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;

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

此技术的详细信息:

  • Optimize GROUP BY query to retrieve latest row per user
  • Postgres Wiki

基于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'
   );

db<>fiddle here 证明两者。
slfiddle

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

相关:

  • Generating time series between two dates in PostgreSQL