SQL查询查找具有特定数量关联的行

2024-05-19

使用 Postgres 我有一个架构conversations and conversationUsers. Each conversation有很多conversationUsers。我希望能够找到具有确切指定数量的对话conversationUsers。换句话说,提供了一个数组userIds (say, [1, 4, 6])我希望能够找到仅包含这些用户的对话,而不再包含更多用户。

到目前为止我已经尝试过这个:

SELECT c."conversationId"
FROM "conversationUsers" c
WHERE c."userId" IN (1, 4)
GROUP BY c."conversationId"
HAVING COUNT(c."userId") = 2;

不幸的是,这似乎也返回了包括这 2 位用户在内的对话。 (例如,如果对话还包括"userId" 5).


这是一个案例关系划分 /questions/tagged/relational-division- 增加了特殊要求,即同一对话不得有额外的 users.

Assuming表的PK"conversationUsers" is on ("userId", "conversationId"),它强制组合的唯一性,NOT NULL并隐含地提供了性能的基本指标。多列 PK 中的列this命令。理想情况下,您还有另一个索引("conversationId", "userId"). See:

  • 复合索引也适合第一个字段的查询吗? https://dba.stackexchange.com/a/27493/3684

对于基本查询,有“蛮力”计算匹配用户数量的方法all所有给定用户的对话,然后过滤与所有给定用户匹配的对话。对于小表和/或只有短输入数组和/或每个用户的对话很少,但是扩展性不好:

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[])
   );

消除与其他用户的对话NOT EXISTS反半连接。更多的:

  • 我如何(或可以)在多个列上选择 DISTINCT? https://stackoverflow.com/questions/54418/how-do-i-or-can-i-select-distinct-on-multiple-columns/12632129#12632129

替代技术:

  • 选择其他表中不存在的行 https://stackoverflow.com/questions/19363481/select-rows-which-are-not-present-in-other-table/19364694#19364694

还有其他各种(更快)的关系划分 /questions/tagged/relational-division查询技术。但最快的并不适合dynamic用户 ID 的数量。

  • 如何在多通关系中过滤 SQL 结果 https://stackoverflow.com/questions/7364969/how-to-filter-sql-results-in-a-has-many-through-relation/7774879#7774879

For a 快速查询也可以处理动态数量的用户 ID,请考虑递归CTE https://www.postgresql.org/docs/current/queries-with.html:

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[])
   );

为了便于使用,将其包装在函数中或准备好的声明 https://www.postgresql.org/docs/current/sql-prepare.html. Like:

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);

Call:

EXECUTE conversations('{1,4,6}');

数据库小提琴(还展示了一个function)

还有改进的余地:top为了提高性能,您必须将对话次数最少的用户放在输入数组中的第一位,以便尽早消除尽可能多的行。为了获得最佳性能,您可以动态生成非动态、非递归查询(使用其中之一)fast第一个链接中的技术)并依次执行。您甚至可以使用动态 SQL 将其包装在单个 plpgsql 函数中...

更多解释:

  • 在 WHERE 子句中多次使用同一列 https://stackoverflow.com/questions/47351766/using-same-column-multiple-times-in-where-clause/47512013#47512013

替代方案:稀疏写入表的 MV

如果表"conversationUsers"大部分是只读的(旧的对话不太可能改变)你可以使用MATERIALIZED VIEW https://www.postgresql.org/docs/current/sql-creatematerializedview.html在排序数组中使用预先聚合的用户,并在该数组列上创建一个普通的 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。请参阅:

  • https://dba.stackexchange.com/a/207938/3684 https://dba.stackexchange.com/a/207938/3684

关于对子查询中的行进行排序:

  • 如何将 ORDER BY 和 LIMIT 与聚合函数结合应用? https://dba.stackexchange.com/a/213724/3684

在旧版本中使用普通的多列索引(users, "conversationId")。对于很长的数组,哈希索引在 Postgres 10 或更高版本中可能有意义。

那么更快的查询将是:

SELECT "conversationId"
FROM   mv_conversation_users c
WHERE  users = '{1,4,6}'::int[];  -- sorted array!

数据库小提琴

您必须权衡存储、写入和维护的额外成本与读取性能的好处。

另外:考虑不带双引号的合法标识符。conversation_id代替"conversationId" etc.:

  • PostgreSQL 列名区分大小写吗? https://stackoverflow.com/questions/20878932/are-postgresql-column-names-case-sensitive/20880247#20880247
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

