気になった。そして、私たち皆が知っているように、好奇心は猫を殺すことで有名です。
だから、猫を皮むくための最速の方法はどれですか?
このテストの正確な猫肌環境:
- まともなRAMと設定を備えたDebian Squeeze上のPostgreSQL 9.0。
- 6.000人の学生、24.000のクラブメンバーシップ(実際のデータを含む同様のデータベースからコピーされたデータ。)
- 質問の命名スキーマからのわずかな迂回:student.idisstudent.stud_idandclub.idisclub.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つのクラブ会員になることが求められました。しかし、多くのユースケースでは、さまざまな数に備える必要があります。
この関連する後の回答での詳細な議論: