如何采用单独列排序的 DISTINCT ON 子查询,并使其快速?
How do I take a DISTINCT ON subquery that is ordered by a separate column, and make it fast?
(又名 - 使用与问题“Selecting rows ordered by some column and distinct on another”非常相似的查询和数据,我如何才能快速将其获取到 运行)。 Postgres 11.
我有 table prediction
和 (article_id, prediction_date, predicted_as, article_published_date)
表示分类器对一组文章的输出。
经常将新文章添加到单独的 table(由 FK article_id
表示),并在我们调整分类器时添加新的预测。
示例数据:
| id | article_id | predicted_as | prediction_date | article_published_date
| 1009381 | 362718 | negative | 2018-07-27 | 2018-06-26
| 1009382 | 362718 | positive | 2018-08-12 | 2018-06-26
| 1009383 | 362719 | positive | 2018-08-13 | 2010-09-22
| 1009384 | 362719 | positive | 2018-09-28 | 2010-09-22
| 1009385 | 362719 | negative | 2018-10-01 | 2010-09-22
创建table脚本:
create table prediction
(
id serial not null
constraint prediction_pkey
primary key,
article_id integer not null
constraint prediction_article_id_fkey
references article,
predicted_as classifiedas not null,
prediction_date date not null,
article_published_date date not null
);
create index prediction_article_id_prediction_date_idx
on prediction (article_id asc, prediction_date desc);
我们经常想查看每篇文章的最新分类。为此,我们使用:
SELECT DISTINCT ON (article_id) article_id, id, article_published_date
FROM prediction
ORDER BY article_id, prediction_date desc
其中 returns 类似于:
| id | article_id | predicted_as | prediction_date | article_published_date
| 120950 | 1 | negative | 2018-06-29 | 2018-03-25
| 120951 | 2 | negative | 2018-06-29 | 2018-03-19
使用 (article_id, prediciton_date desc)
上的索引,此查询 运行 非常快(~15 毫秒)。这是解释计划:
Unique (cost=0.56..775374.53 rows=1058394 width=20)
-> Index Scan using prediction_article_id_prediction_date_id_idx on prediction (cost=0.56..756071.98 rows=7721023 width=20)
到目前为止一切顺利。
当我想按 article_published_field 对结果进行排序时出现问题。例如:
explain (analyze, buffers)
select *
from (
select distinct on (article_id) article_id, id, article_published_date
from prediction
order by article_id, prediction_date desc
) most_recent_predictions
order by article_published_date desc
limit 3;
这可行,但查询需要大约 3-4 秒才能到达 运行,这使得直接使用它来响应 Web 请求太慢了。
这里是解释计划:
Limit (cost=558262.52..558262.53 rows=3 width=12) (actual time=4748.977..4748.979 rows=3 loops=1)
Buffers: shared hit=7621849 read=9051
-> Sort (cost=558262.52..560851.50 rows=1035593 width=12) (actual time=4748.975..4748.976 rows=3 loops=1)
Sort Key: most_recent_predictions.article_published_date DESC
Sort Method: top-N heapsort Memory: 25kB
Buffers: shared hit=7621849 read=9051
-> Subquery Scan on most_recent_predictions (cost=0.43..544877.67 rows=1035593 width=12) (actual time=0.092..4508.464 rows=1670807 loops=1)
Buffers: shared hit=7621849 read=9051
-> Result (cost=0.43..534521.74 rows=1035593 width=16) (actual time=0.092..4312.916 rows=1670807 loops=1)
Buffers: shared hit=7621849 read=9051
-> Unique (cost=0.43..534521.74 rows=1035593 width=16) (actual time=0.090..4056.644 rows=1670807 loops=1)
Buffers: shared hit=7621849 read=9051
-> Index Scan using prediction_article_id_prediction_date_idx on prediction (cost=0.43..515295.09 rows=7690662 width=16) (actual time=0.089..3248.250 rows=7690662 loops=1)
Buffers: shared hit=7621849 read=9051
Planning Time: 0.130 ms
Execution Time: 4749.007 ms
有什么方法可以使这个查询运行更快,还是我必须求助于刷新物化视图或设置触发系统来快速获取此数据?
供参考:
prediction
table 有 770 万行
prediction
table 中有 170 万个不同的 article_id
(article_id, prediciton_date desc)
上有一个索引,article_published_date desc
上也有一个索引
VACUUM ANALYSE
已经 运行
您 可以尝试 的一件事是使用 window 函数 ROW_NUMBER() OVER(...)
而不是 DISTINCT ON()
(这意味着对 ORDER BY
子句)。此方法在功能上等同于您的第二个查询,并且可能能够利用现有索引:
SELECT *
FROM (
SELECT
article_id,
id,
article_published_date,
ROW_NUMBER() OVER(PARTITION BY article_id ORDER BY prediction_date DESC) rn
FROM prediction
) x WHERE rn = 1
ORDER BY article_published_date DESC
LIMIT 3;
不知你能否完成这项工作:
select article_id, id, article_published_date
from prediction p
where p.prediction_date = (select max(p2.prediction_date)
from prediction p2
where p2.article_id = p.article_id
)
order by article_published_date desc;
然后使用这两个索引:
(article_published_date desc, prediction_date, article_id, id)
(article_id, prediction_date desc)
.
虽然您只需要少量的结果行(在您的示例中为 LIMIT 3
),并且如果 article_published_date
和 prediction_date
之间存在任何正相关,则此查询应该彻底更快,因为它只需要从添加索引的顶部扫描几个元组(并重新检查第二个索引):
有这两个索引:
CREATE INDEX ON prediction (article_published_date DESC, prediction_date DESC, article_id DESC);
CREATE INDEX ON prediction (article_id, prediction_date DESC);
递归查询:
WITH RECURSIVE cte AS (
(
SELECT p.article_published_date, p.article_id, p.prediction_date, ARRAY[p.article_id] AS a_ids
FROM prediction p
WHERE NOT EXISTS ( -- no later row for same article
SELECT FROM prediction
WHERE article_id = p.article_id
AND prediction_date > p.prediction_date
)
ORDER BY p.article_published_date DESC, p.prediction_date DESC, p.article_id DESC
LIMIT 1
)
UNION ALL
SELECT p.article_published_date, p.article_id, p.prediction_date, a_ids || p.article_id
FROM cte c, LATERAL (
SELECT p.article_published_date, p.article_id, p.prediction_date
FROM prediction p
WHERE (p.article_published_date, p.prediction_date, p.article_id)
< (c.article_published_date, c.prediction_date, c.article_id)
AND p.article_id <> ALL(a_ids) -- different article
AND NOT EXISTS ( -- no later row for same article
SELECT FROM prediction
WHERE article_id = p.article_id
AND prediction_date > p.prediction_date
)
ORDER BY p.article_published_date DESC, p.prediction_date DESC, p.article_id DESC
LIMIT 1
) p
)
SELECT article_published_date, article_id, prediction_date
FROM cte
LIMIT 3;
这里有一个 plpgsql 解决方案 做同样的事情,可能稍微快一点:
CREATE OR REPLACE FUNCTION f_top_n_predictions(_n int = 3)
RETURNS TABLE (_article_published_date date, _article_id int, _prediction_date date) AS
$func$
DECLARE
a_ids int[];
BEGIN
FOR _article_published_date, _article_id, _prediction_date IN
SELECT article_published_date, article_id, prediction_date
FROM prediction
ORDER BY article_published_date DESC, prediction_date DESC, article_id DESC
LOOP
IF _article_id = ANY(a_ids)
OR EXISTS (SELECT FROM prediction p
WHERE p.article_id = _article_id
AND p.prediction_date > _prediction_date) THEN
-- do nothing
ELSE
RETURN NEXT;
a_ids := a_ids || _article_id;
EXIT WHEN cardinality(a_ids) >= _n;
END IF;
END LOOP;
END
$func$ LANGUAGE plpgsql;
致电:
SELECT * FROM f_top_n_predictions();
如果对您有用,我会添加解释,因为解释比查询本身更有效。
除此之外,每篇文章有多个预测,并且有额外的 table article
,此查询成为竞争者:
SELECT p.*
FROM article a
CROSS JOIN LATERAL (
SELECT p.article_published_date, p.article_id, p.prediction_date
FROM prediction p
WHERE p.article_id = a.id
ORDER BY p.prediction_date DESC
LIMIT 1
) p
ORDER BY p.article_published_date DESC;
但是如果上面的查询完成了工作,你就不需要这个了。变得有趣的更大或没有 LIMIT
.
基础知识:
- Optimize GROUP BY query to retrieve latest record per user
- Can spatial index help a “range - order by - limit” query
db<>fiddlehere,演示全部.
(又名 - 使用与问题“Selecting rows ordered by some column and distinct on another”非常相似的查询和数据,我如何才能快速将其获取到 运行)。 Postgres 11.
我有 table prediction
和 (article_id, prediction_date, predicted_as, article_published_date)
表示分类器对一组文章的输出。
经常将新文章添加到单独的 table(由 FK article_id
表示),并在我们调整分类器时添加新的预测。
示例数据:
| id | article_id | predicted_as | prediction_date | article_published_date
| 1009381 | 362718 | negative | 2018-07-27 | 2018-06-26
| 1009382 | 362718 | positive | 2018-08-12 | 2018-06-26
| 1009383 | 362719 | positive | 2018-08-13 | 2010-09-22
| 1009384 | 362719 | positive | 2018-09-28 | 2010-09-22
| 1009385 | 362719 | negative | 2018-10-01 | 2010-09-22
创建table脚本:
create table prediction
(
id serial not null
constraint prediction_pkey
primary key,
article_id integer not null
constraint prediction_article_id_fkey
references article,
predicted_as classifiedas not null,
prediction_date date not null,
article_published_date date not null
);
create index prediction_article_id_prediction_date_idx
on prediction (article_id asc, prediction_date desc);
我们经常想查看每篇文章的最新分类。为此,我们使用:
SELECT DISTINCT ON (article_id) article_id, id, article_published_date
FROM prediction
ORDER BY article_id, prediction_date desc
其中 returns 类似于:
| id | article_id | predicted_as | prediction_date | article_published_date
| 120950 | 1 | negative | 2018-06-29 | 2018-03-25
| 120951 | 2 | negative | 2018-06-29 | 2018-03-19
使用 (article_id, prediciton_date desc)
上的索引,此查询 运行 非常快(~15 毫秒)。这是解释计划:
Unique (cost=0.56..775374.53 rows=1058394 width=20)
-> Index Scan using prediction_article_id_prediction_date_id_idx on prediction (cost=0.56..756071.98 rows=7721023 width=20)
到目前为止一切顺利。
当我想按 article_published_field 对结果进行排序时出现问题。例如:
explain (analyze, buffers)
select *
from (
select distinct on (article_id) article_id, id, article_published_date
from prediction
order by article_id, prediction_date desc
) most_recent_predictions
order by article_published_date desc
limit 3;
这可行,但查询需要大约 3-4 秒才能到达 运行,这使得直接使用它来响应 Web 请求太慢了。
这里是解释计划:
Limit (cost=558262.52..558262.53 rows=3 width=12) (actual time=4748.977..4748.979 rows=3 loops=1)
Buffers: shared hit=7621849 read=9051
-> Sort (cost=558262.52..560851.50 rows=1035593 width=12) (actual time=4748.975..4748.976 rows=3 loops=1)
Sort Key: most_recent_predictions.article_published_date DESC
Sort Method: top-N heapsort Memory: 25kB
Buffers: shared hit=7621849 read=9051
-> Subquery Scan on most_recent_predictions (cost=0.43..544877.67 rows=1035593 width=12) (actual time=0.092..4508.464 rows=1670807 loops=1)
Buffers: shared hit=7621849 read=9051
-> Result (cost=0.43..534521.74 rows=1035593 width=16) (actual time=0.092..4312.916 rows=1670807 loops=1)
Buffers: shared hit=7621849 read=9051
-> Unique (cost=0.43..534521.74 rows=1035593 width=16) (actual time=0.090..4056.644 rows=1670807 loops=1)
Buffers: shared hit=7621849 read=9051
-> Index Scan using prediction_article_id_prediction_date_idx on prediction (cost=0.43..515295.09 rows=7690662 width=16) (actual time=0.089..3248.250 rows=7690662 loops=1)
Buffers: shared hit=7621849 read=9051
Planning Time: 0.130 ms
Execution Time: 4749.007 ms
有什么方法可以使这个查询运行更快,还是我必须求助于刷新物化视图或设置触发系统来快速获取此数据?
供参考:
prediction
table 有 770 万行prediction
table 中有 170 万个不同的 (article_id, prediciton_date desc)
上有一个索引,article_published_date desc
上也有一个索引
VACUUM ANALYSE
已经 运行
article_id
您 可以尝试 的一件事是使用 window 函数 ROW_NUMBER() OVER(...)
而不是 DISTINCT ON()
(这意味着对 ORDER BY
子句)。此方法在功能上等同于您的第二个查询,并且可能能够利用现有索引:
SELECT *
FROM (
SELECT
article_id,
id,
article_published_date,
ROW_NUMBER() OVER(PARTITION BY article_id ORDER BY prediction_date DESC) rn
FROM prediction
) x WHERE rn = 1
ORDER BY article_published_date DESC
LIMIT 3;
不知你能否完成这项工作:
select article_id, id, article_published_date
from prediction p
where p.prediction_date = (select max(p2.prediction_date)
from prediction p2
where p2.article_id = p.article_id
)
order by article_published_date desc;
然后使用这两个索引:
(article_published_date desc, prediction_date, article_id, id)
(article_id, prediction_date desc)
.
虽然您只需要少量的结果行(在您的示例中为 LIMIT 3
),并且如果 article_published_date
和 prediction_date
之间存在任何正相关,则此查询应该彻底更快,因为它只需要从添加索引的顶部扫描几个元组(并重新检查第二个索引):
有这两个索引:
CREATE INDEX ON prediction (article_published_date DESC, prediction_date DESC, article_id DESC);
CREATE INDEX ON prediction (article_id, prediction_date DESC);
递归查询:
WITH RECURSIVE cte AS (
(
SELECT p.article_published_date, p.article_id, p.prediction_date, ARRAY[p.article_id] AS a_ids
FROM prediction p
WHERE NOT EXISTS ( -- no later row for same article
SELECT FROM prediction
WHERE article_id = p.article_id
AND prediction_date > p.prediction_date
)
ORDER BY p.article_published_date DESC, p.prediction_date DESC, p.article_id DESC
LIMIT 1
)
UNION ALL
SELECT p.article_published_date, p.article_id, p.prediction_date, a_ids || p.article_id
FROM cte c, LATERAL (
SELECT p.article_published_date, p.article_id, p.prediction_date
FROM prediction p
WHERE (p.article_published_date, p.prediction_date, p.article_id)
< (c.article_published_date, c.prediction_date, c.article_id)
AND p.article_id <> ALL(a_ids) -- different article
AND NOT EXISTS ( -- no later row for same article
SELECT FROM prediction
WHERE article_id = p.article_id
AND prediction_date > p.prediction_date
)
ORDER BY p.article_published_date DESC, p.prediction_date DESC, p.article_id DESC
LIMIT 1
) p
)
SELECT article_published_date, article_id, prediction_date
FROM cte
LIMIT 3;
这里有一个 plpgsql 解决方案 做同样的事情,可能稍微快一点:
CREATE OR REPLACE FUNCTION f_top_n_predictions(_n int = 3)
RETURNS TABLE (_article_published_date date, _article_id int, _prediction_date date) AS
$func$
DECLARE
a_ids int[];
BEGIN
FOR _article_published_date, _article_id, _prediction_date IN
SELECT article_published_date, article_id, prediction_date
FROM prediction
ORDER BY article_published_date DESC, prediction_date DESC, article_id DESC
LOOP
IF _article_id = ANY(a_ids)
OR EXISTS (SELECT FROM prediction p
WHERE p.article_id = _article_id
AND p.prediction_date > _prediction_date) THEN
-- do nothing
ELSE
RETURN NEXT;
a_ids := a_ids || _article_id;
EXIT WHEN cardinality(a_ids) >= _n;
END IF;
END LOOP;
END
$func$ LANGUAGE plpgsql;
致电:
SELECT * FROM f_top_n_predictions();
如果对您有用,我会添加解释,因为解释比查询本身更有效。
除此之外,每篇文章有多个预测,并且有额外的 table article
,此查询成为竞争者:
SELECT p.*
FROM article a
CROSS JOIN LATERAL (
SELECT p.article_published_date, p.article_id, p.prediction_date
FROM prediction p
WHERE p.article_id = a.id
ORDER BY p.prediction_date DESC
LIMIT 1
) p
ORDER BY p.article_published_date DESC;
但是如果上面的查询完成了工作,你就不需要这个了。变得有趣的更大或没有 LIMIT
.
基础知识:
- Optimize GROUP BY query to retrieve latest record per user
- Can spatial index help a “range - order by - limit” query
db<>fiddlehere,演示全部.