SQLの結果を多対多の関係にフィルターする方法


100

私はテーブルを持っていると仮定するとstudentclubstudent_club

student {
    id
    name
}
club {
    id
    name
}
student_club {
    student_id
    club_id
}

サッカー(30)クラブと野球(50)クラブの両方ですべての生徒を見つける方法を知りたいです。
このクエリは機能しませんが、これまでのところ最も近いものです。

SELECT student.*
FROM   student
INNER  JOIN student_club sc ON student.id = sc.student_id
LEFT   JOIN club c ON c.id = sc.club_id
WHERE  c.id = 30 AND c.id = 50

回答:


145

気になった。そして、私たち皆が知っているように、好奇心は猫を殺すことで有名です。

だから、猫を皮むくための最速の方法はどれですか?

このテストの正確な猫肌環境:

  • まともなRAMと設定を備えたDebian Squeeze上のPostgreSQL 9.0
  • 6.000人の学生、24.000のクラブメンバーシップ(実際のデータを含む同様のデータベースからコピーされたデータ。)
  • 質問の命名スキーマからのわずかな迂回:student.idis student.stud_idand club.idis club.club_idhere。
  • このスレッドでは、2つのインデックスがあるインデックスで、作成者にちなんでクエリに名前を付けました。
  • すべてのクエリを数回実行してキャッシュにデータを入力し、次にEXPLAIN ANALYZEを使用して5つの中から最適なものを選択しました。
  • 関連するインデックス(最適である必要があります-どのクラブが照会されるかについて事前の知識がない限り):

    ALTER TABLE student ADD CONSTRAINT student_pkey PRIMARY KEY(stud_id );
    ALTER TABLE student_club ADD CONSTRAINT sc_pkey PRIMARY KEY(stud_id, club_id);
    ALTER TABLE club       ADD CONSTRAINT club_pkey PRIMARY KEY(club_id );
    CREATE INDEX sc_club_id_idx ON student_club (club_id);

    club_pkeyここではほとんどのクエリで必要ありません。
    主キーは、PostgreSQLで一意のインデックスを自動的に実装します。
    最後のインデックスは、PostgreSQLのマルチカラムインデックスのこの既知の欠点を補うためのものです。

複数列のBツリーインデックスは、インデックスの列の任意のサブセットを含むクエリ条件で使用できますが、先頭(左端)の列に制約がある場合に最も効率的です。

結果:

EXPLAIN ANALYZEからの合計ランタイム。

1)マーティン2:44.594 ms

SELECT s.stud_id, s.name
FROM   student s
JOIN   student_club sc USING (stud_id)
WHERE  sc.club_id IN (30, 50)
GROUP  BY 1,2
HAVING COUNT(*) > 1;

2)アーウィン1:33.217 ms

SELECT s.stud_id, s.name
FROM   student s
JOIN   (
   SELECT stud_id
   FROM   student_club
   WHERE  club_id IN (30, 50)
   GROUP  BY 1
   HAVING COUNT(*) > 1
   ) sc USING (stud_id);

3)マーティン1:31.735 ms

SELECT s.stud_id, s.name
   FROM   student s
   WHERE  student_id IN (
   SELECT student_id
   FROM   student_club
   WHERE  club_id = 30
   INTERSECT
   SELECT stud_id
   FROM   student_club
   WHERE  club_id = 50);

4)デレク:2.287 ms

SELECT s.stud_id,  s.name
FROM   student s
WHERE  s.stud_id IN (SELECT stud_id FROM student_club WHERE club_id = 30)
AND    s.stud_id IN (SELECT stud_id FROM student_club WHERE club_id = 50);

5)アーウィン2:2.181ミリ秒

SELECT s.stud_id,  s.name
FROM   student s
WHERE  EXISTS (SELECT * FROM student_club
               WHERE  stud_id = s.stud_id AND club_id = 30)
AND    EXISTS (SELECT * FROM student_club
               WHERE  stud_id = s.stud_id AND club_id = 50);

6)ショーン:2.043 ms

SELECT s.stud_id, s.name
FROM   student s
JOIN   student_club x ON s.stud_id = x.stud_id
JOIN   student_club y ON s.stud_id = y.stud_id
WHERE  x.club_id = 30
AND    y.club_id = 50;

最後の3つはほとんど同じように動作します。4)と5)は同じクエリプランになります。

