2017-09-05 7 views
0

現在、NULLを含む左結合でフィルタを実行する方法を理解しようとしています。ここで私が働いているスキーマの簡略化 バージョンがあります:bookclub_idreviewer_id与えられ、すべての書籍を私に返すために、彼らは> = 3を評価したので、クエリはpostgresの外部結合後にNULLをフィルタリングする

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

だだから私はnullのための明示的なチェックを追加しました:

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

私たちはそれを考え出したと信じています。ブール論理仲間間違ったことがないと

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

:私たちはWHERE句の括弧のセットが欠落していました。このクエリは適切な結果を返し、正常なクエリプランを持っているので、それが問題全体のように見えます。探してくれてありがとう。

関連する問題