PostgreSQL where all in array

后端 未结 9 1565
情书的邮戳
情书的邮戳 2020-11-30 07:23

What is the easiest and fastest way to achieve a clause where all elements in an array must be matched - not only one when using IN? After all it should behave

相关标签:
9条回答
  • 2020-11-30 08:02

    I'm collapsing those users into an array. I'm also using a CTE (the thing in the WITH clause) to make this more readable.

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

    EDIT (Some resources)

    • array functions: http://www.postgresql.org/docs/9.1/static/functions-array.html
    • CTEs: http://www.postgresql.org/docs/9.1/static/queries-with.html
    0 讨论(0)
  • 2020-11-30 08:04

    Based on Alex Blakemore answer

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

    I have found an alternative query with the same goal, finding the conversation_id of a conversation that contains user_1 and user_2 (ignoring aditional users)

    select *
    from conversations_users cu1
    where 2 = (
        select count(distinct user_id)
        from conversations_users cu2
        where user_id in (1, 2) and cu1.conversation_id = cu2.conversation_id
    )
    

    It is slower according the analysis that postgres perform via explain query statement, and i guess that is true because there is more conditions beign evaluated, at least, for each row of the conversations_users the subquery will get executed as it is correlated subquery. The possitive point with this query is that you aren't grouping, thus you can select aditional fields of the conversations_users table. In some situations (like mine) it could be handy.

    0 讨论(0)
  • 2020-11-30 08:09

    create a mapping table with all possible values and use this

    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)
    
    0 讨论(0)
  • 2020-11-30 08:13
    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)        
    )
    

    this can easily be made into a rails scope.

    0 讨论(0)
  • 2020-11-30 08:15

    Based on @Alex Blakemore's answer, the equivalent Rails 4 scope on you Conversation class would be:

    # 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 }
    

    Note you can optimize it instead of doing a Rails - (minus) you could do a .where("NOT IN") but that would be really complex to read.

    0 讨论(0)
  • 2020-11-30 08:17

    This preserves ActiveRecord objects.

    In the below example, I want to know the time sheets which are associated with all codes in the array.

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

    You should have the full ActiveRecord objects to work with. If you want it to be a true scope, you can just use your above example and pass in the results with .pluck(:id).

    0 讨论(0)
提交回复
热议问题