복붙노트

[RUBY-ON-RAILS] 배열의 PostgreSQL의 경우 모든

RUBY-ON-RAILS

배열의 PostgreSQL의 경우 모든

IN을 사용하지 않는 하나의 경우 - 배열의 모든 요소가 일치해야 조항을 달성 할 수있는 가장 쉽고 빠른 방법은 무엇입니까? 결국은 MongoDB를의 $ 모든처럼 행동해야한다.

conversation_users는이 conversation_id 사이에 테이블을 조인하고 내가 마음에 이런 일이 USER_ID 그룹 대화에 대한 생각 :

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

UPDATE 16.07.12

스키마 및 사례에 대한 자세한 정보를 추가 :

UPDATE 23.07.12

내 질문은 사람의 정확한 일치를 찾는 것에 대해입니다. 따라서:

쿼리하는 경우 (1,2,3) 사이의 대화는 (1,2)와 일치하지 않습니다

해결법

  1. ==============================

    1.가정하면 테이블이 좋은 연습을 다음과 중복 행, 다음을 수행해야합니다 간단한 쿼리와 같은 무언가를 방지하기 위해, 즉 제약 조건을 정의 고유 복합 키가 가입 할 수 있습니다.

    가정하면 테이블이 좋은 연습을 다음과 중복 행, 다음을 수행해야합니다 간단한 쿼리와 같은 무언가를 방지하기 위해, 즉 제약 조건을 정의 고유 복합 키가 가입 할 수 있습니다.

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

    그것은 마지막에 숫자 2가 user_ids 목록의 길이입니다 점에 유의하는 것이 중요합니다. 그건 분명히 USER_ID 목록 길이를 변경하는 경우 변경해야합니다. 당신은, 당신의 중복을 포함하지 않는 테이블을 조인 성능에 약간의 비용으로 변화 "COUNT (*)"을 "수 (별개의 USER_ID)"을 가정 할 수 없습니다.

    이 쿼리는 대화도 추가 사용자를 포함하더라도 지정된 모든 사용자를 포함하는 모든 대화를 찾습니다.

    당신이 사용자의 정확히 지정된 세트 만 대화를 원하는 경우, 하나의 접근 방법은 아래와 같이 WHERE 절에 중첩 된 하위 쿼리를 사용하는 것입니다. 참고, 첫 번째와 마지막 줄은 가운데 두 줄이 새로운, 원래 쿼리와 동일합니다.

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

    데이터베이스가 지원하는 경우 동등하게, 당신은 세트 차이 연산자를 사용할 수 있습니다. 여기에 오라클 구문의 예입니다. (포스트 그레스 또는 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 conversations_users where user_id not in (1,2)
    

    좋은 쿼리 최적화 동일하게 마지막 두 변형을 치료하지만, 확실하게 특정 데이터베이스를 확인해야합니다. 예를 들어, 오라클 11GR2 쿼리 계획은 마이너스 연산자를 적용하기 전에 대화 ID의 두 세트를 정렬하지만, 마지막 쿼리의 정렬 단계를 건너 뜁니다. 두 쿼리 계획은 행, 코어, 캐시, 인덱스 등의 수와 같은 여러 요인에 따라 빠르게 할 수 있도록

  2. ==============================

    2.나는 배열로 해당 사용자를 축소하고있다. 나는이 더 읽기 쉽게하기 위해 CTE합니다 (WITH 절에서 일을) 사용하고 있습니다.

    나는 배열로 해당 사용자를 축소하고있다. 나는이 더 읽기 쉽게하기 위해 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) 
    

    EDIT (일부 자원)

  3. ==============================

    3.IN과 수 ()와 @ 알렉스 '대답은 아마 가장 간단한 해결책이지만,이 PL / pgSQL의 기능이 빠를 것으로 예상 :

    IN과 수 ()와 @ 알렉스 '대답은 아마 가장 간단한 해결책이지만,이 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
    ...
    

    이 양식은 관계 부문에 대한 쿼리의 광범위한 테스트에서 최고의 수행.

    또한 앱에서 쿼리를 만들 수있다,하지만 난 당신이 하나 개의 배열 매개 변수를 사용할 것을 전제로했다. 또한,이 빠른 어쨌든 아마입니다.

    어느 쿼리는 다음이 빨리하는 등의 인덱스가 필요합니다

    CREATE INDEX conversations_users_user_id_idx ON conversations_users (user_id);
    

    (USER_ID, conversation_id)에 다중 열 기본 (또는 고유) 키는 물론이지만, (당신은 잘 할 수 있습니다처럼!) (conversation_id, USER_ID)이 열등한 것 하나. 당신은 dba.SE에이 관련 질문에서 위의 링크에서 짧은 근거, 또는 포괄적 인 평가를 찾을 수

    나는 또한 당신이 conversations.conversation_id에 기본 키가 가정합니다.

    당신은 @ 알렉스 '쿼리 ANALYZE EXPLAIN와 성능 테스트 및이 기능을 실행하고 결과를보고 할 수 있습니까?

    참고 두 솔루션은 대화를 찾을 수 있다는 곳 배열 참여할 적어도 사용자 - 추가 사용자와의 대화를 포함하여. 당신이 사람들을 제외 할 경우, 내 기능의 추가 절을 주석을 해제 (또는 다른 쿼리에 추가).

    당신이 함수의 기능에 대한 자세한 설명이 필요하면 말해.

  4. ==============================

    4.이 액티브 객체를 유지합니다.

    이 액티브 객체를 유지합니다.

    아래 예제에서는 배열의 모든 코드와 연결된 시간 시트를 알고 싶어요.

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

    당신과 함께 작업의 전체 액티브 오브젝트가 있어야합니다. 당신이 진정한 범위 싶은 경우에, 당신은 당신의 위의 예제를 사용하고 .pluck과 결과에 전달할 수 있습니다 (: ID).

  5. ==============================

    5.모든 가능한 값으로 매핑 테이블을 생성하고 이것을 사용

    모든 가능한 값으로 매핑 테이블을 생성하고 이것을 사용

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

    6.

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

    이것은 쉽게 레일 범위로 할 수있다.

  7. ==============================

    7.난 당신이 정말 임시 테이블 덤비는 시작하고 싶지 않은 추측하고있다.

    난 당신이 정말 임시 테이블 덤비는 시작하고 싶지 않은 추측하고있다.

    귀하의 질문은 당신이 상위에 사용자 또는 대화 정확히 세트 대화를 원하는 여부에 분명했다. 다음은 상위 집합입니다 :

    with users as (select user_id from users where user_id in (<list>)
                  ),
         conv  as (select conversation_id, user_id
                   from conversations_users
                   where user_id in (<list>)
                  )
    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
    

    이 쿼리가 잘 작동하기 위해서는, 당신이 사용자와 conversations_users 모두 USER_ID에 인덱스가 있다고 가정합니다.

    정확한 설정하십시오. . .

    with users as (select user_id from users where user_id in (<list>)
                  ),
         conv  as (select conversation_id, user_id
                   from conversations_users
                   where user_id in (<list>)
                  )
    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
    
  8. ==============================

    8.@ 알렉스 블랙 모어의 대답에 따라, 상응하는 회화 클래스가 될 것이다 당신에 4 범위를 레일 :

    @ 알렉스 블랙 모어의 대답에 따라, 상응하는 회화 클래스가 될 것이다 당신에 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 }
    

    대신 레일을하고의를 최적화 할 수 있습니다 주 - ( "NOT IN") 당신이 어디에요을 할 수있는 (마이너스)하지만 읽기 정말 복잡 할 것입니다.

  9. ==============================

    9.알렉스 블랙 모어의 답변에 따라

    알렉스 블랙 모어의 답변에 따라

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

    나는 (이 추가적으로 사용자 무시) USER_1 및 user_2를 포함하는 대화의 conversation_id을 찾는, 같은 목표와 대안 쿼리를 발견했다

    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
    )
    

    이 포스트 그레스 쿼리 문을 설명을 통해 수행하는 분석에 따라 느리게, 그리고 난 더 조건이 최소한의 conversations_users의 각 행에 대해 하위 쿼리는 상관 서브 쿼리와 같이 실행 얻을 것이다, 평가 beign이 있기 때문에 그것이 사실 것 같아요. 이 쿼리를 가진 possitive 점은 그래서 당신이 conversations_users 테이블의이 추가적으로 필드를 선택할 수 있습니다, 그룹화되지 않은 것입니다. 어떤 경우에는 (광산 등)은 편리 할 수 ​​있습니다.

  10. from https://stackoverflow.com/questions/11468572/postgresql-where-all-in-array by cc-by-sa and MIT license