後期追加:

派手なSQLですが、パフォーマンスを維持できません。

7)ypercube 1:148.649ミリ秒

SELECT s.stud_id,  s.name
FROM   student AS s
WHERE  NOT EXISTS (
   SELECT *
   FROM   club AS c 
   WHERE  c.club_id IN (30, 50)
   AND    NOT EXISTS (
      SELECT *
      FROM   student_club AS sc 
      WHERE  sc.stud_id = s.stud_id
      AND    sc.club_id = c.club_id  
      )
   );

8)ypercube 2:147.497 ms

SELECT s.stud_id,  s.name
FROM   student AS s
WHERE  NOT EXISTS (
   SELECT *
   FROM  (
      SELECT 30 AS club_id  
      UNION  ALL
      SELECT 50
      ) AS c
   WHERE NOT EXISTS (
      SELECT *
      FROM   student_club AS sc 
      WHERE  sc.stud_id = s.stud_id
      AND    sc.club_id = c.club_id  
      )
   );

予想通り、これら2つはほぼ同じように動作します。テーブルスキャンのクエリプランの結果。プランナはここでインデックスを使用する方法を見つけません。


9)wildplasser 1:49.849ミリ秒

WITH RECURSIVE two AS (
   SELECT 1::int AS level
        , stud_id
   FROM   student_club sc1
   WHERE  sc1.club_id = 30
   UNION
   SELECT two.level + 1 AS level
        , sc2.stud_id
   FROM   student_club sc2
   JOIN   two USING (stud_id)
   WHERE  sc2.club_id = 50
   AND    two.level = 1
   )
SELECT s.stud_id, s.student
FROM   student s
JOIN   two USING (studid)
WHERE  two.level > 1;

ファンシーSQL、CTEにはまともなパフォーマンス。非常にエキゾチックなクエリプラン。
繰り返しますが、9.1がこれを処理する方法は興味深いでしょう。ここで使用するdbクラスターをすぐに9.1にアップグレードします。多分私はシバン全体を再実行します...


10)wildplasser 2:36.986ミリ秒

WITH sc AS (
   SELECT stud_id
   FROM   student_club
   WHERE  club_id IN (30,50)
   GROUP  BY stud_id
   HAVING COUNT(*) > 1
   )
SELECT s.*
FROM   student s
JOIN   sc USING (stud_id);

クエリのCTEバリアント2)。驚いたことに、まったく同じデータを使用したクエリプランが若干異なる可能性があります。で順次スキャンが見つかりましたstudent。サブクエリバリアントがインデックスを使用しています。


11)ypercube 3:101.482 ms

別の遅い追加@ypercube。方法がいくつあるかは、確かに驚くべきことです。

SELECT s.stud_id, s.student
FROM   student s
JOIN   student_club sc USING (stud_id)
WHERE  sc.club_id = 10                 -- member in 1st club ...
AND    NOT EXISTS (
   SELECT *
   FROM  (SELECT 14 AS club_id) AS c  -- can't be excluded for missing the 2nd
   WHERE  NOT EXISTS (
      SELECT *
      FROM   student_club AS d
      WHERE  d.stud_id = sc.stud_id
      AND    d.club_id = c.club_id
      )
   )

12)アーウィン3:2.377ミリ秒

@ypercubeの11)は、実際にはこの単純なバリアントの気まぐれな逆のアプローチにすぎず、これもまだ欠けていました。トップの猫とほぼ同じ速度でパフォーマンスします。

SELECT s.*
FROM   student s
JOIN   student_club x USING (stud_id)
WHERE  sc.club_id = 10                 -- member in 1st club ...
AND    EXISTS (                        -- ... and membership in 2nd exists
   SELECT *
   FROM   student_club AS y
   WHERE  y.stud_id = s.stud_id
   AND    y.club_id = 14
   )

13)アーウィン4:2.375 ms

信じがたいですが、ここに別の真に新しい変種があります。私は2つ以上のメンバーシップの可能性があると思いますが、2つだけで上位の猫にもランクされます。

SELECT s.*
FROM   student AS s
WHERE  EXISTS (
   SELECT *
   FROM   student_club AS x
   JOIN   student_club AS y USING (stud_id)
   WHERE  x.stud_id = s.stud_id
   AND    x.club_id = 14
   AND    y.club_id = 10
   )

クラブ会員数の動的

つまり、さまざまな数のフィルター。この質問では、ちょうど2つのクラブ会員になることが求められました。しかし、多くのユースケースでは、さまざまな数に備える必要があります。

この関連する後の回答での詳細な議論:


1
Brandstetter、とてもいい仕事です。私はあなたに追加の信用を与えるためにこの質問に賞金を始めました(しかし、私は24時間待つ必要があります)。とにかく、私は...これらのクエリを使用すると、複数のclub_idの代わりに2つだけのを追加する開始するときに行くか疑問
Xeoncross

@Xeoncross:寛大なジェスチャーへの称賛。:) club_idが増えると、1)と2)の速度が近づくと思いますが、ランキングを倒すには、もっと大きな数値にする必要があります。
Erwin Brandstetter、2011年

クラブが2つ以上ある場合は、それらのクラブを含む別のテーブルを作成します。次に、selectでそのテーブルに結合します。
Paul Morgan

@アーウィン:Thnx(ベンチマーク用)。手抜きではありませんが、(student_id, club_id)インデックス(またはその逆)を使用してそれらのクエリ(つまり、自分のものだけではありません)を試すことができます。
ypercubeᵀᴹ

3
問題のドメインとサンプルサイズを考えると、200ミリ秒未満は許容できるパフォーマンスであると考えるのは間違っていますか?個人的な興味のために、SQL Server 2008 R2で同じ構造インデックスと(私は)データの広がりを使用して独自のテストを実行しましたが、100万人の学生(特定のドメインに適度に大きいセット)にスケーリングしましたが、まだありませんでしたIMO、異なるアプローチを分離することはあまりありません。もちろん、リレーショナル分割に基づくものは、ベーステーブルをターゲットにして、「拡張性」の利点を与えることができます。
11

18
SELECT s.*
FROM student s
INNER JOIN student_club sc_soccer ON s.id = sc_soccer.student_id
INNER JOIN student_club sc_baseball ON s.id = sc_baseball.student_id
WHERE 
 sc_baseball.club_id = 50 AND 
 sc_soccer.club_id = 30

10
select *
from student
where id in (select student_id from student_club where club_id = 30)
and id in (select student_id from student_club where club_id = 50)

このクエリは正常に機能しますが、RDBMSに非常に多くのインデックス*クラブの数をチェックするように依頼する必要があるのではと気になります。
Xeoncross、2011

6
SQLのpythonのようなクリーンなスタイルに似ているため、このクエリが最も好きです。私はこの種のコードと0.44ms(Seanのクエリとの違い)を喜んでトレードします。
MGP 2013年

5

student_idだけが必要な場合:

    Select student_id
      from student_club
     where club_id in ( 30, 50 )
  group by student_id
    having count( student_id ) = 2

学生の名前も必要な場合:

Select student_id, name
  from student s
 where exists( select *
                 from student_club sc
                where s.student_id = sc.student_id
                  and club_id in ( 30, 50 )
             group by sc.student_id
               having count( sc.student_id ) = 2 )

club_selectionテーブルに3つ以上のクラブがある場合:

Select student_id, name
  from student s
 where exists( select *
                 from student_club sc
                where s.student_id = sc.student_id
                  and exists( select * 
                                from club_selection cs
                               where sc.club_id = cs.club_id )
             group by sc.student_id
               having count( sc.student_id ) = ( select count( * )
                                                   from club_selection ) )

最初の2つは私のクエリ1と同じ/に含まれていますが、3つ目は上記のコメントで追加された@Xeoncrossの質問に対応しています。私はだまされずにその部分に投票します。
Erwin Brandstetter、2011年

コメントをありがとうございますが、私はいくつかのフォーマットも示しています。「そのまま」残しておきます。
Paul Morgan、

4
SELECT *
FROM   student
WHERE  id IN (SELECT student_id
              FROM   student_club
              WHERE  club_id = 30
              INTERSECT
              SELECT student_id
              FROM   student_club
              WHERE  club_id = 50)  

以上の一般的な解決策は、に拡張しやすくn、クラブと回避するINTERSECT(MySQLで利用できません)とIN(としてこれのパフォーマンスは、MySQLで吸います

SELECT s.id,
       s.name
FROM   student s
       join student_club sc
         ON s.id = sc.student_id
WHERE  sc.club_id IN ( 30, 50 )
GROUP  BY s.id,
          s.name
HAVING COUNT(DISTINCT sc.club_id) = 2  

間違いなく、2番目の答えは、コードによって生成されるクエリに最適です。私は真剣に10の結合またはサブクエリを記述して、10の基準の関係区分を見つけますか?いえいえ、代わりにこの素晴らしいソリューションを使用します。HAVINGMySQLで何ができるか教えてくれてありがとう。
エリックL.

4

別のCTE。見た目はきれいですが、通常のサブクエリでgroupbyと同じプランが生成される可能性があります。

WITH two AS (
    SELECT student_id FROM tmp.student_club
    WHERE club_id IN (30,50)
    GROUP BY student_id
    HAVING COUNT(*) > 1
    )
SELECT st.* FROM tmp.student st
JOIN two ON (two.student_id=st.id)
    ;

テストしたい人のために、私の生成したtestdataのコピー:

DROP SCHEMA tmp CASCADE;
CREATE SCHEMA tmp;

CREATE TABLE tmp.student
    ( id INTEGER NOT NULL PRIMARY KEY
    , sname VARCHAR
    );

CREATE TABLE tmp.club
    ( id INTEGER NOT NULL PRIMARY KEY
    , cname VARCHAR
    );

CREATE TABLE tmp.student_club
    ( student_id INTEGER NOT NULL  REFERENCES tmp.student(id)
    , club_id INTEGER NOT NULL  REFERENCES tmp.club(id)
    );

INSERT INTO tmp.student(id)
    SELECT generate_series(1,1000)
    ;

INSERT INTO tmp.club(id)
    SELECT generate_series(1,100)
    ;

INSERT INTO tmp.student_club(student_id,club_id)
    SELECT st.id  , cl.id
    FROM tmp.student st, tmp.club cl
    ;

DELETE FROM tmp.student_club
WHERE random() < 0.8
    ;

UPDATE tmp.student SET sname = 'Student#' || id::text ;
UPDATE tmp.club SET cname = 'Soccer' WHERE id = 30;
UPDATE tmp.club SET cname = 'Baseball' WHERE id = 50;

ALTER TABLE tmp.student_club
    ADD PRIMARY KEY (student_id,club_id)
    ;

ええ、それは実際には、私の最初のバージョンのようにgroup byを持つサブクエリです。同じクエリプラン+ CTEオーバーヘッドは、同じパフォーマンス+ CTEのビットになります。素晴らしいテスト設定ですが。
Erwin Brandstetter、2011年

CTEオーバーヘッドがあるかどうかはわかりません。テストデータの配布は非常に重要です。統計の可用性も同様です。VACUUMANALYZEの後、実行時間は67.4から1.56ミリ秒になりました。QPに関係するのはハッシュとビットマップのみです。
wildplasser '19年

それはあなたの場合は特別です、大きなテーブルの80%を削除してたくさん更新した後、何よりも死んだタプルが多くなりました。当然のことながら、真空分析は非常に役立ちます。CTEありとなしの両方のバリアントを実行しましたが、驚いたことに、クエリプランは同一ではありませんでした。それ以上に、私はそのためのチャットルームを開きます。
Erwin Brandstetter、2011年

心配しないで、80%のデッド行を知っていました...統計も重要だと思います。しかし、ヒストグラムはランダムに削除されるため、かなり「フラット」です。多分それは、プランナが計画を切り替えることを決定するのに十分に変化する必要なページの見積もりだけです。
wildplasser '19年

3

したがって、猫の皮をむく方法は複数あります。さらに2つ
追加して、完全なものにします。

1)GROUPが最初、JOINが後で

(student_id, club_id)がで一意である正しいデータモデルを想定していstudent_clubます。マーティン・スミスの2番目のバージョンは多少似ていますが、最初に参加し、後でグループに参加します。これはもっと速いはずです:

SELECT s.id, s.name
  FROM student s
  JOIN (
   SELECT student_id
     FROM student_club
    WHERE club_id IN (30, 50)
    GROUP BY 1
   HAVING COUNT(*) > 1
       ) sc USING (student_id);

2)存在する

そしてもちろん、クラシックがありEXISTSます。デレクのバリアントに似ていINます。シンプルで高速。(MySQLでは、これはを使用したバリアントよりもかなり高速ですIN):

