sql >> Databáze >  >> RDS >> Mysql

Jak filtrovat výsledky SQL ve vztahu has-many-through

Byl jsem zvědavý. A jak všichni víme, zvědavost má pověst zabijáka koček.

Jaký je tedy nejrychlejší způsob stažení kůže z kočky?

Prostředí stahující kočku pro tento test:

  • PostgreSQL 9.0 na Debian Squeeze se slušnou RAM a nastavením.
  • 6 000 studentů, 24 000 členství v klubech (data zkopírována z podobné databáze s údaji ze skutečného života.)
  • Nepatrná odchylka od schématu pojmenování v otázce:student.id je student.stud_id a club.id je club.club_id zde.
  • Dotazy jsem v tomto vláknu pojmenoval po jejich autorovi.
  • Několikrát jsem provedl všechny dotazy, abych naplnil mezipaměť, a poté jsem pomocí EXPLAIN ANALYZE vybral nejlepší z 5 .
  • Relevantní indexy (měly by být optimální – pokud nebudeme předem vědět, které kluby budou dotazovány):
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 není zde pro většinu dotazů vyžadován.
Primární klíče automaticky implementují jedinečné indexy v PostgreSQL.
Poslední index má nahradit tento známý nedostatek vícesloupcové indexy na PostgreSQL:

Vícesloupcový index B-stromu lze použít s podmínkami dotazu, které zahrnují libovolnou podmnožinu sloupců indexu, ale index je nejúčinnější, když existují omezení na úvodní (zcela vlevo) sloupce.

Výsledky

Celková doba běhu z EXPLAIN ANALYZE .

1) Martin 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) Erwin 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) Martin 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) Derek: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) Erwin 2:2,181 ms

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

Poslední tři fungují téměř stejně. 4) a 5) mají za následek stejný plán dotazů.

Pozdní přidání

Fantastické SQL, ale výkon nemůže držet krok:

7) ypercube 1:148,649 ms

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

Jak se dalo očekávat, tyto dva fungují téměř stejně. Plán dotazů vede ke skenování tabulek, plánovač zde nenašel způsob, jak indexy použít.

9) wildplasser 1:49,849 ms

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;

Efektní SQL, slušný výkon na CTE. Velmi exotický plán dotazů.

10) wildplasser 2:36,986 ms

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 varianta dotazu 2). Překvapivě to může vést k mírně odlišnému plánu dotazů s přesně stejnými daty. Našel jsem sekvenční skenování na student , kde poddotaz-varianta používala index.

11) ypercube 3:101,482 ms

Další pozdní přídavek ypercube. Je neuvěřitelně úžasné, kolik způsobů existuje.

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 ms

ypercube's 11) je ve skutečnosti jen obrácený přístup k této jednodušší variantě, který také stále chyběl. Podává výkon téměř stejně rychle jako nejlepší kočky.

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 ms

Těžko uvěřit, ale je tu další, skutečně nová varianta. Vidím potenciál pro více než dvě členství, ale také se řadí mezi nejlepší kočky s pouhými dvěma.

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

Dynamický počet členství v klubu

Jinými slovy:různý počet filtrů. Tato otázka vyžadovala přesně dvě klubová členství. Ale mnoho případů použití se musí připravit na různý počet. Viz:



  1. Formátujte data tabulky SQL jako textovou tabulku

  2. Vysoké využití místa z crfclust.bdb

  3. Konfigurace PostgreSQL pro kontinuitu podnikání

  4. SequelizeConnectionError:certifikát s vlastním podpisem