Как отфильтровать результаты SQL в отношении "имеет много сквозных"


101

Предполагая , что у меня есть таблицы 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

Ответы:


146

Мне было любопытно. И, как все мы знаем, любопытство имеет репутацию убийцы кошек.

Итак, какой самый быстрый способ снять шкуру с кошки?

Точная среда для снятия шкуры кошки для этого теста:

  • PostgreSQL 9.0 на Debian Squeeze с приличной оперативной памятью и настройками.
  • 6.000 студентов, 24000 членских взносов в клубы (данные скопированы из аналогичной базы данных с данными из реальной жизни).
  • Небольшое отклонение от схемы именования в вопросе: 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 мс

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;

Последние три работают примерно так же. 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  
      )
   );

Как и ожидалось, эти двое работают практически одинаково. Результатом плана запроса является сканирование таблиц, планировщик не находит здесь способа использовать индексы.


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

Еще одно позднее добавление @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 мс

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

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
Brandstetter, Очень хорошая работа. Я назначил награду за этот вопрос, чтобы дать вам дополнительную оценку (но мне нужно ждать 24 часа). В любом случае, мне интересно, как проходят эти запросы, когда вы начинаете добавлять несколько club_id вместо двух ...
— Xeoncross

@Xeoncross: Спасибо за ваш щедрый жест. :) С большим количеством club_ids я подозреваю, что 1) и 2) станут ближе по скорости, но это должно быть большее число, чтобы опустить рейтинг.
— Эрвин Брандштеттер

если у вас больше пары клубов, создайте другую таблицу, содержащую эти клубы. Затем присоединяйтесь к этой таблице по вашему выбору.
— Пол Морган

@Erwin: Спасибо (за тесты). Не придираюсь, но, возможно, вы можете попробовать эти запросы (я имею в виду все, а не только мои) с (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)

Этот запрос работает нормально, но что-то меня беспокоит, что мне приходится просить СУБД проверять столько индексов * количество клубов.
— Xeoncross

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

5

Если вам просто нужен student_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. Он выглядит чистым, но, вероятно, сгенерирует тот же план, что и groupby в обычном подзапросе.

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

Да, это просто подзапрос с group by, как в моей первой версии. Тот же план запроса + накладные расходы 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 .. приятные дополнения к не самой полной коллекции кошачьих шкур! :) Я добавил их в тест.
— Эрвин Брандштеттер

Это нечестная борьба :) Большим преимуществом реляционного деления, такого как этот, является то, что делитель может быть базовой таблицей, так что изменение делителя обходится очень дешево, т.е. строки обновления контраста в базовой таблице, нацеленной на тот же запрос с изменением SQL запрашивать каждый раз.
— однажды, когда

@ErwinBrandstetter: Можно ли добавить в ваши тесты третий вариант?
— ypercubeᵀᴹ

@ypercube: Вы поняли. Довольно закрученная версия. :)
— Эрвин Брандштеттер

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-сканирование позволяет избежать необходимости в двух отдельных подзапросах.

Всегда есть причина злоупотреблять рекурсивными запросами!

(Кстати: в 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-beta-something) для CTE: seq scan + bitmap вместо index scan + merge. Может быть, ошибка в эвристике оптимизатора? Я собираюсь произвести еще одно оскорбление CTE ...
— wildplasser

1

@ erwin-brandstetter Пожалуйста, сравните это:

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)

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


Не могу дождаться, чтобы увидеть, было ли это исправлено в 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.