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
的数据类型为 date
或 timestamp
时有效(特别是 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'))
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
(也适用于 date
或 timestamptz
)。
- 时间戳的实际范围未知。
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
我在 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
的数据类型为 date
或 timestamp
时有效(特别是 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'))
Is there any way to make it faster?
哦,是的,快得多。 (2021 年更新。)
基本评估
如果您经常且快速地需要它,并且写入 table 的次数很少或 predictable(例如:新行总是有当前时间),建议 materialized view would be fastest, like
在相关情况下,通常会查找 table 候选值,从而实现 多 更快的查询:
- Optimize groupwise maximum query
本案例的假设:
- Postgres 9.4 或更高版本。
creation_date
是数据类型timestamp
(也适用于date
或timestamptz
)。- 时间戳的实际范围未知。
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