PostgreSQL
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> PostgreSQL

Truy vấn SQL để tìm một hàng có số lượng liên kết cụ thể

Đây là trường hợp của - 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 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:



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Flask-Migrate không phát hiện bảng

  2. các điểm ngắt trong nhật thực bằng cách sử dụng postgresql

  3. Tìm các bài báo trong đó mảng

  4. Không thể thay đổi thuộc tính chỉ đọc giao dịch khi đang giao dịch

  5. Có thể tạo cơ sở dữ liệu postgres trong dòng lệnh nhưng không phải trong tập lệnh bash