Đây là trường hợp của quan hệ-phân chia - với yêu cầu đặc biệt bổ sung rằng cùng một cuộc trò chuyện sẽ không có bổ sung người dùng.
Giả định là PK của bảng "conversationUsers"
thực thi tính duy nhất của các kết hợp, NOT NULL
và cũng cung cấp chỉ số cần thiết cho hiệu suất một cách ngầm định. Các cột của PK nhiều cột trong this gọi món! Nếu không, bạn phải làm thêm.
Về thứ tự của các cột chỉ mục:
Đối với truy vấn cơ bản, có "brute force" phương pháp đếm số lượng người dùng phù hợp cho tất cả cuộc trò chuyện của tất cả người dùng nhất định và sau đó lọc những cuộc trò chuyện phù hợp với tất cả người dùng nhất định. OK đối với các bảng nhỏ và / hoặc chỉ các mảng đầu vào ngắn và / hoặc một vài cuộc hội thoại cho mỗi người dùng, nhưng không chia tỷ lệ tốt :
SELECT "conversationId"
FROM "conversationUsers" c
WHERE "userId" = ANY ('{1,4,6}'::int[])
GROUP BY 1
HAVING count(*) = array_length('{1,4,6}'::int[], 1)
AND NOT EXISTS (
SELECT FROM "conversationUsers"
WHERE "conversationId" = c."conversationId"
AND "userId" <> ALL('{1,4,6}'::int[])
);
Loại bỏ các cuộc trò chuyện với người dùng khác bằng NOT EXISTS
chống bán tham gia. Thêm:
Các kỹ thuật thay thế:
Có nhiều phân chia quan hệ khác, (nhiều) nhanh hơn kỹ thuật truy vấn. Nhưng những cái nhanh nhất không phù hợp với động số lượng ID người dùng.
Đối với một truy vấn nhanh điều đó cũng có thể xử lý số lượng ID người dùng động, hãy xem xét CTE đệ quy :
WITH RECURSIVE rcte AS (
SELECT "conversationId", 1 AS idx
FROM "conversationUsers"
WHERE "userId" = ('{1,4,6}'::int[])[1]
UNION ALL
SELECT c."conversationId", r.idx + 1
FROM rcte r
JOIN "conversationUsers" c USING ("conversationId")
WHERE c."userId" = ('{1,4,6}'::int[])[idx + 1]
)
SELECT "conversationId"
FROM rcte r
WHERE idx = array_length(('{1,4,6}'::int[]), 1)
AND NOT EXISTS (
SELECT FROM "conversationUsers"
WHERE "conversationId" = r."conversationId"
AND "userId" <> ALL('{1,4,6}'::int[])
);
Để dễ sử dụng, hãy đưa nó vào một hàm hoặc câu lệnh đã soạn sẵn . Như:
PREPARE conversations(int[]) AS
WITH RECURSIVE rcte AS (
SELECT "conversationId", 1 AS idx
FROM "conversationUsers"
WHERE "userId" = $1[1]
UNION ALL
SELECT c."conversationId", r.idx + 1
FROM rcte r
JOIN "conversationUsers" c USING ("conversationId")
WHERE c."userId" = $1[idx + 1]
)
SELECT "conversationId"
FROM rcte r
WHERE idx = array_length($1, 1)
AND NOT EXISTS (
SELECT FROM "conversationUsers"
WHERE "conversationId" = r."conversationId"
AND "userId" <> ALL($1);
Gọi:
EXECUTE conversations('{1,4,6}');
db <> fiddle tại đây (cũng thể hiện một chức năng )
Vẫn còn chỗ để cải thiện:để đạt được hàng đầu hiệu suất, bạn phải đặt người dùng có ít cuộc trò chuyện nhất trước tiên trong mảng đầu vào của bạn để loại bỏ càng nhiều hàng càng sớm càng tốt. Để có được hiệu suất cao nhất, bạn có thể tạo động một truy vấn không động, không phải đệ quy (sử dụng một trong các truy vấn nhanh kỹ thuật từ liên kết đầu tiên) và thực hiện lần lượt. Bạn thậm chí có thể gói nó trong một hàm plpgsql duy nhất với SQL động ...
Giải thích thêm:
Thay thế:MV cho bảng viết thưa thớt
Nếu bảng "conversationUsers"
chủ yếu ở chế độ chỉ đọc (các cuộc trò chuyện cũ không có khả năng thay đổi) bạn có thể sử dụng MATERIALIZED VIEW
với những người dùng được tổng hợp trước trong các mảng được sắp xếp và tạo chỉ mục btree thuần túy trên cột mảng đó.
CREATE MATERIALIZED VIEW mv_conversation_users AS
SELECT "conversationId", array_agg("userId") AS users -- sorted array
FROM (
SELECT "conversationId", "userId"
FROM "conversationUsers"
ORDER BY 1, 2
) sub
GROUP BY 1
ORDER BY 1;
CREATE INDEX ON mv_conversation_users (users) INCLUDE ("conversationId");
Chỉ mục bao trùm được chứng minh yêu cầu Postgres 11. Xem:
Giới thiệu về việc sắp xếp các hàng trong một truy vấn con:
Trong các phiên bản cũ hơn, hãy sử dụng chỉ mục đa cột thuần túy trên (users, "conversationId")
. Với các mảng rất dài, chỉ mục băm có thể có ý nghĩa trong Postgres 10 trở lên.
Sau đó, truy vấn nhanh hơn nhiều sẽ chỉ đơn giản là:
SELECT "conversationId"
FROM mv_conversation_users c
WHERE users = '{1,4,6}'::int[]; -- sorted array!
db <> fiddle tại đây
Bạn phải cân nhắc giữa chi phí gia tăng cho việc lưu trữ, ghi và bảo trì so với lợi ích của hiệu suất đọc.
Ngoài ra:hãy xem xét các mã định danh hợp pháp không có dấu ngoặc kép. conversation_id
thay vì "conversationId"
vv: