:: Visual Foxpro, Foxpro for DOS
Условия объединения и выборки при использовании JOIN
Tir

Сообщений: 38
Дата регистрации: 21.03.2005
Доброго времени суток, уважаемые лисоводы! При объединении таблиц при помощи JOIN столкнулся с такой особенностью его обработки. Например, если при объединении 2-х таблиц на них наложить доп. условия выборки, то объединение таблиц будет происходить по разному, если это условие записать в предложении WHERE или в предложении FROM.
Пример:
SELECT ;
t1.fld1, ;
t1.fld2, ;
t2.fld1, ;
t2.fld2 ;
FROM t1 LEFT OUTER JOIN t2 ON (t1.fld1=t2.fld1 AND t2.fld2<>0) ;
WHERE t1.fld2>1000
В этом случае объединение будет произведено верно: в результат будут включены все записи t1, удовлетворяющие доп. условию выборки t1.fld2>1000 и записи t2, удовлетворяющие условию объединения t1.fld1=t2.fld2 и доп. условию выборки t2.fld2<>0.
SELECT ;
t1.fld1, ;
t1.fld2, ;
t2.fld1, ;
t2.fld2 ;
FROM t1 LEFT OUTER JOIN t2 ON (t1.fld1=t2.fld1) ;
WHERE t1.fld2>1000 AND t2.fld2<>0
Если перенести доп. условие выборки, накладываемое на t2, в предложение WHERE, то объединение будет произведено как внутреннее (INNER JOIN).
То есть доп. условия выборки, накладываемые в данном случае на "левую" таблицу, выносить в предложение WHERE можно, а на правую - нельзя.
Откуда такая асимметрия? И если можно, объясните, как вообще в FoxPro обрабатывается предложение JOIN при использовании его вместе с предложением WHERE? Или скажите, где подробно освещён данный вопрос (желательно на русском языке). Заранее благодарен.
Ratings: 0 negative/0 positive
Re: Условия объединения и выборки при использовании JOIN
Snick

Сообщений: 5949
Откуда: Москва
Дата регистрации: 21.05.2001
Навскидку, посмотрите для начала хотя бы вот эту статью
Некоторые аспекты использования пользовательских функций в предложениях SQL
Ratings: 0 negative/0 positive
Re: Условия объединения и выборки при использовании JOIN
Влад Колосов

Сообщений: 22664
Откуда: Ростов-на-Дону
Дата регистрации: 05.05.2005
forum.foxclub.ru


------------------
Совершенство - это не тогда, когда нельзя
ничего прибавить, а тогда, когда нечего убавить.
Ratings: 0 negative/0 positive
Re: Условия объединения и выборки при использовании JOIN
Владимир Максимов
Автор

Сообщений: 14098
Откуда: Москва
Дата регистрации: 02.09.2000
Если тема все еще актуальна...

--------------------------------------------------------------------------------
Объединение нескольких таблиц

Вообще-то, объединение нескольких таблиц в одном запросе ничем не отличается от стандарта, используемого в команде Select-SQL. Просто это тот вопрос, который вызывает наибольшее количество проблем со стороны начинающих программистов. Именно поэтому этот вопрос включен в данную статью.

Для пояснения особенностей объединения таблиц необходимо предварительно создать тестовые таблицы

* Первая таблица
CREATE CURSOR FirstTab (FirstTabId I, FirstName C(50), FirstRecNum I)
INSERT INTO FirstTab (FirstTabId, FirstName, FirstRecNum) VALUES (1, "Строка 1, Таблица 1, Код 1", 1)
INSERT INTO FirstTab (FirstTabId, FirstName, FirstRecNum) VALUES (1, "Строка 2, Таблица 1, Код 1", 2)
INSERT INTO FirstTab (FirstTabId, FirstName, FirstRecNum) VALUES (2, "Строка 3, Таблица 1, Код 2", 3)
INSERT INTO FirstTab (FirstTabId, FirstName, FirstRecNum) VALUES (2, "Строка 4, Таблица 1, Код 2", 4)
INSERT INTO FirstTab (FirstTabId, FirstName, FirstRecNum) VALUES (3, "Строка 5, Таблица 1, Код 3", 5)
INSERT INTO FirstTab (FirstTabId, FirstName, FirstRecNum) VALUES (4, "Строка 6, Таблица 1, Код 4", 6)
* Вторая таблица
CREATE CURSOR SecondTab (SecondTabId I, SecondName C(50), SecondRecNum I)
INSERT INTO SecondTab (SecondTabId, SecondName, SecondRecNum) VALUES (1, "Строка 1, Таблица 2, Код 1", 1)
INSERT INTO SecondTab (SecondTabId, SecondName, SecondRecNum) VALUES (1, "Строка 2, Таблица 2, Код 1", 2)
INSERT INTO SecondTab (SecondTabId, SecondName, SecondRecNum) VALUES (2, "Строка 3, Таблица 2, Код 2", 3)
INSERT INTO SecondTab (SecondTabId, SecondName, SecondRecNum) VALUES (3, "Строка 4, Таблица 2, Код 3", 4)
INSERT INTO SecondTab (SecondTabId, SecondName, SecondRecNum) VALUES (3, "Строка 5, Таблица 2, Код 3", 5)
INSERT INTO SecondTab (SecondTabId, SecondName, SecondRecNum) VALUES (5, "Строка 6, Таблица 2, Код 5", 6)

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

