SQL查询以查找具有特定关联数的行
使用Postgres,我有一个模式,其中包含SQL查询以查找具有特定关联数的行,sql,postgresql,sequelize.js,relational-division,Sql,Postgresql,Sequelize.js,Relational Division,使用Postgres,我有一个模式,其中包含对话和对话用户。每个对话都有许多对话用户。我希望能够找到具有确切指定数量的conversationUsers的对话。换句话说,如果提供了一组用户ID(比如说,[1,4,6]),我希望能够找到只包含这些用户的对话,而不是更多 到目前为止,我已经尝试过: SELECT c."conversationId" FROM "conversationUsers" c WHERE c."userId" IN (1, 4) GROUP BY c."conversati
对话
和对话用户
。每个对话
都有许多对话用户
。我希望能够找到具有确切指定数量的conversationUsers
的对话。换句话说,如果提供了一组用户ID
(比如说,[1,4,6]
),我希望能够找到只包含这些用户的对话,而不是更多
到目前为止,我已经尝试过:
SELECT c."conversationId"
FROM "conversationUsers" c
WHERE c."userId" IN (1, 4)
GROUP BY c."conversationId"
HAVING COUNT(c."userId") = 2;
不幸的是,这似乎也返回了包括这两个用户在内的对话。(例如,如果对话还包括
“userId”
5,则返回结果) 您可以像这样修改查询,它应该可以工作:
SELECT c."conversationId"
FROM "conversationUsers" c
WHERE c."conversationId" IN (
SELECT DISTINCT c1."conversationId"
FROM "conversationUsers" c1
WHERE c1."userId" IN (1, 4)
)
GROUP BY c."conversationId"
HAVING COUNT(DISTINCT c."userId") = 2;
您可以这样修改查询,它应该可以工作:
SELECT c."conversationId"
FROM "conversationUsers" c
WHERE c."conversationId" IN (
SELECT DISTINCT c1."conversationId"
FROM "conversationUsers" c1
WHERE c1."userId" IN (1, 4)
)
GROUP BY c."conversationId"
HAVING COUNT(DISTINCT c."userId") = 2;
这可能更容易理解。您需要对话ID,请按它分组。添加HAVING子句,该子句基于组内所有可能的匹配用户ID计数之和。这将起作用,但由于没有预限定符,处理时间将更长
select
cu.ConversationId
from
conversationUsers cu
group by
cu.ConversationID
having
sum( case when cu.userId IN (1, 4) then 1 else 0 end ) = count( distinct cu.UserID )
要进一步简化列表,请对至少有一个人参与的对话进行预查询。。。如果他们一开始不在,为什么还要考虑其他的对话呢
select
cu.ConversationId
from
( select cu2.ConversationID
from conversationUsers cu2
where cu2.userID = 4 ) preQual
JOIN conversationUsers cu
preQual.ConversationId = cu.ConversationId
group by
cu.ConversationID
having
sum( case when cu.userId IN (1, 4) then 1 else 0 end ) = count( distinct cu.UserID )
这可能更容易理解。您需要对话ID,请按它分组。添加HAVING子句,该子句基于组内所有可能的匹配用户ID计数之和。这将起作用,但由于没有预限定符,处理时间将更长
select
cu.ConversationId
from
conversationUsers cu
group by
cu.ConversationID
having
sum( case when cu.userId IN (1, 4) then 1 else 0 end ) = count( distinct cu.UserID )
要进一步简化列表,请对至少有一个人参与的对话进行预查询。。。如果他们一开始不在,为什么还要考虑其他的对话呢
select
cu.ConversationId
from
( select cu2.ConversationID
from conversationUsers cu2
where cu2.userID = 4 ) preQual
JOIN conversationUsers cu
preQual.ConversationId = cu.ConversationId
group by
cu.ConversationID
having
sum( case when cu.userId IN (1, 4) then 1 else 0 end ) = count( distinct cu.UserID )
这是一个例子-增加了一个特殊要求,即同一对话不应有额外的用户
假设是表“conversationUsers”
的主键,它强制组合的唯一性,不为空
,并隐式提供性能所必需的索引。按此顺序排列的多列PK!否则你必须做得更多。关于索引列的顺序:
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[])
);
为便于使用,请将其包装在函数或中。比如:
dbfiddle(同时演示函数)
还有改进的余地:要获得最佳性能,您必须将对话最少的用户放在输入数组的第一位,以便尽早消除尽可能多的行。为了获得最佳性能,您可以动态生成一个非动态、非递归的查询(使用第一个链接中的一种快速技术)并依次执行。您甚至可以使用动态SQL将其封装在单个plpgsql函数中
更多说明:
“conversationUsers”
大部分是只读的(旧的对话不太可能更改),您可以对排序数组中的预聚合用户使用a,并在该数组列上创建一个普通的btree索引
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");
已证明的覆盖指数要求Postgres 11。见:
(用户,“conversationId”)
上使用普通的多列索引。对于非常长的数组,哈希索引在Postgres 10或更高版本中可能有意义
那么更快的查询就是:
SELECT "conversationId"
FROM mv_conversation_users c
WHERE users = '{1,4,6}'::int[]; -- sorted array!
小提琴
您必须权衡存储、写入和维护的额外成本与读取性能的好处
撇开:没有双引号的法律标识符。代码>对话id而不是“对话id”
等:
“conversationUsers”
的主键,它强制组合的唯一性,不为空
,并隐式提供性能所必需的索引。按此顺序排列的多列PK!否则你必须做得更多。关于索引列的顺序:
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[])
);
为便于使用,请将其包装在函数或中。比如:
dbfiddle(同时演示函数)
还有改进的余地:要获得最佳性能,您必须将对话最少的用户放在输入数组的第一位,以便尽早消除尽可能多的行。为了获得最佳性能,您可以动态生成一个非动态、非递归的查询(使用第一个链接中的一种快速技术)并依次执行。您甚至可以使用动态SQL将其封装在单个plpgsql函数中
更多说明:
“conversationUsers”
大部分是只读的(旧的对话不太可能更改),您可以对排序数组中的预聚合用户使用a,并在该数组列上创建一个普通的btree索引
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");
已证明的覆盖指数要求Postgres 11。见: