sql >> データベース >  >> RDS >> Mysql

SQLの結果をフィルタリングする方法は、多くのスルー関係になります

    興味がありました。そして、誰もが知っているように、好奇心は猫を殺すという評判があります。

    では、猫の皮を剥ぐ最も速い方法はどれですか?

    このテストの猫の皮を剥ぐ環境:

    • PostgreSQL 9.0 適切なRAMと設定を備えたDebianSqueezeで。
    • 6.000人の学生、24.000人のクラブ会員(実際のデータを含む同様のデータベースからコピーされたデータ)
    • 質問の命名スキーマからのわずかな逸脱:student.id student.stud_idです およびclub.id club.club_idです ここ。
    • このスレッドでは、作成者にちなんでクエリに名前を付けました。
    • すべてのクエリを数回実行してキャッシュにデータを入力し、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ミリ秒

    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ミリ秒

    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ミリ秒

    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ミリ秒

    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ミリ秒

    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ミリ秒

    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のためのまともなパフォーマンス。非常にエキゾチックなクエリプラン。

    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

    もう1つの遅い追加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)erwin 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)erwin 4:2.375ミリ秒

    信じがたいことですが、ここにもう1つの真に新しいバリアントがあります。 2つ以上のメンバーシップの可能性があると思いますが、2つだけでトップの猫の1つにもランクされています。

    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. PostgreSQLシーケンスのHibernateの使用は、シーケンステーブルに影響しません

    2. データファイルとStatisticaのマージ、パート1

    3. OracleのCEIL()関数

    4. PostgreSQLでのデータベースの一覧表示と切り替え