В последующих примерах, поясняющих работу механизма объединения таблиц, я буду использовать эти две созданные таблицы.



Список таблиц

Объединение таблиц через указания списка таблиц в опции FROM - это самый первый способ объединения таблиц, который появился в стандарте Select-SQL. Выглядит такое объединение следующим образом

SELECT FirstTab.*, SecondTab.* ;
FROM FirstTab, SecondTab

Если выполнить такой запрос, то результат будет очень странным. Подобное объединение называют "Декартовым объединением" или "Curtesian product". Используют такой вид объединения достаточно редко. Разве что, по ошибке. Но в данном случае это позволяет понять механизм работы объединения вообще.

Обратите внимание на значение полей FirstTab.FirstRecNum и SecondTab.SecondRecNum. В данной таблице - это просто порядковые номера записей. Судя по результату, механизм объединения таблиц похож на вложенные циклы. Т.е. что-то вроде такого кода

SELECT FirstTab
SCAN
SELECT SecondTab
SCAN
* Формирование очередной строки результирующей выборки
ENDSCAN
ENDSCAN

Цикл SCAN...ENDSCAN предназначен для перебора записей в таблице. В данном синтаксисе он автоматически переходит на начало таблицы и перебирает каждую запись в порядке их следования пока не дойдет до последней записи. Посмотрите HELP по данному циклу. У него есть ряд дополнительных опций, позволяющих уточнить, какие именно записи следует перебирать.

Разумеется, физически, при выполнении команды Select-SQL все происходит не так, но данная модель позволяет понять, почему в запросе оказались те или иные записи.

Ну, хорошо, в таком синтаксисе объединение таблиц редко когда имеет практический смысл. Неужели нельзя просто перечислять объединяемые таблицы в опции FROM? Разумеется, можно. Только надо задать дополнительные условия. Другими словами, задать условия объединения таблиц.

SELECT FirstTab.*, SecondTab.* ;
FROM FirstTab, SecondTab ;
WHERE FirstTab.FirstTabId = SecondTab.SecondTabId

Вот результат этого запроса уже почти соответствует ожиданиям. Однако только почти. В чем же заключается это самое "почти"?

В качестве условия объединения таблиц указано равенство кодов. Т.е. в результирующую выборку должны попасть записи, у которых значение поля FirstTab.FirstTabId равно значению поля SecondTab.SecondTabId. Ну, они и попали. Однако почему в результирующей выборке четыре записи с кодом 1?

Обычно новички рассуждают примерно следующим образом: В первой таблице есть две записи с кодом 1. Во второй таблице есть две записи с кодом 1. Значит, в результирующей выборке должно оказаться две записи с кодом 1. Правильно? Нет! Не правильно!

Чтобы понять, где же ошибка в рассуждениях вернемся к модели вложенных циклов. В этом случае, алгоритм формирования результирующей выборки можно представить примерно так

SELECT FirstTab
SCAN
SELECT SecondTab
SCAN
IF (выполняется условие WHERE)
* Формирование очередной строки результирующей выборки
ENDIF
ENDSCAN
ENDSCAN

Эта модель наглядно показывает, что каждая запись первой таблицы сопоставляется с каждой записью второй таблицы. Затем проверяется выполнение условия WHERE для каждой из полученных пар записей. Т.е. количество записей, которые попадут в результирующую выборку равно произведению количества записей с кодом 1 как в первой, так и во второй таблицах.



Объединение INNER JOIN

По сути, объединение через INNER JOIN ничем не отличается от перечисления таблиц в опции FROM. Просто несколько другой синтаксис.

SELECT FirstTab.*, SecondTab.* ;
FROM FirstTab ;
INNER JOIN SecondTab ON FirstTab.FirstTabId = SecondTab.SecondTabId

Если выполнить этот запрос, то его результат ничем не будет отличаться от результата запроса

SELECT FirstTab.*, SecondTab.* ;
FROM FirstTab, SecondTab ;
WHERE FirstTab.FirstTabId = SecondTab.SecondTabId

