|
Программирование >> Полное сканирование таблицы
ПРИМЕЧАНИЕ В старой записи внешних соединений в SQL Server комбинация внешнего соединения и условия равно null не работает. Например, преобразуя предьщущий пример в запись SQL Server, вы могли бы попробовать такой запрос: SELECT ... FROM Employees Е. Departments D WHERE E.Department ID *= D.Department ID AND D.Department ID IS NULL Ho результат был бы совсем не тот, которого вы ожидаете! Вспомните, что SQL Server интерпретирует все фильтрующие условия для присоединяемой внешне таблицы itaK часть соединения. SQL Server попытается провести соединение со строками Departments, для которых существуют равные null первичные ключи (например, значения D. Departnient ID, равные mill). Даже еспи бы такие строки существовали, нарушая правильный дизайн базы данных, они бы никогда не могли бьп-ь успешно присоединены к таблице Empl oyees, так itaK соединение по условию равенства не может быть успешно выполнено для ключевых значений, равных null. Вместо этого запрос не отфильтрует никакие строки, а вернет всех сотрудников, причем все соединения будут внешними. Внешние соединения, ведущие к внутренним соединениям Рассмотрим рис. 7.24, на котором внешнее соединение ведет к внутреннему соединению. Рис. 7.24. Внешнее соединение ведет к внутреннему соединению В старом стиле Oracle SQL подобное соединение записывается так: SELECT ... FROM Tab!el Tl. ТаЫе2 Т2, ТаЫеЗ ТЗ WHERE Tl.FKey2 = Т2.РКеу2(+) AND T2.FKey3 = ТЗ.РКеуЗ Во внешнем случае первого соединения база данных сгенерирует псевдостроку из Т2, причем значение всех столбцов, в том числе Т2. FKey3 будет равно null. Однако равный null внешний ключ никогда не может быть успешно присоединен к другой таблице, поэтому строка, представляющая внешний случай соединения, будет отброшена при попытке выполнить внутреннее соединение с ТЗ. Таким образом, внешнее соединение, ведущее к внутреннему соединению, дает в точности тот же результат, который вы бы получили, если бы оба соединения были внутренними. Но его стоимость выше, так как база данных отбрасывает стоки, которые не удовлетворяют соединению, позже в плане исполнения. Это всегда будет ошибкой. Если разработчик требует сохранить подобное соединение, замените внешнее соединение, ведущее к внутреннему, внешним соединением, ведущим к другому внешнему соединению. В противном случае используйте два внутренних соединения. Рис. 7.25. Внешнее соединение по направлению к детальной таблице В новом стиле записи ANSI это может быть запрос следующего вида: SELECT ... FROM Departments D LEFT OUTER JOIN Employees E ON D.Department ID = E.DepartmentJD Или в старой записи Oracle: SELECT ... FROM Departments D. Employees E WHERE D.DepartmentJD = E.Department ID(+) В старой записи SQL Server: SELECT ... FROM Departments D. Employees E WHERE D.Department ID *= E.Department ID В любом случае, что же семантически означает этот запрос? Его можно представить как обращение: Перечисли мне всех сотрудников, для которых указаны отделы (внутренний случай), а также данные об их отделах, и еще присовокупи данные об отделах, в которых нет сотрудников (внешний случай) . Во внутреннем случае результат сопоставляет каждую строку с детальной сущностью (сотрудник, принадлежащий какому-то отделу), а во внешнем случае результат отображает каждую строку на главную сущность (отдел, не принадлежащий ни одному сотруднику). Маловероятно, что такая нелепая смесь сущностей будет полезна и необходима в одном запросе, поэтому подобные запросы, в которых внешние соединения ведут к детальным таблицам, редко бывают верны. Самый распространенный случай такой ошибки - это соединение с детальной таблицей, которая обычно предлагает ноль или одну детальную строку для каждой главной, и лишь изредка это соединение предлагает несколько детальных строк для главной таблицы. Иногда разработчики упускают из виду последствия этого редкого случая многие к одному , а во время тестирования ошибка может и не всплыть. Внешние соединения с детальной таблицей с фильтром На рис. 7.26 показано внешнее соединение с детальной таблицей, для которой также существует фильтрующее условие. Иногда две ошибки компенсируют друг друга. Внешнее соединение с детальной таблицей, имеющей фильтр, может представ- Внешние соединения, указывающие на детальную таблицу Рассмотрим рис. 7.25, где указатель стрелки посередине связи обозначает внешнее соединение, указывающее на детальную таблицу. лять собой двойную ошибку и иметь все недостатки, описанные в последних двух подразделах. Иногда фильтр компенсирует эффеет проблемного внешнего соединения, функционально превращая его во внутреннее. В таких случаях следует избегать удаления фильтра, если только вы не превращаете одновременно внешнее соединение во внутреннее. 110.3 Рис. 7.26. Внешнее соединение с фильтрованной детальной таблицей Наиболее интересный случай, который может проиллюстрировать рис. 7.26, - когда фильтр имеет смысл только в контексте внешнего соединения. Это случай, когда фильтрующее условие для Т1 истинно только во внешнем случае - например, Tl.Fkey ID IS NULL. (Здесь Tl.Fkey ID - это внешний ключ, указывающий на Т2. PKey ID в показанном на диаграмме соединении.) Как и в предыдущем примере условия IS NULL для значения ключа соединения (тогда это был первичный ключ), этот случай эквивалентен подзапросу NOT EXISTS. Так же, как и в том примере, это необычное выражение условия NOT EXISTS иногда обеспечивает дополнительную степень контроля над местом в плане исполнения, когда база данных выполняет соединение и отбрасывает строки, не удовлетворяющие условию. Так как все строки, присоединенные во внутреннем случае, отбрасываются условием IS NULL, мы избегаем обычной проблемы внешних соединений с детальными таблицами - смешивания различных сущностей в строках, полученных во внутреннем и внешнем случаях соединения. Опять две ошибки компенсируют друг друга! Запросы с подзапросами Практически все реальные запросы с подзапросами включают особый вид условий для строк во внешнем, главном запросе; они должны соответствовать или не соответствовать подходящим строкам в связанном запросе. Например, если вам нужны данные об отделах, в которых есть сотрудники, вы можете использовать такой запрос: SELECT ... FROM Departments D WHERE EXISTS (SELECT NULL FROM Employees E WHERE E.Department ID = D.Department ID) Или же вы можете запросить данные об отделах, в которых нет сотрудников: SELECT ... FROM Departments D WHERE NDT EXISTS (SELECT NULL FROM Employees E WHERE E.Department ID = D.Department ID)
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |