Mysql
 sql >> база данни >  >> RDS >> Mysql

Как да филтрирате SQL резултатите във връзка има-много-през

Бях любопитен. И както всички знаем, любопитството има репутация за убиване на котки.

И така, кой е най-бързият начин да одирате котка?

Средата за отлепване на котка за този тест:

  • PostgreSQL 9.0 на Debian Squeeze с прилична RAM и настройки.
  • 6 000 студенти, 24 000 членства в клубове (данните са копирани от подобна база данни с данни от реалния живот.)
  • Леко отклонение от схемата за именуване във въпроса:student.id е student.stud_id и club.id е club.club_id тук.
  • Наименувах заявките на техния автор в тази тема.
  • Изпълних всички заявки няколко пъти, за да попълня кеша, след което избрах най-доброто от 5 с EXPLAIN ANALYZE .
  • Съответни индекси (трябва да са оптималните - стига да не знаем предварително кои клубове ще бъдат запитани):
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) 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) Мартин 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 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) Шон: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;

Последните три се представят почти по същия начин. 4) и 5) водят до същия план за заявка.

Късни допълнения

Изискан SQL, но производителността не може да се справи:

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

Както се очакваше, тези двамата се представят почти еднакво. Планът на заявката води до сканиране на таблици, плановникът не намира начин да използва индексите тук.

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;

Изискан SQL, прилична производителност за CTE. Много екзотичен план за заявка.

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 вариант на заявка 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) erwin 3:2,377 ms

11 на ypercube) всъщност е просто изкривяващият обратен подход на този по-опростен вариант, който също все още липсваше. Работи почти толкова бързо, колкото и топ котките.

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

Трудно е за вярване, но ето още един, наистина нов вариант. Виждам потенциал за повече от две членства, но също така се нарежда сред най-добрите котки само с две.

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

Динамичен брой клубни членства

С други думи:различен брой филтри. Този въпрос задават точнодва клубни членства. Но много случаи на употреба трябва да се подготвят за различен брой. Вижте:



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Конфигурация на MySQL 8

  2. Отстраняване на неизправности:MySQL/MariaDB грешка #1044 и #1045 Достъпът е отказан за потребителя

  3. Заявката за изтриване не работи в mysql

  4. MySQL максимално използване на паметта

  5. Как да получите числови типове от MySQL с помощта на PDO?