SELECT s.id, s.name
  FROM student s
 WHERE EXISTS (SELECT 1 FROM student_club
               WHERE  student_id = s.student_id AND club_id = 30)
   AND EXISTS (SELECT 1 FROM student_club
               WHERE  student_id = s.student_id AND club_id = 50);

3

誰もこの(クラシック)バージョンを追加していないので:

SELECT s.*
FROM student AS s
WHERE NOT EXISTS
      ( SELECT *
        FROM club AS c 
        WHERE c.id IN (30, 50)
          AND NOT EXISTS
              ( SELECT *
                FROM student_club AS sc 
                WHERE sc.student_id = s.id
                  AND sc.club_id = c.id  
              )
      )

または類似:

SELECT s.*
FROM student AS s
WHERE NOT EXISTS
      ( SELECT *
        FROM
          ( SELECT 30 AS club_id  
          UNION ALL
            SELECT 50
          ) AS c
        WHERE NOT EXISTS
              ( SELECT *
                FROM student_club AS sc 
                WHERE sc.student_id = s.id
                  AND sc.club_id = c.club_id  
              )
      )

もう少し別の方法で試してください。Explain Extended:EAVテーブルの複数の属性:GROUP BYとNOT EXISTSの記事に触発されました。

SELECT s.*
FROM student_club AS sc
  JOIN student AS s
    ON s.student_id = sc.student_id
WHERE sc.club_id = 50                      --- one option here
  AND NOT EXISTS
      ( SELECT *
        FROM
          ( SELECT 30 AS club_id           --- all the rest in here
                                           --- as in previous query
          ) AS c
        WHERE NOT EXISTS
              ( SELECT *
                FROM student_club AS scc 
                WHERE scc.student_id = sc.id
                  AND scc.club_id = c.club_id  
              )
      )

別のアプローチ:

SELECT s.stud_id
FROM   student s

EXCEPT

SELECT stud_id
FROM 
  ( SELECT s.stud_id, c.club_id
    FROM student s 
      CROSS JOIN (VALUES (30),(50)) c (club_id)
  EXCEPT
    SELECT stud_id, club_id
    FROM student_club
    WHERE club_id IN (30, 50)   -- optional. Not needed but may affect performance
  ) x ;   

+1 ..完全ではない猫のコレクションに素敵な追加!:)私はそれらをベンチマークに追加しました。
Erwin Brandstetter、2011年

これは公平な戦いではありません:)このようなリレーショナル除算の大きな利点は除数をベーステーブルにすることができるため、除数の変更が非常に安価です。つまり、同じクエリの対象となるベーステーブルの行をSQL​​を変更して更新するのと対照的です。毎回クエリします。
2011年

@ErwinBrandstetter:テストに3番目のバリエーションを追加することは可能ですか?
ypercubeᵀᴹ

@ypercube:わかった。かなりねじれたバージョン。:)
Erwin Brandstetter、2012

1
@Erwin:これに時間を費やすことができた場合、両方(stud_id, club_id)(club_id, stud_id)(またはプライマリと一意)の2つの一意キーを試すこともできますか?これらのクエリの一部については、2から140ミリ秒の違いは、実行プランの違いで説明するには高すぎるとまだ思います。
ypercubeᵀᴹ

2
WITH RECURSIVE two AS
    ( SELECT 1::integer AS level
    , student_id
    FROM tmp.student_club sc0
    WHERE sc0.club_id = 30
    UNION
    SELECT 1+two.level AS level
    , sc1.student_id
    FROM tmp.student_club sc1
    JOIN two ON (two.student_id = sc1.student_id)
    WHERE sc1.club_id = 50
    AND two.level=1
    )
SELECT st.* FROM tmp.student st
JOIN two ON (two.student_id=st.id)
WHERE two.level> 1

    ;

CTEスキャンは2つの別々のサブクエリの必要性を回避するため、これはかなりうまく機能しているようです。

再帰クエリを誤用する理由は常にあります!

(ところで:mysqlには再帰クエリがないようです)


そこへのもう1つの中途半端な道を見つけるための+1!クエリをベンチマークに追加しました。よろしくお願いします。:)
Erwin Brandstetter '19年

