Помогите разобраться с запросом MySQL - PullRequest
0 голосов
/ 08 апреля 2009

Вот таблицы, которые у меня есть:

Class
- id
- name

Order
- id
- name
- class_id (FK)

Family
- id
- order_id (FK)
- name

Genus
- id
- family_id (FK)
- name

Species
- id
- genus_id (FK)
- name

Я пытаюсь сделать запрос, чтобы получить список классов, порядков и фамилий, в которых нет ни одного вида. Вы можете видеть, что таблица имеет некоторую форму иерархии от Порядка вплоть до Видов. Каждая таблица имеет внешний ключ (FK), который относится к ближайшей таблице над собой в иерархии.

Пытаюсь заставить это работать, но я не очень хорошо себя чувствую. Любая помощь будет оценена!

Ответы [ 4 ]

6 голосов
/ 08 апреля 2009

Мета-ответ (комментарий к двум предыдущим ответам):

Использование IN имеет тенденцию ухудшаться до чего-то очень похожего на ИЛИ (дизъюнкцию) всех терминов в IN. Плохая производительность.

Выполнение левого соединения и поиск нуля - это улучшение, но это мракобес. Если мы можем сказать, что мы имеем в виду, давайте скажем это в вау, наиболее близком к тому, как мы сказали бы это на естественном языке:

select f.name
from   family f left join genus g on f.id = g.family_id
       WHERE NOT EXISTS (select * from species c where c.id = g.id);

Мы хотим, где что-то не существует, поэтому, если мы можем сказать «где не существует», тем лучше. И, select * в подзапросе не означает, что он действительно возвращает целую строку, так что это не «оптимизация» - заменить select * на select 1, по крайней мере, не в любой современной СУБД.

Кроме того, там, где в семье много родов (а в биологии большинство семей), мы получим по одному ряду (семья, род), когда все, что нас волнует, это семья. Итак, давайте получим один ряд на семью:

select DISTINCT f.name
from   family f left join genus g on f.id = g.family_id
       WHERE NOT EXISTS (select * from species c where c.id = g.id);

Это все еще не оптимально. Зачем? Что ж, он удовлетворяет требованию ОП, поскольку он находит «пустые» роды, но не может найти семейства, у которых нет родов, «пустых» семейств. Можем ли мы заставить это сделать это тоже?

select f.name
from   family f 
       WHERE NOT EXISTS (
       select * from genus g 
       join species c on c.id = g.id 
       where g.id = f.id);

Мы можем даже избавиться от отличного, потому что мы ни к чему не присоединяемся к семье. И это является оптимизацией.

Комментарий от ОП:

Это было очень ясное объяснение. Однако мне любопытно, почему использование IN или дизъюнкций плохо сказывается на производительности. Можете ли вы уточнить это или указать мне ресурс, где я могу узнать больше об относительной стоимости производительности различных операций с БД?

Думайте об этом так. Скажем, что в SQL не было оператора IN. Как бы вы подделали IN?

По серии ИЛИ:

where foo in (1, 2, 3)

эквивалентно

where ( foo = 1 ) or ( foo = 2 ) or (foo = 3 ) 

Хорошо, вы говорите, но это все еще не говорит мне, почему это плохо. Это плохо, потому что часто нет приличного способа использовать ключ или индекс для поиска этого. Таким образом, вы получаете либо a) сканирование таблицы, где для каждого дизъюнкции (или предиката, или элемента списка IN) проверяется строка, пока тест не станет истинным или список не будет исчерпан. Или б) вы получаете сканирование таблицы для каждого из этих дизъюнкций. Второй случай (b) на самом деле может быть лучше, поэтому вы иногда видите выбор с ИЛИ, превращенным в один выбор для каждого этапа объединения ИЛИ вместе:

 select * from table where x = 1 or x = 3 ;

 select * from table where x = 1 
 union select * from table where x = 3 ;

Теперь нельзя сказать, что вы никогда не сможете использовать список ИЛИ или IN. А в некоторых случаях оптимизатор запросов достаточно умен, чтобы превратить список IN в объединение - и другие ответы, которые вам были даны, как раз те случаи, когда это наиболее вероятно.

Но если мы сможем явно превратить наш запрос в объединение, нам не нужно задумываться о том, умный ли оптимизатор запросов. И вообще, соединения - это то, что лучше всего делает база данных.

3 голосов
/ 08 апреля 2009

Ну, просто сделав это быстро и грязно, я бы написал что-то вроде этого. Я трачу большую часть своего времени на использование Firebird, поэтому синтаксис MySQL может быть немного другим, но идея должна быть ясной

select f.name
from   family f left join genus g on f.id = g.family_id
       left join species s on g.id = species.genus_id
where  ( s.id is null )

если вы хотите установить существование рода, вы просто удалите «левую» часть объединения из семейства в род.

Надеюсь, я не неправильно понял вопрос и, таким образом, веду вас по неверному пути. Удачи!

редактировать: На самом деле, перечитывая это, я думаю, что это просто поймает семьи, где нет видов внутри рода. Я думаю, вы могли бы также добавить «и (g.id является нулевым)».

1 голос
/ 08 апреля 2009
SELECT f.name
FROM   family f
WHERE  NOT EXISTS (
       SELECT  1
       FROM    genus g 
       JOIN    species s
       ON      g.id = s.genus_id
       WHERE   g.family_id = f.id
       )

Обратите внимание, что в отличие от чистых LEFT JOIN решений, это более эффективно.

Он не выбирает ВСЕ строки, отфильтровывая строки со значениями NOT NULL, но вместо этого выбирает не более одной строки из genus и species.

1 голос
/ 08 апреля 2009

Подберите для спасения ...


select f.name from family as f, genus as g
where
  f.id == g.family_id and
  g.id not in (select genus_id from species);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...