Ghi chú sơ bộ
Bạn đang sử dụng các loại dữ liệu lẻ. character(24)
? char(n)
là một loại lỗi thời và hầu như luôn luôn là lựa chọn sai. Bạn có các chỉ mục trên person_id
và tham gia vào nó nhiều lần. integer
sẽ hiệu quả hơn nhiều vì nhiều lý do. (Hoặc bigint
, nếu bạn dự định ghi hơn 2 tỷ hàng trong suốt vòng đời của bảng.) Liên quan:
LIKE
là vô nghĩa nếu không có ký tự đại diện. Sử dụng =
thay thế. Nhanh hơn.
x.location_host LIKE '2015.testonline.ca'
x.location_host = '2015.testonline.ca'
Sử dụng count(e1.*)
hoặc count(*)
thay vì thêm một cột giả với giá trị 1
cho mỗi truy vấn con. (Ngoại trừ lần cuối ( e3
), nơi bạn không cần bất kỳ dữ liệu thực tế nào.)
Đôi khi bạn không nhất quán trong việc chuyển chuỗi theo nghĩa đen timestamp
và đôi khi không ( timestamp '2016-04-30 23:59:59.999'
). Hoặc nó có ý nghĩa, sau đó làm điều đó mọi lúc, hoặc không, sau đó không làm điều đó.
Nó không. Khi được so sánh với một timestamp
cột, một chuỗi ký tự được ép buộc bằng timestamp
mọi cách. Vì vậy, bạn không cần một diễn viên rõ ràng.
Kiểu dữ liệu Postgres timestamp
có tới 6 chữ số phân số. BETWEEN
Biểu hiện của bạn để lại trường hợp góc. Tôi thay thế chúng bằng các biểu thức ít bị lỗi hơn.
Chỉ mục
Quan trọng: để tối ưu hóa hiệu suất tạo các chỉ mục nhiều màu .
Đối với truy vấn con đầu tiên hp
:
CREATE INDEX event_pg_location_host_timestamp__idx
ON event_pg (location_host, timestamp_);
Hoặc, nếu bạn có thể quét chỉ mục từ nó, hãy thêm person_id
vào chỉ mục:
CREATE INDEX event_pg_location_host_timestamp__person_id_idx
ON event_pg (location_host, timestamp_, person_id);
Đối với các khoảng thời gian rất lớn kéo dài hầu hết hoặc tất cả các bảng, chỉ mục này nên được ưu tiên hơn - nó cũng hỗ trợ hlp
truy vấn con, vì vậy hãy tạo một trong hai cách:
CREATE INDEX event_pg_location_host_person_id_timestamp__idx
ON event_pg (location_host, person_id, timestamp_);
Dành cho tnk
:
CREATE INDEX event_pg_location_fragment_timestamp__idx
ON event_pg (location_fragment, person_id, timestamp_);
Nếu các vị từ của bạn trên location_host
và location_fragment
là hằng số, chúng ta có thể sử dụng chỉ số phần rẻ hơn nhiều thay vì , đặc biệt là kể từ khi bạn location_*
cột dường như lớn:
CREATE INDEX event_pg_hp_person_id_ts_idx ON event_pg (person_id, timestamp_)
WHERE location_host = '2015.testonline.ca';
CREATE INDEX event_pg_hlp_person_id_ts_idx ON event_pg (person_id, timestamp_)
WHERE location_host = 'helpcentre.testonline.ca';
CREATE INDEX event_pg_tnk_person_id_ts_idx ON event_pg (person_id, timestamp_)
WHERE location_fragment = '/file/thank-you';
Xem xét:
Một lần nữa, tất cả các chỉ số này nhỏ hơn đáng kể và nhanh hơn với integer
hoặc bigint
cho person_id
.
Nói chung, bạn cần vào ANALYZE
bảng sau khi tạo một chỉ mục mới - hoặc đợi cho đến khi autovacuum khởi động để làm điều đó cho bạn.
Để có được các bản quét chỉ mục , bảng của bạn phải VACUUM
đủ. Kiểm tra ngay sau khi VACUUM
làm bằng chứng của khái niệm. Đọc trang Wiki Postgres được liên kết để biết chi tiết nếu bạn không quen với việc quét chỉ mục .
Truy vấn cơ bản
Thực hiện những gì tôi đã thảo luận. Truy vấn cho phạm vi nhỏ ( vài hàng mỗi person_id
):
SELECT count(*)::int AS view_homepage
, count(hlp.hlp_ts)::int AS use_help
, count(tnk.yes)::int AS thank_you
FROM (
SELECT DISTINCT ON (person_id)
person_id, timestamp_ AS hp_ts
FROM event_pg
WHERE timestamp_ >= '2016-04-23'
AND timestamp_ < '2016-05-01'
AND location_host = '2015.testonline.ca'
ORDER BY person_id, timestamp_
) hp
LEFT JOIN LATERAL (
SELECT timestamp_ AS hlp_ts
FROM event_pg y
WHERE y.person_id = hp.person_id
AND timestamp_ >= hp.hp_ts
AND timestamp_ < '2016-05-01'
AND location_host = 'helpcentre.testonline.ca'
ORDER BY timestamp_
LIMIT 1
) hlp ON true
LEFT JOIN LATERAL (
SELECT true AS yes -- we only need existence
FROM event_pg z
WHERE z.person_id = hp.person_id -- we can use hp here
AND location_fragment = '/file/thank-you'
AND timestamp_ >= hlp.hlp_ts -- this introduces dependency on hlp anyways.
AND timestamp_ < '2016-05-01'
ORDER BY timestamp_
LIMIT 1
) tnk ON true;
DISTINCT ON
thường rẻ hơn cho vài hàng mỗi person_id
. Giải thích chi tiết:
Nếu bạn có nhiều hàng trên mỗiperson_id
(nhiều khả năng cho các khoảng thời gian lớn hơn), CTE đệ quy được thảo luận trong câu trả lời này trong chương 1a có thể nhanh hơn (nhiều):
Xem nó tích hợp dưới đây.
Tối ưu hóa & tự động hóa truy vấn tốt nhất
Đó là câu hỏi hóc búa cũ: một kỹ thuật truy vấn là tốt nhất cho một tập nhỏ hơn, một cho một tập lớn hơn. Trong trường hợp cụ thể của bạn, chúng tôi có một chỉ báo rất tốt từ đầu - độ dài của khoảng thời gian nhất định - mà chúng tôi có thể sử dụng để quyết định.
Chúng tôi gói tất cả trong một hàm PL / pgSQL. Việc triển khai của tôi chuyển từ DISTINCT ON
sang rCTE khi khoảng thời gian nhất định dài hơn ngưỡng đã đặt:
CREATE OR REPLACE FUNCTION f_my_counts(_ts_low_inc timestamp, _ts_hi_excl timestamp)
RETURNS TABLE (view_homepage int, use_help int, thank_you int) AS
$func$
BEGIN
CASE
WHEN _ts_hi_excl <= _ts_low_inc THEN
RAISE EXCEPTION 'Timestamp _ts_hi_excl (1st param) must be later than _ts_low_inc!';
WHEN _ts_hi_excl - _ts_low_inc < interval '10 days' THEN -- example value !!!
-- DISTINCT ON for few rows per person_id
RETURN QUERY
WITH hp AS (
SELECT DISTINCT ON (person_id)
person_id, timestamp_ AS hp_ts
FROM event_pg
WHERE timestamp_ >= _ts_low_inc
AND timestamp_ < _ts_hi_excl
AND location_host = '2015.testonline.ca'
ORDER BY person_id, timestamp_
)
, hlp AS (
SELECT hp.person_id, hlp.hlp_ts
FROM hp
CROSS JOIN LATERAL (
SELECT timestamp_ AS hlp_ts
FROM event_pg
WHERE person_id = hp.person_id
AND timestamp_ >= hp.hp_ts
AND timestamp_ < _ts_hi_excl
AND location_host = 'helpcentre.testonline.ca' -- match partial idx
ORDER BY timestamp_
LIMIT 1
) hlp
)
SELECT (SELECT count(*)::int FROM hp) -- AS view_homepage
, (SELECT count(*)::int FROM hlp) -- AS use_help
, (SELECT count(*)::int -- AS thank_you
FROM hlp
CROSS JOIN LATERAL (
SELECT 1 -- we only care for existence
FROM event_pg
WHERE person_id = hlp.person_id
AND location_fragment = '/file/thank-you'
AND timestamp_ >= hlp.hlp_ts
AND timestamp_ < _ts_hi_excl
ORDER BY timestamp_
LIMIT 1
) tnk
);
ELSE
-- rCTE for many rows per person_id
RETURN QUERY
WITH RECURSIVE hp AS (
( -- parentheses required
SELECT person_id, timestamp_ AS hp_ts
FROM event_pg
WHERE timestamp_ >= _ts_low_inc
AND timestamp_ < _ts_hi_excl
AND location_host = '2015.testonline.ca' -- match partial idx
ORDER BY person_id, timestamp_
LIMIT 1
)
UNION ALL
SELECT x.*
FROM hp, LATERAL (
SELECT person_id, timestamp_ AS hp_ts
FROM event_pg
WHERE person_id > hp.person_id -- lateral reference
AND timestamp_ >= _ts_low_inc -- repeat conditions
AND timestamp_ < _ts_hi_excl
AND location_host = '2015.testonline.ca' -- match partial idx
ORDER BY person_id, timestamp_
LIMIT 1
) x
)
, hlp AS (
SELECT hp.person_id, hlp.hlp_ts
FROM hp
CROSS JOIN LATERAL (
SELECT timestamp_ AS hlp_ts
FROM event_pg y
WHERE y.person_id = hp.person_id
AND location_host = 'helpcentre.testonline.ca' -- match partial idx
AND timestamp_ >= hp.hp_ts
AND timestamp_ < _ts_hi_excl
ORDER BY timestamp_
LIMIT 1
) hlp
)
SELECT (SELECT count(*)::int FROM hp) -- AS view_homepage
, (SELECT count(*)::int FROM hlp) -- AS use_help
, (SELECT count(*)::int -- AS thank_you
FROM hlp
CROSS JOIN LATERAL (
SELECT 1 -- we only care for existence
FROM event_pg
WHERE person_id = hlp.person_id
AND location_fragment = '/file/thank-you'
AND timestamp_ >= hlp.hlp_ts
AND timestamp_ < _ts_hi_excl
ORDER BY timestamp_
LIMIT 1
) tnk
);
END CASE;
END
$func$ LANGUAGE plpgsql STABLE STRICT;
Gọi:
SELECT * FROM f_my_counts('2016-01-23', '2016-05-01');
Định nghĩa rCTE hoạt động với CTE theo định nghĩa. Tôi cũng đã trượt các CTE cho DISTINCT ON
truy vấn (như tôi đã thảo luận với @Lennart trong các nhận xét ), cho phép chúng tôi sử dụng CROSS JOIN
thay vì LEFT JOIN
giảm tập hợp với mỗi bước, vì chúng tôi có thể đếm riêng từng CTE. Điều này có tác dụng làm việc theo hướng ngược lại:
- Trên một đã có chúng tôi giảm số lượng hàng sẽ làm cho tham gia thứ ba rẻ hơn.
- Mặt khác, chúng tôi giới thiệu chi phí hoạt động cho các CTE và cần thêm RAM đáng kể, điều này có thể đặc biệt quan trọng đối với các truy vấn lớn như của bạn.
Bạn sẽ phải kiểm tra cái nào vượt trội hơn cái kia.