Результаты полностью совпадают. Более того, можно сформировать "Декартово объединение" и при использовании INNER JOIN

SELECT FirstTab.*, SecondTab.* ;
FROM FirstTab ;
INNER JOIN SecondTab ON .T.

Это значит, что объединение по INNER JOIN и простым перечислением таблиц в опции FROM - вполне взаимозаменяемы. Хотя, безусловно, более наглядный и понятный синтаксис объединения через INNER JOIN. Однако, если по каким-либо соображениям, использование INNER JOIN невозможно, то его можно смело заменять перечислением таблиц в опции FROM.



Внешнее объединение (LEFT, RIGHT, FULL JOIN)

Чтобы понять общий принцип работы внешних объединений рассмотрим пример с LEFT JOIN

SELECT FirstTab.*, SecondTab.* ;
FROM FirstTab ;
LEFT JOIN SecondTab ON FirstTab.FirstTabId = SecondTab.SecondTabId

Можно заметить, что результат выполнения этого запроса в той части, где есть соответствие FirstTab.FirstTabId = SecondTab.SecondTabId полностью совпадают с результатом запроса по INNER JOIN. Отличия касаются только тех записей, для которых не выполняется условие объединения.

Для наглядности, механизм работы такого запроса можно представить такой моделью:

SELECT FirstTab
SCAN
SELECT SecondTab
SCAN
IF (выполняется условие объединения ON)
IF (выполняется условие в WHERE)
* Формирование очередной строки результирующей выборки
ENDIF
ENDIF
ENDSCAN
IF (нет ни одной записи в SecondTab для которой выполняется
условие объединения ON с текущей записью FirstTab)
* Текущей записи FirstTab ставится в соответствие фиктивная
* строка SecondTab все поля которой имеют значение NULL
IF (выполняется условие в WHERE)
* Формирование очередной строки результирующей выборки
ENDIF
ENDIF
ENDSCAN

Правда, это то, как должно работать. Для версий младше Visual FoxPro 8 есть ошибка. Если в директиве WHERE присутствуют условия, налагаемые на поля таблицы SecondTab (любые), то фиктивные строки со значение NULL - не образуются. Это ошибка. Она была исправлена только в версии Visual FoxPro 8.

По такой же схеме работают все внешние объединения (LEFT, RIGHT, FULL). Разница только в том, какие таблицы "дополняются" фиктивными записями с NULL-значениями.

Приведенная схема позволяет понять, почему при внешнем объединении не имеет смысла задавать в директиве WHERE условия для "не полной" таблицы, кроме условий на значение NULL. Дело в том, что если задать для полей таблицы SecondTab дополнительное условие вроде

SELECT FirstTab.*, SecondTab.* ;
FROM FirstTab ;
LEFT JOIN SecondTab ON FirstTab.FirstTabId = SecondTab.SecondTabId ;
WHERE SecondTab.SecondTabId = 3

то результат такой выборки игнорирует записи со значениями NULL. Ведь значение полей этих записей не удовлетворяют условию WHERE. Это свойство значения NULL. Оно не равно никакому другому значению. В том числе и значению NULL.

Если, тем не менее, необходимо наложить дополнительное ограничение на присоединяемую таблицу, то это ограничение следует перенести в условие объединения.

SELECT FirstTab.*, SecondTab.* ;
FROM FirstTab ;
LEFT JOIN SecondTab ON FirstTab.FirstTabId = SecondTab.SecondTabId ;
AND SecondTab.SecondTabId = 3

Правда результат такого запроса будет может оказаться отличным от ожидаемого. В принципе, можно было бы сделать проверку на NULL. Примерно так:

SELECT FirstTab.*, SecondTab.* ;
FROM FirstTab ;
LEFT JOIN SecondTab ON FirstTab.FirstTabId = SecondTab.SecondTabId ;
WHERE IsNull(SecondTab.SecondTabId) OR SecondTab.SecondTabId = 3

Но такой синтаксис даст корректный результат только в версии Visual FoxPro 8. Для младших версий FoxPro такое условие эквивалентно отсутствию проверки на значение NULL. Т.е. результат будет далек от ожидаемого.

Если у Вас установлена версия Visual FoxPro 8 или старше, то Вы можете видеть в чем отличие результата такого запроса от запроса с дополнительным условием в директиве ON. По сути, если условие переносится в директиву ON, то это эквивалентно предварительному подзапросу по подчиненной таблице

Объединение по UNION
...
--------------------------------------------------------------------------------
Ratings: 0 negative/0 positive


Извините, только зарегистрированные пользователи могут оставлять сообщения в этом форуме.

On-line: 25 of63  (Гостей: 24)

© 2000-2024 Fox Club 
Яндекс.Метрика