Мета-ответ (комментарий к двум предыдущим ответам):
Использование 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 в объединение - и другие ответы, которые вам были даны, как раз те случаи, когда это наиболее вероятно.
Но если мы сможем явно превратить наш запрос в объединение, нам не нужно задумываться о том, умный ли оптимизатор запросов. И вообще, соединения - это то, что лучше всего делает база данных.