2017-09-05 1 views
0

현재 null을 포함하는 왼쪽 조인으로 필터를 수행하는 방법을 알아 내려고하고 있습니다. 여기에 내가 일하고 있어요 스키마의 단순화 버전이다 : A는 bookclub_idreviewer_id 주어진 모든 책을 나에게 돌려 위해 그들이> = 3을 평가 한 것을 쿼리는포스트 그레스에서 외부 조인 후 nulls 필터링

CREATE TABLE bookclubs (
    bookclub_id UUID NOT NULL PRIMARY KEY 
); 

CREATE TABLE books (
    bookclub_id UUID NOT NULL, 
    book_id UUID NOT NULL 
); 
ALTER TABLE books ADD CONSTRAINT books_pk PRIMARY KEY(bookclub_id, book_id); 
ALTER TABLE books ADD CONSTRAINT book_to_bookclub FOREIGN KEY(bookclub_id) 
    REFERENCES bookclubs(bookclub_id) ON UPDATE NO ACTION ON DELETE CASCADE; 
CREATE INDEX books_bookclub_index ON books (bookclub_id); 

CREATE TABLE book_reviews (
    bookclub_id UUID NOT NULL, 
    book_id UUID NOT NULL, 
    reviewer_id TEXT NOT NULL, 
    rating int8 NOT NULL 
); 
ALTER TABLE book_reviews ADD CONSTRAINT book_reviews_pk PRIMARY KEY(bookclub_id, book_id, reviewer_id); 
ALTER TABLE book_reviews ADD CONSTRAINT book_review_to_book FOREIGN KEY(bookclub_id,book_id) 
    REFERENCES books(bookclub_id,book_id) ON UPDATE NO ACTION ON DELETE CASCADE; 
CREATE INDEX book_review_to_book_index ON book_reviews (bookclub_id, book_id); 
CREATE INDEX book_review_by_reviewer ON book_reviews (bookclub_id, reviewer_id, rating); 

내가 원하는, 또는 그들이 평가하지 않았습니다. 그들이 평가하지 않은 도서에는 book_reviews 테이블에 항목이 없으며 이는 내가 할 수있는 일이 아닙니다. rating은 관련성이있는 경우 실제로 열거 형이지만 실제로는 그렇지 않습니다. 명백한 일을에서

내 첫 번째 시도는 실패

SELECT * 
FROM books 
     LEFT OUTER JOIN book_reviews 
        ON (((books.bookclub_id = book_reviews.bookclub_id) 
          AND (books.book_id = book_reviews.book_id)) 
         AND (book_reviews.reviewer_id = 'alice')) 
WHERE books.bookclub_id = '00000000-0000-0000-0000-000000000000' 
     AND book_reviews.rating != 1 
     AND book_reviews.rating != 2; 

이는 WHERE 조건이 실제로 어떻게 구현되는지에 대해 한 번 내가 생각하는 어떤 의미를 만드는 사용자의 리뷰가없는 책 삭제합니다. 여기

Nested Loop (cost=0.30..16.39 rows=1 width=104) 
    -> Index Scan using book_reviews_pk on book_reviews (cost=0.15..8.21 rows=1 width=72) 
     Index Cond: ((bookclub_id = '00000000-0000-0000-0000-000000000000'::uuid) AND (reviewer_id = 'alice'::text)) 
     Filter: ((rating <> 1) AND (rating <> 2)) 
    -> Index Only Scan using books_pk on books (cost=0.15..8.17 rows=1 width=32) 
     Index Cond: ((bookclub_id = '00000000-0000-0000-0000-000000000000'::uuid) AND (book_id = book_reviews.book_id)) 

그래서 내가 널 위해 명시 적 검사를 추가 쿼리 계획이다 :

SELECT * 
FROM books 
     LEFT OUTER JOIN book_reviews 
        ON (((books.bookclub_id = book_reviews.bookclub_id) 
          AND (books.book_id = book_reviews.book_id)) 
         AND (book_reviews.reviewer_id = 'alice')) 
WHERE books.bookclub_id = '00000000-0000-0000-0000-000000000000' 
     AND book_reviews.rating IS NULL 
     OR (book_reviews.rating != 1 
      AND book_reviews.rating != 2); 

이 올바른 결과를 반환하지만 끔찍하게 비효율적 인 것으로 나타나고 중단에 DB를 갈기. 여기에 내가이 일을 해석에는 전문가는 아니지만 쿼리 계획

Hash Left Join (cost=18.75..52.56 rows=1346 width=104) 
    Hash Cond: ((books.bookclub_id = book_reviews.bookclub_id) AND (books.book_id = book_reviews.book_id)) 
    Filter: (((books.bookclub_id = '00000000-0000-0000-0000-000000000000'::uuid) AND (book_reviews.rating IS NULL)) OR ((book_reviews.rating <> 1) AND (book_reviews.rating <> 2))) 
    -> Seq Scan on books (cost=0.00..23.60 rows=1360 width=32) 
    -> Hash (cost=18.69..18.69 rows=4 width=72) 
     -> Bitmap Heap Scan on book_reviews (cost=10.23..18.69 rows=4 width=72) 
       Recheck Cond: (reviewer_id = 'alice'::text) 
       -> Bitmap Index Scan on book_review_by_reviewer (cost=0.00..10.22 rows=4 width=0) 
        Index Cond: (reviewer_id = 'alice'::text) 

하지만 그 Filter 외부 나쁜 것 같습니다에 이동. 원하는 결과를 얻을 수 있도록 쿼리를 구조화하는 효율적인 방법이 있습니까? 감사합니다

답변

0
는 조인 조건에 필터를 이동

:

SELECT * 
FROM 
    books 
    LEFT OUTER JOIN 
    book_reviews ON 
     books.bookclub_id = book_reviews.bookclub_id 
     AND books.book_id = book_reviews.book_id 
     AND book_reviews.reviewer_id = 'alice' 
     AND book_reviews.rating != 1 
     AND book_reviews.rating != 2 
WHERE books.bookclub_id = '00000000-0000-0000-0000-000000000000' 

또는 약간 짧게 :

AND book_reviews.rating not in (1, 2) 
+0

답변 주셔서 감사합니다.하지만 제대로 작동하지 않는 것 같습니다. 나는 여전히 리뷰 내용을 nulls로 필터링해야하는 행을 얻는다. https://gist.github.com/drapp/0e9b09fe97f99a27fa1dde2683df7316 –

+0

@DouglasRapp 이제는 문제를 이해할 수 있다고 생각한다. 나는 오늘 시간이 없지만 내일 시도 할 것이다. –

0

을 나는 우리가 그것을 알아 냈다고 생각합니다. 우리는 WHERE 절에 괄호 세트 누락되었습니다 : 그없이

SELECT * 
FROM books 
     LEFT OUTER JOIN book_reviews 
        ON (((books.bookclub_id = book_reviews.bookclub_id) 
          AND (books.book_id = book_reviews.book_id)) 
         AND (book_reviews.reviewer_id = 'alice')) 
WHERE books.bookclub_id = '00000000-0000-0000-0000-000000000000' 
     AND (book_reviews.rating IS NULL 
      OR (book_reviews.rating != 1 
      AND book_reviews.rating != 2)); 

부울 논리가 잘못 연결합니다. 이 쿼리는 올바른 결과를 반환하고 정상적인 쿼리 계획을 가지고 있으므로 전체적인 문제인 것처럼 보입니다. 보고 주셔서 감사합니다.

관련 문제