Як фільтрувати результати SQL у співвідношенні «багато-через»


100

Припускаючи , що у мене є таблиці student, clubі student_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

Мені було цікаво. І як ми всі знаємо, цікавість має репутацію вбивства котів.

Отже, який найшвидший спосіб зібрати кішку?

Точне оточення для котів для цього тесту:

  • PostgreSQL 9.0 на Debian Squeeze з гідною оперативною пам’яттю та налаштуваннями.
  • 6 000 студентів, 24 000 членів клубу (дані скопійовані з подібної бази даних з реальними даними).
  • Невелике відхилення від схеми іменування у питанні: student.idє student.stud_idі club.idє club.club_idтут.
  • Я назвав запити за їхнім автором у цій темі, з індексом, де їх два.
  • Я кілька разів запускав усі запити, щоб заповнити кеш, тоді я вибрав найкраще з 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-дерева може використовуватися з умовами запитів, що включають будь-який підмножина стовпців індексу, але індекс є найбільш ефективним, коли на провідних (крайніх лівих) стовпцях є обмеження.

Результати:

Загальний час виконання від ПОЯСНЕННЯ АНАЛІЗУ.

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,228 мс

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;

Останні три виконують майже те саме. 4) і 5) призводять до одного плану запитів.

Пізні доповнення:

Фантазії SQL, але продуктивність не може йти в ногу.

7) іперкуба 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) іперкуба 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  
      )
   );

Як і очікувалося, ці двоє виконують майже те саме. Результати плану запитів при скануванні таблиць, планувальник не знайде способу використання тут індексів.


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) іперкуба 3: 101,482 мс

Ще одне пізня добавка @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,337 мс

@ ypercube's 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 мс

Важко повірити, але ось ще один, справді новий варіант. Я бачу потенціал для більш ніж двох членів, але він також посідає серед кращих котів лише з двома.

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
Брендестетер, Дуже приємна робота. Я розпочав щедро це питання, щоб дати вам додатковий кредит (але я повинен почекати 24 години). У всякому разі, мені цікаво, як проходять ці запити, коли ви починаєте додавати декілька club_id замість лише двох ...
Xeoncross

@Xeoncross: Кудо ваш щедрий жест. :) З більшою кількістю club_ids я підозрюю, що швидкість наблизиться до 1) і 2), але, щоб перевалити рейтинг, доведеться збільшити кількість.
Ервін Брандстеттер

якщо у вас є більше декількох клубів, тоді створіть іншу таблицю, яка містить ці клуби. Потім приєднайтесь до цієї таблиці у виборі.
Пол Морган

@Erwin: Thnx (для орієнтирів). Не запозичення, але, можливо, ви можете спробувати ці запити (я маю на увазі всі, а не лише мої) з (student_id, club_id)індексом (або зворотним).
ypercubeᵀᴹ

3
Я помиляюся, вважаючи, що що-небудь менше 200 мс є прийнятною продуктивністю, враховуючи домен, про який йдеться, та розмір вибірки? Для особистого інтересу я провів свої власні тести на SQL Server 2008 R2, використовуючи ті самі індекси структури та (я думаю) поширення даних, але масштабування до мільйона студентів (я вважаю, що досить великий набір для даного домену), і все ще не було Не варто розділяти різні підходи, ІМО. Звичайно, ті, що базуються на реляційному поділі, можуть орієнтуватися на базову таблицю, надаючи їм перевагу "розширюваності".
день, коли

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

6
Мені найбільше подобається цей запит, оскільки він нагадує чистий стиль, його схожий на python в sql. Я б із задоволенням торгував 0,44 мс (що відрізняється від запиту Шона) для такого типу коду.
MGP

5

Якщо ви просто хочете студент_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 у вас більше двох клубів:

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

Перші два включені в / те саме, що і мій запит 1. Але третій адресує @Xeoncross 'додав питання до коментарів вище. Я б проголосував за цю частину без дупу.
Ервін Брандстеттер

Дякую за коментар, але я також демонструю деяке форматування. Я залишу це так, як є.
Пол Морган

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  

Без сумніву, ваша друга відповідь найкраща для запитів, що генеруються кодом. Я серйозно збираюся написати 10 приєднань чи підзапитів, щоб знайти реляційний поділ на 10 критеріїв? Чорт, ні, я замість цього використаю це геніальне рішення. Дякуємо, що ви навчали мене, що HAVINGробить у MySQL.
Ерік Л.

4

Ще один CTE. Це виглядає чисто, але, ймовірно, генерує той самий план, що і груповий звичайний підзапит.

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

Для тих, хто хоче тестувати, копія моїх генеруючих тестових даних:

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

Так, це насправді просто запит з групою, як у моїй першій версії. Один і той же план запитів + ​​накладні витрати CTE призводять до однакової продуктивності + трохи для CTE. Хоча гарне налаштування тесту.
Ервін Брандштеттер

