所有在数组中的PostgreSQL

实现子句中最简单,最快速的方法是什么,必须匹配数组中的所有元素 – 使用IN时不仅仅是一个? 毕竟它应该像mongodb的$ all 。

考虑到conversation_users是conversation_id和user_id之间的连接表的群组对话我有类似这样的想法:

 WHERE (conversations_users.user_id ALL IN (1,2)) 

更新 16.07.12

添加有关架构和案例的更多信息:

  1. join-table非常简单:

      Table "public.conversations_users" Column | Type | Modifiers | Storage | Description -----------------+---------+-----------+---------+------------- conversation_id | integer | | plain | user_id | integer | | plain | 
  2. 对话有很多用户,用户属于许多对话。 为了找到对话中的所有用户,我正在使用此连接表。

  3. 最后,我试图找出一个ruby on rails scope ,根据它的参与者找到我的对话 – 例如:

     scope :between, ->(*users) { joins(:users).where('conversations_users.user_id all in (?)', users.map(&:id)) } 

更新 23.07.12

我的问题是找到一个完全匹配的人。 因此:

查询(1,2)(1,2,3)之间的对话将不匹配

假设连接表遵循良好实践并且定义了唯一的复合键,即防止重复行的约束,那么类似下面的简单查询应该这样做。

 select conversation_id from conversations_users where user_id in (1, 2) group by conversation_id having count(*) = 2 

值得注意的是,末尾的数字2是user_id列表的长度。 如果user_id列表改变长度,那显然需要改变。 如果您不能假设您的连接表不包含重复项,请将“count(*)”更改为“count(distinct user_id)”,但性能可能会有所降低。

即使对话还包括其他用户,此查询也会查找包含所有指定用户的所有会话。

如果只想要与指定用户集完全对话,则一种方法是在where子句中使用嵌套子查询,如下所示。 注意,第一行和最后一行与原始查询相同,只有中间的两行是新的。

 select conversation_id from conversations_users where user_id in (1, 2) and conversation_id not in (select conversation_id from conversation_users where user_id not in (1,2)) group by conversation_id having count(*) = 2 

同样,如果数据库支持,您可以使用set difference运算符。 以下是Oracle语法中的示例。 (对于Postgres或DB2,将关键字“减”更改为“除外”。)

 select conversation_id from conversations_users where user_id in (1, 2) group by conversation_id having count(*) = 2 minus select conversation_id from conversation_users where user_id not in (1,2) 

一个好的查询优化器应该相同地处理最后两个变体,但请检查您的特定数据库以确保。 例如,Oracle 11GR2查询计划在应用减号运算符之前对两组会话ID进行排序,但跳过最后一个查询的排序步骤。 因此,根据多个因素,例如行数,核心数,缓存数,索引数等,查询计划可能会更快。

我正在将这些用户折叠成一个数组。 我也使用CTE(WITH子句中的东西)来使它更具可读性。

 => select * from conversations_users ; conversation_id | user_id -----------------+--------- 1 | 1 1 | 2 2 | 1 2 | 3 3 | 1 3 | 2 (6 rows) => WITH users_on_conversation AS ( SELECT conversation_id, array_agg(user_id) as users FROM conversations_users WHERE user_id in (1, 2) --filter here for performance GROUP BY conversation_id ) SELECT * FROM users_on_conversation WHERE users @> array[1, 2]; conversation_id | users -----------------+------- 1 | {1,2} 3 | {1,2} (2 rows) 

编辑 (一些资源)

  • 数组函数: http : //www.postgresql.org/docs/9.1/static/functions-array.html
  • CTE: http : //www.postgresql.org/docs/9.1/static/queries-with.html

虽然@Alex’用INcount()回答可能是最简单的解决方案,但我希望这个PL / pgSQL函数更快:

 CREATE OR REPLACE FUNCTION f_conversations_among_users(_user_arr int[]) RETURNS SETOF conversations AS $BODY$ DECLARE _sql text := ' SELECT c.* FROM conversations c'; i int; BEGIN FOREACH i IN ARRAY _user_arr LOOP _sql := _sql || ' JOIN conversations_users x' || i || ' USING (conversation_id)'; END LOOP; _sql := _sql || ' WHERE TRUE'; FOREACH i IN ARRAY _user_arr LOOP _sql := _sql || ' AND x' || i || '.user_id = ' || i; END LOOP; /* uncomment for conversations with exact list of users and no more _sql := _sql || ' AND NOT EXISTS ( SELECT 1 FROM conversations_users u WHERE u.conversation_id = c.conversation_id AND u.user_id <> ALL (_user_arr) ) */ -- RAISE NOTICE '%', _sql; RETURN QUERY EXECUTE _sql; END; $BODY$ LANGUAGE plpgsql VOLATILE; 

呼叫:

 SELECT * FROM f_conversations_among_users('{1,2}') 

函数动态构建执行表单的查询:

 SELECT c.* FROM conversations c JOIN conversations_users x1 USING (conversation_id) JOIN conversations_users x2 USING (conversation_id) ... WHERE TRUE AND x1.user_id = 1 AND x2.user_id = 2 ... 

这种forms在对关系划分的查询的广泛测试中表现最佳。

您也可以在应用程序中构建查询,但我假设您要使用一个数组参数。 而且,无论如何,这可能是最快的。

任何一个查询都需要像下面这样的索引才能快速:

 CREATE INDEX conversations_users_user_id_idx ON conversations_users (user_id); 

(user_id, conversation_id)上的多列主要(或唯一)键也是如此,但是一个on (conversation_id, user_id) (就像你很可能拥有!)会更 。 您可以在上面的链接中找到简短的理由,或者根据dba.SE上的相关问题进行全面评估

我还假设你在conversations.conversation_id上有一个主键。

您可以使用EXPLAIN ANALYZE在@Alex’查询和此function上运行性能测试并报告您的发现吗?

请注意,这两种解决方案都可以找到至少包含arrays中用户的对话 – 包括与其他用户的对话。
如果要排除这些,请在我的函数中取消注释additional子句(或将其添加到任何其他查询中)。

如果您需要有关该function的更多说明,请告诉我。

这会保留ActiveRecord对象。

在下面的示例中,我想知道与arrays中所有代码关联的时间表。

 codes = [8,9] Timesheet.joins(:codes).select('count(*) as count, timesheets.*'). where('codes.id': codes). group('timesheets.id'). having('count(*) = ?', codes.length) 

您应该使用完整的ActiveRecord对象。 如果您希望它是一个真正的范围,您可以使用上面的示例并使用.pluck(:id)传递结果。

创建一个包含所有可能值的映射表并使用它

 select t1.col from conversations_users as t1 inner join mapping_table as map on t1.user_id=map.user_id group by t1.col having count(distinct conversations_users.user_id)= (select count(distinct user_id) from mapping) 
 select id from conversations where not exists( select * from conversations_users cu where cu.conversation_id=conversations.id and cu.user_id not in(1,2,3) ) 

这很容易变成轨道范围。

我猜你真的不想开始搞乱临时表。

您的问题不清楚是否要与完全相同的用户对话或与超集的对话。 以下是超集:

 with users as (select user_id from users where user_id in () ), conv as (select conversation_id, user_id from conversations_users where user_id in () ) select distinct conversation_id from users u left outer join conv c on u.user_id = c.user_id where c.conversation_id is not null 

为使此查询正常工作,它假定您在user和users中都有user_id索引。

对于确切的集合。 。 。

 with users as (select user_id from users where user_id in () ), conv as (select conversation_id, user_id from conversations_users where user_id in () ) select distinct conversation_id from users u full outer join conv c on u.user_id = c.user_id where c.conversation_id is not null and u.user_id is not null 

基于@Alex Blakemore的答案, Conversation类中的等效Rails 4范围将是:

 # Conversations exactly with users array scope :by_users, -> (users) { self.by_any_of_users(users) .group("conversations.id") .having("COUNT(*) = ?", users.length) - joins(:conversations_users) .where("conversations_users.user_id NOT IN (?)", users) } # generates an IN clause scope :by_any_of_users, -> (users) { joins(:conversations_users).where(conversations_users: { user_id: users }).distinct } 

请注意,您可以优化它而不是执行Rails - (减号)您可以执行.where("NOT IN")但这样做会非常复杂。