[英]Slow query in postgres using count distinct
我的目標是創建一個查詢,該查詢將返回在365天的窗口中購買的唯一客戶的數量。 我在postgres中創建了以下查詢,結果查詢非常慢。 我的表是812,024行,僅包含訂單日期和客戶ID。 當我刪除非重復語句時,我可以得到查詢以在大約60秒鍾內返回結果,但尚未完成。 我在(order_date,id)上創建了一個索引。 我是SQL的新手,這確實是我第一次做任何事情,並且整日試圖找到解決此問題的方法之后,即使我找不到任何可以工作的東西,已經看到了很多與眾不同的緩慢表現。
SELECT
(d1.Ordered) AS Ordered,
COUNT(distinct d2.ID) Users
FROM
(
SELECT order_date AS Ordered
FROM orders
GROUP BY order_date
) d1
INNER JOIN
(
SELECT order_date AS Ordered, id
FROM orders
) d2
ON d2.Ordered BETWEEN d1.Ordered - 364 AND d1.Ordered
GROUP BY d1.Ordered
ORDER BY d1.Ordered
"Sort (cost=3541596.30..3541596.80 rows=200 width=29)"
" Sort Key: orders_1.order_date"
" -> HashAggregate (cost=3541586.66..3541588.66 rows=200 width=29)"
" -> Nested Loop (cost=16121.73..3040838.52 rows=100149627 width=29)"
" -> HashAggregate (cost=16121.30..16132.40 rows=1110 width=4)"
" -> Seq Scan on orders orders_1 (cost=0.00..14091.24 rows=812024 width=4)"
" -> Index Only Scan using x on orders (cost=0.43..1822.70 rows=90225 width=29)"
" Index Cond: ((order_date >= (orders_1.order_date - 364)) AND (order_date <= orders_1.order_date))"
不需要自我連接,使用generate_series
select
g.order_date as "Ordered",
count(distinct o.id) as "Users"
from
generate_series(
(select min(order_date) from orders),
(select max(order_date) from orders),
'1 day'
) g (order_date)
left join
orders o on o.order_date between g.order_date - 364 and g.order_date
group by 1
order by 1
您尚未顯示架構,因此請在此處進行一些猜測。 適當更改列名稱等。
SELECT
count(DISTINCT users.user_id)
FROM users
INNER JOIN order_date ON (users.user_id = orders.user_id)
WHERE orders.order_date > current_date - INTERVAL '1' YEAR;
要么
SELECT
count(users.user_id)
FROM users
INNER JOIN order_date ON (users.user_id = orders.user_id)
WHERE orders.order_date > current_date - INTERVAL '1' YEAR
GROUP BY users.user_id;
假設實際date
類型。
SELECT d.day, count(distinct o.id) AS users_past_year
FROM (
SELECT generate_series(min(order_date), max(order_date), '1 day')::date AS day
FROM orders -- single query
) d
LEFT JOIN ( -- fold duplicates on same day right away
SELECT id, order_date
FROM orders
GROUP BY 1,2
) o ON o.order_date > d.day - interval '1 year' -- exclude
AND o.order_date <= d.day -- include
GROUP BY 1
ORDER BY 1;
只有在同一天,首先折疊同一位用戶的多次購買才有意義。 否則,省略該步驟並直接左聯接到表orders
會更快。
orders.id
是用戶的ID很奇怪。 應命名為user_id
。
如果您對SELECT
列表中的generate_series()
感到不滿意(效果很好),則可以在Postgres 9.3+中將其替換為LATERAL JOIN
。
FROM (SELECT min(order_date) AS a
, max(order_date) AS z FROM orders) x
, generate_series(x.a, x.z, '1 day') AS d(day)
LEFT JOIN ...
請注意,在這種情況下, day
是類型的timestamp
。 工作原理相同。 您可能想要投射。
我了解這是單個用戶的只讀表。 這簡化了事情。
您似乎已經有了一個索引:
CREATE INDEX orders_mult_idx ON orders (order_date, id);
那很好。
可以嘗試的一些事情:
當然,通常的性能建議適用於:
https://wiki.postgresql.org/wiki/Slow_Query_Questions
https://wiki.postgresql.org/wiki/Performance_Optimization
使用此索引對表進行集群一次:
CLUSTER orders USING orders_mult_idx;
這應該有所幫助。 它還可以在表上有效地運行VACUUM FULL
,從而刪除所有無效行並壓縮表(如果適用)。
ALTER TABLE orders ALTER COLUMN number SET STATISTICS 1000;
ANALYZE orders;
此處說明:
確保分配了足夠的資源。 特別是對於shared_buffers
和work_mem
。 您可以在會話中臨時執行此操作。
嘗試禁用嵌套循環( enable_nestloop
)(僅在您的會話中)。 也許哈希聯接更快。 (不過,我會感到驚訝。)
SET enable_nestedloop = off;
-- test ...
RESET enable_nestedloop;
由於這本質上似乎是“臨時表”,因此您可以嘗試使其成為僅保存在RAM中的實際臨時表。 您需要足夠的RAM來分配足夠的temp_buffers
。 詳細說明:
確保手動運行ANALYZE
。 臨時表不包含在自動真空中。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.