Я не знаю, чи є CTE-накладні витрати. Поширення тестових даних є дуже важливим. Такою є наявність статистичних даних: після АНАЛІЗУВАННЯ ВАКУУМА час роботи зайнявся від 67,4 до 1,56 мс. У QP беруть участь лише хеш-файли та растрові карти.
wildplasser

Це особливо у вашому випадку, після видалення 80% з великої таблиці та оновлення багато у вас було більше мертвих кортежів, ніж будь-що інше. Недарма вакуумний аналіз дуже допомагає. Я застосував обидва варіанти з і без CTE, і на диво, плани запитів не були ідентичними. а ще краще, я відкрию для цього чат.
Ервін Брандстеттер

Не хвилюйтесь, я знав про 80% мертвих рядків ... Я думаю, що і статистика має значення. Але гістограма є досить «плоскою», видаленою випадковим чином. Може бути, саме оцінка необхідних сторінок достатньо зміниться, щоб планувальник вирішив переключити плани.
wildplasser

3

Тож існує кілька способів шкіряти кота .
Я додам ще два, щоб зробити це, ну, більш повним.

1) ГРУПА спочатку, приєднайтесь пізніше

Припускаючи нормальний модель даних , де (student_id, club_id)знаходиться унікальний в student_club. Друга версія Мартіна Сміта схожа на дещо схожу, але він приєднується до першої, до групи пізніше. Це має бути швидше:

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 vs. 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

Це не справедлива боротьба :) Велика перевага реляційного поділу, такого як це дільник, може бути базовою таблицею, так що зміна дільника дуже дешева, тобто контрастне оновлення рядків у базовій таблиці, націлене на той самий запит із зміною SQL запит кожного разу.
одного дня, коли

@ErwinBrandstetter: Чи можна було б додати третій варіант у ваші тести?
ypercubeᵀᴹ

@ypercube: ти це отримав. Досить скручена версія. :)
Erwin Brandstetter

1
@Erwin: Коли вам вдасться витратити час на це, чи можете ви також спробувати два унікальних клавіші, як на (так (stud_id, club_id)і на (club_id, stud_id)первинному та унікальному)? Я все ще думаю, що для деяких із цих запитів різниця від 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-сканування дозволяє уникнути необхідності в двох окремих підзапитах.

Завжди є причина неправильного використання рекурсивних запитів!

(BTW: у mysql, схоже, немає рекурсивних запитів)


+1 - знайти ще один пристойний шлях до цього шляху! Я додав ваш запит до еталону. Сподіваюся, що з вами все гаразд. :)
Ервін Брандштеттер

Нічого страшного. Але це було задумано як жарт, звичайно. CTE насправді спрацьовує добре, якщо додати більше «бродячих» студентських * клубних записів. (Для тестування я використав 1000 студентів * 100 клубів і видалив 80% випадковим чином)
wildplasser

1

Різні плани запитів у запиті 2) та 10)

Я тестував у реальному житті db, тому назви відрізняються від списку котячих шкір. Це резервна копія, тому нічого не змінювалося протягом усіх тестових запусків (крім незначних змін у каталогах).

Запит 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. Кімната чату була непростою, тому я тут зловживаю відповіддю.
Ервін Брандстеттер

Дивні сцени. В основному той самий QP (9.0.1-бета-щось) для CTE: сканування послідовності + растрове зображення замість індексу сканування + злиття. Може бути недоліком у евристиці витрат оптимізатора? Я збираюся зробити ще одне зловживання CTE ...
wildplasser

1

@ erwin-marketetter Будь ласка, порівняйте це:

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
Ви повинні знати, що @повідомлення про це працює лише в коментарях, а не у відповідях. Я наткнувся на цю посаду випадково. План запиту та ефективність вашого запиту ідентичні запиту Шона. Це фактично те саме, але запит Шона з явним JOINсинтаксисом є загальноприйнятою формою, оскільки він зрозуміліший. +1 - ще одна відповідна відповідь!
Ервін Брандстеттер

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)

Тож все ще здається, що хочеться сканувати послідовність на студента.


Не можу чекати, якщо це було виправлено у 9.1.
Ервін Брандстеттер

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
;

Використання найшвидшого варіанту (містер Шон у графіку містера Брандстеттера). Може бути варіантом лише з одним приєднанням до матриці Student_club має право жити. Отже, найдовший запит матиме лише два стовпці для обчислення, ідея - зробити запит тонким.


1
Хоча цей фрагмент коду може вирішити питання, зокрема пояснення дійсно допомагає покращити якість вашої публікації. Пам’ятайте, що ви відповідаєте на запитання читачів у майбутньому, а не лише про людину, яка зараз запитує! Будь ласка, відредагуйте свою відповідь, щоб додати пояснення та вказати, які обмеження та припущення застосовуються.
BrokenBinary
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.