大丈夫です。しかし、それはもちろん冗談を意図したものでした。CTEは、「迷子」の生徒*クラブのレコードが追加された場合に実際にうまく機能します。(テストのために、私は1000人の学生* 100個のクラブを使用し、ランダムに80%を削除しました)
wildplasser '19年

1

クエリ2)と10)の異なるクエリプラン

私は実際のdbでテストしたので、名前はcatskinリストとは異なります。これはバックアップコピーなので、すべてのテスト実行中に何も変更されませんでした(カタログへのマイナーな変更を除く)。

クエリ2)

SELECT a.*
FROM   ef.adr a
JOIN (
    SELECT adr_id
    FROM   ef.adratt
    WHERE  att_id IN (10,14)
    GROUP  BY adr_id
    HAVING COUNT(*) > 1) t using (adr_id);

Merge Join  (cost=630.10..1248.78 rows=627 width=295) (actual time=13.025..34.726 rows=67 loops=1)
  Merge Cond: (a.adr_id = adratt.adr_id)
  ->  Index Scan using adr_pkey on adr a  (cost=0.00..523.39 rows=5767 width=295) (actual time=0.023..11.308 rows=5356 loops=1)
  ->  Sort  (cost=630.10..636.37 rows=627 width=4) (actual time=12.891..13.004 rows=67 loops=1)
        Sort Key: adratt.adr_id
        Sort Method:  quicksort  Memory: 28kB
        ->  HashAggregate  (cost=450.87..488.49 rows=627 width=4) (actual time=12.386..12.710 rows=67 loops=1)
              Filter: (count(*) > 1)
              ->  Bitmap Heap Scan on adratt  (cost=97.66..394.81 rows=2803 width=4) (actual time=0.245..5.958 rows=2811 loops=1)
                    Recheck Cond: (att_id = ANY ('{10,14}'::integer[]))
                    ->  Bitmap Index Scan on adratt_att_id_idx  (cost=0.00..94.86 rows=2803 width=0) (actual time=0.217..0.217 rows=2811 loops=1)
                          Index Cond: (att_id = ANY ('{10,14}'::integer[]))
Total runtime: 34.928 ms

クエリ10)

WITH two AS (
    SELECT adr_id
    FROM   ef.adratt
    WHERE  att_id IN (10,14)
    GROUP  BY adr_id
    HAVING COUNT(*) > 1
    )
SELECT a.*
FROM   ef.adr a
JOIN   two using (adr_id);

Hash Join  (cost=1161.52..1261.84 rows=627 width=295) (actual time=36.188..37.269 rows=67 loops=1)
  Hash Cond: (two.adr_id = a.adr_id)
  CTE two
    ->  HashAggregate  (cost=450.87..488.49 rows=627 width=4) (actual time=13.059..13.447 rows=67 loops=1)
          Filter: (count(*) > 1)
          ->  Bitmap Heap Scan on adratt  (cost=97.66..394.81 rows=2803 width=4) (actual time=0.252..6.252 rows=2811 loops=1)
                Recheck Cond: (att_id = ANY ('{10,14}'::integer[]))
                ->  Bitmap Index Scan on adratt_att_id_idx  (cost=0.00..94.86 rows=2803 width=0) (actual time=0.226..0.226 rows=2811 loops=1)
                      Index Cond: (att_id = ANY ('{10,14}'::integer[]))
  ->  CTE Scan on two  (cost=0.00..50.16 rows=627 width=4) (actual time=13.065..13.677 rows=67 loops=1)
  ->  Hash  (cost=384.68..384.68 rows=5767 width=295) (actual time=23.097..23.097 rows=5767 loops=1)
        Buckets: 1024  Batches: 1  Memory Usage: 1153kB
        ->  Seq Scan on adr a  (cost=0.00..384.68 rows=5767 width=295) (actual time=0.005..10.955 rows=5767 loops=1)
Total runtime: 37.482 ms

@wildplasser:異なるクエリプランをご覧ください!予想外。9.0ページ。チャットルームは扱いにくいので、ここで答えを悪用しました。
Erwin Brandstetter、2011年

奇妙なシーン。基本的に、CTEと同じQP(9.0.1-beta-something)です。インデックスscan + mergeではなく、seq scan + bitmapです。多分、オプティマイザのコストヒューリスティックスの欠陥ですか?さらに別のCTEの乱用を作成します...
wildplasser '19年

1

@ erwin-brandstetterください、これをベンチマークしてください:

