4

私はデータベースを持っています

books          (primary key: bookID)
characterNames (foreign key: books.bookID) 
locations      (foreign key: books.bookID)

文字名と場所のテキスト内位置は、対応するテーブルに保存されます。
私は psycopg2 を使用して Pythonscript を作成しており、本の中で特定のキャラクター名と場所のすべての出現を見つけています。キャラクター名と場所の両方が見つかった本での出現のみが必要です。
ここでは、1 つの場所と 1 つの文字を検索するためのソリューションを既に取得しています。

WITH b AS (  
    SELECT bookid  
    FROM   characternames  
    WHERE  name = 'XXX'  
    GROUP  BY 1  
    INTERSECT  
    SELECT bookid  
    FROM   locations  
    WHERE  l.locname = 'YYY'  
    GROUP  BY 1  
    )  
SELECT bookid, position, 'char' AS what  
FROM   b  
JOIN   characternames USING (bookid)  
WHERE  name = 'XXX'  
UNION  ALL  
SELECT bookid, position, 'loc' AS what  
FROM   b  
JOIN   locations USING (bookid)  
WHERE  locname = 'YYY'  
ORDER  BY bookid, position;  

CTE 'b' にはすべての bookid が含まれており、文字名 'XXX' と場所 'YYY' が表示されます。

さらに、2 つの場所と名前 (またはそれぞれ 2 つの名前と場所) を検索することについても考えています。検索対象のすべてのエンティティが 1 つの本に含まれている必要がある場合は簡単ですが、次の場合は どう でしょ う か
。アル、ツールショップ)



この問題は、4、5、6... の条件で繰り返すことができます。
サブクエリをさらにINTERSECTすることを考えましたが、うまくいきません。
代わりに、見つかった bookID を UNION し、グループ化して、複数回出現する bookid を選択します。

WITH b AS (  
    SELECT bookid, count(bookid) AS occurrences  
    FROM  
        (SELECT DISTINCT bookid  
        FROM characterNames  
        WHERE name='XXX'  
        UNION  
        SELECT DISTINCT bookid  
        FROM characterNames  
        WHERE name='YYY'  
        UNION  
        SELECT DISTINCT bookid  
        FROM locations  
        WHERE locname='ZZZ'  
        GROUP BY bookid)  
    WHERE occurrences>1)  

これはうまくいくと思いますが、現時点ではテストできませんが、これが最善の方法ですか?

4

1 に答える 1

4

一般化されたケースにカウントを使用するという考えは健全です。ただし、構文に対するいくつかの調整:

WITH b AS (  
   SELECT bookid
   FROM  (
      SELECT DISTINCT bookid  
      FROM   characterNames  
      WHERE  name='XXX'  

      UNION ALL  
      SELECT DISTINCT bookid  
      FROM   characterNames  
      WHERE  name='YYY'  

      UNION ALL
      SELECT DISTINCT bookid  
      FROM   locations  
      WHERE  locname='ZZZ'  
      ) x
   GROUP  BY bookid
   HAVING count(*) > 1
   )
SELECT bookid, position, 'char' AS what
FROM   b
JOIN   characternames USING (bookid)
WHERE  name = 'XXX'

UNION  ALL
SELECT bookid, position, 'loc' AS what
FROM   b
JOIN   locations USING (bookid)
WHERE  locname = 'YYY'
ORDER  BY bookid, position;

ノート

  • サブクエリ間の重複を保持するには、UNION ALL(ではなく)を使用します。UNIONこの場合、それらをカウントできるようにする必要があります。

  • サブクエリは、異なる値を生成することになっています。それはDISTINCTあなたがそれを持っている方法で動作します。代わりに試しGROUP BY 1て、パフォーマンスが向上するかどうかを確認することをお勧めします(期待していません)。

  • GROUP BYサブクエリの外に出なければなりません。これは最後のサブクエリにのみ適用され、DISTINCT bookidすでに行っているようにそこには意味がありません。

  • 本に複数のヒットがあるかどうかのチェックは、HAVING条項に入れる必要があります。

     HAVING count(*) > 1
    

    WHERE句で集計値を使用することはできません。


1つのテーブルで条件を組み合わせる

1つのテーブルで複数の条件を単純に組み合わせることができません。調査結果の数をどのように数えますか?しかし、もう少し洗練された方法があります。パフォーマンスが向上する場合と向上しない場合があります。テストする必要があります(を使用してEXPLAIN ANALYZE)。どちらのクエリでも、テーブルに対して少なくとも2回のインデックススキャンが必要ですcharacterNames。少なくとも構文は短くなります。

のヒット数を計算する方法と、外側でcharacterNamesどのように変更したかを検討してください。sum(hits)SELECT

WITH b AS (  
   SELECT bookid
   FROM  (
      SELECT bookid
           , max((name='XXX')::int)
           + max((name='YYY')::int) AS hits
      FROM   characterNames  
      WHERE  (name='XXX'
           OR name='YYY')
      GROUP  BY bookid

      UNION ALL
      SELECT DISTINCT bookid, 1 AS hits  
      FROM   locations  
      WHERE  locname='ZZZ'  
      ) x
   GROUP  BY bookid
   HAVING sum(hits) > 1
   )
...

booleanaをに変換すると、とがinteger0られます。それは役に立ちます。FALSE1TRUE


EXISTSでより速く

私の会社に自転車に乗っている間、これは私の頭の後ろで蹴り続けました。このクエリはさらに高速になる可能性があると私は信じる理由があります。試してみてください:

WITH b AS (  
   SELECT bookid

        , (EXISTS (
            SELECT *
            FROM   characterNames c
            WHERE  c.bookid = b.bookid
            AND    c.name = 'XXX'))::int
        + (EXISTS (
            SELECT *
            FROM   characterNames c
            WHERE  c.bookid = b.bookid
            AND    c.name = 'YYY'))::int AS c_hits

        , (EXISTS (
            SELECT *
            FROM   locations l
            WHERE  l.bookid = b.bookid
            AND    l.locname='ZZZ'))::int AS l_hits
   FROM   books b  
   WHERE  (c_hits + l_hits) > 1
   )
SELECT c.bookid, c.position, 'char' AS what
FROM   b
JOIN   characternames c USING (bookid)
WHERE  b.c_hits > 0
AND    c.name IN ('XXX', 'YYY')

UNION  ALL
SELECT l.bookid, l.position, 'loc' AS what
FROM   b
JOIN   locations l USING (bookid)
WHERE  b.l_hits > 0
AND    l.locname = 'YYY'
ORDER  BY 1,2,3;
  • セミジョインEXISTSは最初の試合で実行を停止できます。CTEでのオール・オア・ナッシングの回答にのみ関心があるため、これにより、作業がはるかに高速になる可能性があります。

  • このようにして、集約する必要もありません(必要ありませんGROUP BY)。

  • また、文字や場所が見つかったかどうかを覚えており、実際に一致するテーブルのみを再訪します

于 2012-04-23T01:18:25.290 に答える