SQL查询查找具有特定数量关联的行 的相关文章

  • PostgreSQL 中的 Long 数据类型相当于什么?

    我想知道相当于什么LongPostgreSQL 中的数据类型 根据the docs http www postgresql org docs 7 4 interactive datatype html DATATYPE INT看起来像big
  • SQL:如何在按部分分组的查询中使用子查询?

    如何在按部分分组的查询中使用子查询 我使用 SQL Server 2008 R2 和 Delphi 2010 我收到此错误 Cannot perform an aggregate function on an expression cont
  • SQL 中基于下一条记录和上一条记录的复杂排序

    这是一个后续问题根据 SQL 中的下一条记录和上一条记录进行排序 https stackoverflow com questions 30477803 sorting based on next and previous records i
  • PostgreSQL 中“-”处或附近的语法错误

    我正在尝试运行查询来更新用户密码 alter user dell sys with password Pass 133 但因为 它给了我这样的错误 ERROR syntax error at or near LINE 1 alter use
  • 无法为数据库添加 SSL 支持

    我正在使用 Spring 3 Hibernate 和 postgres 9 2 为了启用 SSL 数据库连接 我按照以下步骤操作 创建自签名证书 参考 http www postgresql org docs 9 2 static ssl
  • 可以使用表通配符创建 sql 查询吗?

    这可能是一个简单的问题 但我无法在网上找到解决方案 任何帮助将不胜感激 我正在尝试在 PHP 中创建一个 SQL 查询 并希望以某种方式将通配符应用于 TABLE 过滤器 可能是这样的 select from table 但是 到目前为止我
  • 将 UNNEST 与 jOOQ 结合使用

    我正在使用 PostgreSQL 9 4 Spring Boot 1 3 2 和 jOOQ 3 7 我想 jOOQify 以下查询 SELECT id FROM users WHERE username IN SELECT FROM UNN
  • JDBC插入实数数组

    我试图将一个真实的数组插入到 postgresql 数组中 该表的定义是 String sqlTable CREATE TABLE IF NOT EXISTS ccmBlock sampleId INTEGER block REAL 插入内
  • 数据库字段中的逗号分隔值

    我有一个产品表 该表中的每一行对应一个产品 并由唯一的 ID 标识 现在 每个产品都可以有多个与该产品关联的 代码 例如 Id Code 0001 IN ON ME OH 0002 ON VI AC ZO 0003 QA PS OO ME
  • 提高第一个查询的性能

    如果执行以下数据库 postgres 查询 则第二次调用要快得多 我猜第一个查询很慢 因为操作系统 linux 需要从磁盘获取数据 第二个查询受益于文件系统级别和 postgres 中的缓存 有没有一种方法可以优化数据库以快速获得结果fir
  • 使用子查询 select 创建新表

    我试图从子查询选择创建一个新表 但出现以下错误 附近的语法不正确 SELECT INTO foo FROM SELECT DATEPART MONTH a InvoiceDate as CalMonth DATEPART YEAR a In
  • 如何识别拼写不同的相似单词

    我想从数据库中过滤掉重复的客户名称 一位客户可能有多个同名但拼写差异不大的系统条目 这是一个示例 名为 Brook 的客户可能有 3 个系统条目 有了这个变化 布鲁克 贝尔塔 布鲁克 贝尔塔 比鲁克 贝尔塔 假设我们将此名称放入一个数据库列
  • 获取带有计数的不同记录

    我有一张桌子personid and msg列 personid msg 1 msg1 2 msg2 2 msg3 3 msg4 1 msg2 我想得到总计msg对于每个personid 我正在尝试这个查询 select distinct
  • 如何在 DB2 中创建返回序列值的函数?

    如何在 DB2 中创建一个从序列中获取值并返回该值的函数 应该可以在 select 或 insert 语句中使用该函数 例如 select my func from xxx insert into xxx values my func 基本
  • 从 Getdate() 获取时间

    我想采取Getdate 结果 例如 2011 10 05 11 26 55 000 into 11 26 55 AM 我看过其他地方并发现 Select RIGHT CONVERT VARCHAR GETDATE 100 7 这给了我 11
  • MYSQL从每个类别中随机选择一条记录

    我有一个数据库Items表看起来像这样 id name category int 有几十万条记录 每个item可以是 7 种不同的之一categories 对应于categories table id category 我想要一个从每个类别
  • Supabase 客户端权限被拒绝,模式为 public

    每当我尝试使用 supabase supabase js 查询数据库时 都会收到错误 error hint null details null code 42501 message permission denied for schema
  • SQL 约束以防止根据列的先前值更新列

    是否可以使用检查约束 或其他一些技术 来防止在更新记录时设置与其先前值相矛盾的值 一个例子是 NULL 时间戳 表明发生了某些事情 例如 file exported 一旦文件被导出并且具有非 NULL 值 就不应再将其设置为 NULL 另一
  • 转义 to_tsquery 中的特殊字符

    如何转义传递给的字符串中的特殊字符to tsquery 例如 这种查询 select to tsquery AT T 生产 NOTICE text search query contains only stop words or doesn
  • 使用加权行概率从 PostgreSQL 表中选择随机行

    输入示例 SELECT FROM test id percent 1 50 2 35 3 15 3 rows 你会如何编写这样的查询 平均 50 的时间我可以获得 id 1 的行 35 的时间 id 2 的行 15 的时间 id 3 的行

随机推荐