SELECT s.stud_id, s.name
FROM   student s, student_club x, student_club y
WHERE  x.club_id = 30
AND    s.stud_id = x.stud_id
AND    y.club_id = 50
AND    s.stud_id = y.stud_id;

それは6)のようなものです@ @sean、ちょうどきれいだと思います。


2
@-notifyingは回答ではなくコメントでのみ機能することを知っておく必要があります。偶然この投稿に出くわしました。クエリプランとクエリのパフォーマンスは、Seanのクエリと同じです。実質的には同じですが、明確なJOIN構文を使用するSeanのクエリは、より明確であるため、一般的に推奨される形式です。ただし、別の有効な回答として+1してください!
Erwin Brandstetter 2012年

0
-- EXPLAIN ANALYZE
WITH two AS (
    SELECT c0.student_id
    FROM tmp.student_club c0
    , tmp.student_club c1
    WHERE c0.student_id = c1.student_id
    AND c0.club_id = 30
    AND c1.club_id = 50
    )
SELECT st.* FROM tmp.student st
JOIN two ON (two.student_id=st.id)
    ;

クエリプラン:

 Hash Join  (cost=1904.76..1919.09 rows=337 width=15) (actual time=6.937..8.771 rows=324 loops=1)
   Hash Cond: (two.student_id = st.id)
   CTE two
     ->  Hash Join  (cost=849.97..1645.76 rows=337 width=4) (actual time=4.932..6.488 rows=324 loops=1)
           Hash Cond: (c1.student_id = c0.student_id)
           ->  Bitmap Heap Scan on student_club c1  (cost=32.76..796.94 rows=1614 width=4) (actual time=0.667..1.835 rows=1646 loops=1)
                 Recheck Cond: (club_id = 50)
                 ->  Bitmap Index Scan on sc_club_id_idx  (cost=0.00..32.36 rows=1614 width=0) (actual time=0.473..0.473 rows=1646 loops=1)                     
                       Index Cond: (club_id = 50)
           ->  Hash  (cost=797.00..797.00 rows=1617 width=4) (actual time=4.203..4.203 rows=1620 loops=1)
                 Buckets: 1024  Batches: 1  Memory Usage: 57kB
                 ->  Bitmap Heap Scan on student_club c0  (cost=32.79..797.00 rows=1617 width=4) (actual time=0.663..3.596 rows=1620 loops=1)                   
                       Recheck Cond: (club_id = 30)
                       ->  Bitmap Index Scan on sc_club_id_idx  (cost=0.00..32.38 rows=1617 width=0) (actual time=0.469..0.469 rows=1620 loops=1)
                             Index Cond: (club_id = 30)
   ->  CTE Scan on two  (cost=0.00..6.74 rows=337 width=4) (actual time=4.935..6.591 rows=324 loops=1)
   ->  Hash  (cost=159.00..159.00 rows=8000 width=15) (actual time=1.979..1.979 rows=8000 loops=1)
         Buckets: 1024  Batches: 1  Memory Usage: 374kB
         ->  Seq Scan on student st  (cost=0.00..159.00 rows=8000 width=15) (actual time=0.093..0.759 rows=8000 loops=1)
 Total runtime: 8.989 ms
(20 rows)

したがって、それでも学生のseqスキャンが必要なようです。


それが9.1で修正されたかどうかを確認するのを待ちきれません。
Erwin Brandstetter、2011年

0
SELECT s.stud_id, s.name
FROM   student s,
(
select x.stud_id from 
student_club x 
JOIN   student_club y ON x.stud_id = y.stud_id
WHERE  x.club_id = 30
AND    y.club_id = 50
) tmp_tbl
where tmp_tbl.stud_id = s.stud_id
;

最速のバリアントの使用(Mr. BrandstetterチャートのMr. Sean)。結合は1つだけで、student_clubマトリックスのみが生存する権利を持つバリアントである可能性があります。したがって、最長のクエリは計算する列が2つだけになるため、クエリを細くすることが考えられます。


1
このコードスニペットは問題を解決する可能性がありますが、説明を含めると、投稿の品質を向上させるのに役立ちます。あなたが今尋ねている人だけでなく、あなたが将来の読者のための質問に答えていることを忘れないでください!回答を編集して説明を追加し、適用される制限と前提を示してください。
BrokenBinary 2017年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.