Вище було показано, що в багатотабличному запиті можна поєднувати дані таблиць, однак цікаво й те, що та ж сама методика може використовуватись для об'єднання разом двох копій одиночної таблиці. Для об'єднання таблиці із собою можна зробити кожен рядок таблиці одночасно й комбінацією її із собою й комбінацією з кожним іншим рядком таблиці, а потім оцінити кожну комбінацію в термінах предиката. Це дозволяє легко створювати певні види зв'язків між різними елементами усередині одиночної таблиці. Наприклад, допускається зобразити об'єднання таблиці із собою, як об'єднання двох копій однієї й тієї ж таблиці, причому вона насправді не копіюється, але SQL виконує команду так, ніби це було зроблено.
Використання команди для об'єднання таблиці із собою аналогічно тому прийому, що використовується для об'єднання декількох таблиць. Коли поєднується таблиця із собою, всі повторювані імена стовпця заповнюються префіксами імені таблиці. Щоб посилатися до цих стовпців усередині запиту, необхідно мати два різних імена для цієї таблиці. Це можна зробити за допомогою визначення тимчасових імен, що називаються псевдонімами, які визначаються в команді FROM запиту. Синтаксис у цьому випадку наступний: після імені таблиці залишають пробіл, а потім повинен йти псевдонім для неї.
Наприклад, для пошуку студентів, що мають однаковий розмір стипендії, можна скористатися наступним запитом:
У даному прикладі SQL поводиться так, ніби вона з'єднувала дві різні таблиці, з назвами FIRST й SECOND, тобто псевдоніми дозволяють одній й тій же таблиці бути опрацьованою незалежно. Зверніть увагу на те, що псевдоніми можуть використовуватись в команді SELECT до їхнього оголошення в команді FROM, однак SQL буде спочатку допускати будь-які псевдоніми й може відхилити команду, якщо вони не будуть визначені далі в запиті. Крім того, необхідно пам'ятати, що псевдонім існує тільки тоді, коли команда виконується, а після завершення запиту псевдоніми, використовувані в ньому більше не мають ніякого значення.
Вивід останнього прикладу має два значення для кожної комбінації прізвищ, причому другий раз у зворотному порядку - це пов'язане з тим, що кожне значення показане перший раз у кожному псевдонімі й другий раз у предикаті, тобто поточне значення в першому псевдонімі спочатку вибирається в комбінації зі значенням у другому псевдонімі, а потім навпаки. Наприклад, у нашому випадку Поляков вибрався разом з Нагорним, а потім Нагорний вибрався разом з Поляковым і т.д. Крім того, кожен рядок був зрівняний сама із собою, наприклад Поляков з Поляковым.
Кращий спосіб уникнути цього полягає в накладанні порядку на два значення так, щоб один міг бути менше, ніж інший або передував йому за абеткою. Це робить предикат асиметричним щодо зв'язку, тому ті ж самі значення у зворотному порядку не будуть обрані знову. Отже, приклад можна модифікувати в такий спосіб:
Зокрема, Гриценко передує Котенко за абеткою, тому комбінація задовольняє обом умовам предиката й з'являється у висновку. Якщо та ж сама комбінація з'являється у зворотному порядку, тобто Котенко в псевдонімі першої таблиці рівняється із Гриценко в другій таблиці, то друга умова не виконується. З аналогічної причини у висновок не попадає порівняння із самим собою. Якщо ж виникла необхідність порівняння рядків з ними ж, то в запитах варто використати < = замість <
Таким чином, можна використати цю особливість SQL для перевірки певних видів помилок. Наприклад, якщо вважати, що навчальний предмет може вести тільки один викладач, те щораз у таблиці PREDMET необхідна перевірка на цю умову. При цьому щораз, коли код предмета з'являється в таблиці PREDMET, він повинен збігатися з відповідним номером викладача. Наступна команда буде визначати будь-які неузгодженості в цій області:
SELECT FIRST.PNUM, FIRST.TNUM,
SECOND.PNUM, SECOND.TNUM
FROM PREDMET FIRST, PREDMET SECOND
WHERE FIRST.PNUM - SECOND.PNUM
AND FIRST. TNUM <> SECOND. TNUM;
Виводу для даного приклада не буде, тому що даних, що задовольняють предикату в розглянутій таблиці немає. Логіка цього запиту досить проста: з таблиці буде вибиратися черговий рядок і запам'ятовуватися під першим псевдонімом. Після цього SQL почне перевіряти її в комбінації з кожним рядком таблиці під другим псевдонімом. Якщо комбінація рядків задовольняє предикату, то вона вибирається для висновку, тобто якщо буде знайдений навчальний предмет, у якого з'ясується розбіжність номера викладача в таблиці під першим і другим псевдонімом.
Об'єднання таблиці із собою - це найбільше що часто зустрічається ситуація, коли використаються псевдоніми, однак їх можна використати в будь-який час для створення альтернативних імен для таблиць у запиті, наприклад, у випадку, якщо таблиці мають дуже довгі й складні імена.
Більше того, допускається використати будь-яке число псевдонімів для однієї таблиці в запиті, хоча використання більше двох в одній пропозиції SELECT часто буде надмірністю. Наприклад, для призначення стипендії на наступний семестр необхідно переглянути всі варіанти комбінацій студентів з різними розмірами стипендії: 25.50, 17.00 й 0.00 у.е. Тоді такий запит буде виглядати в такий спосіб:
SELECT FIRST.SFAM, SECOND.SFAM, THIRD.SFAM
FROM STUDENTS FIRST, STUDENTS SECOND,
STUDENTS THIRD
WHERE FIRST.STIP = 25.50
AND SECOND.STIP = 17.00
AND THIRD.STIP = 0.00;
Результат виконання для цього запиту записаний нижче:
Як видно з результату, цей запит знаходить всі комбінації студентів із трьома різними розмірами стипендії, тому перший стовпець висновку складається зі студентів зі стипендією 25.50, другий з 17.00 й останній - з 0.00, які повторюються у всіх можливих комбінаціях. Цікаво, що такий запит не може бути виконаний з GROUP BY або ORDER BY, оскільки вони порівнюють значення тільки в одному стовпці висновку.
У команді SELECT допускається не використати кожен псевдонім або таблицю, які згадувалися в пропозиції FROM запиту, тобто псевдонім або таблиця використаються тільки для створення предиката. Розглянемо чисто ілюстративний приклад наступного запиту:
Фактично тут дані про студентів, що мають стипендію 25.50, повторюються стільки разів, скільки існує їхніх сполучень зі студентами, що не одержують стипендію (тобто для яких STIP = 0.00).
В SQL передбачається створення об'єднання, що включає й різні таблиці, і псевдоніми одиночної таблиці. Наступний запит поєднує таблицю з даними про успішність для того, щоб знайти навчальні предмети, які вже здані більш ніж одним студентом, і таблицю навчальних предметів:
У виводі маємо пари номерів студентів, що здали той або інший предмет, що визначається за назвою з таблиці навчальних предметів.
Як уже було сказано, операція об'єднання в SQL з'єднує інформацію із двох таблиць, формуючи пари зв'язаних рядків з них. Об'єднану таблицю утворять пари тих рядків з різних таблиць, в яких у зв'язаних стовпцях знаходяться однакові значення. Якщо рядок однієї з таблиць не має пари, то об'єднання може привести до несподіваних результатів.
Припустимо, що в таблиці TEACHERS з'явився запис {NULL, Федченко, Світлана, Геннадіївна, 01/09/1999}, у якій табельний номер ще не відомий. Несподівані результати можуть вийти при використанні наступних запитів:
Здавалося б, що ці два запити повинні давати однакову кількість рядків, але результати першого запиту нараховують шість рядків, а другого - тільки п'ять. Це пов'язане з тим, що викладач Федченко в даний момент ще не одержала номер й відповідний запис має значення NULL у поле TNUM, отже, це значення не збігається з жодним ідентифікатором навчального предмета в таблиці PREDMET. Виходить, виводу цього рядка в другому запиті не буде - він просто зникає з об'єднання. Таким чином, стандартне SQL-об'єднання може привести до втрати інформації, якщо поєднувані таблиці містять незв'язані рядки.
Другий запит можна модифікувати в такий спосіб:
SELECT TEACHERS.TFAM, PREDMET. PNAME
FROM TEACHERS, PREDMET
WHERE TEACHERS . TNUM *= PREDMET. TNUM;
і його вивід буде більше повний:
TFAM PNAME
-----------------------
Никулина Фізика
Костыркин Хімія
Казанко Математика
Позднякова Економіка
Загарийчук Філософія
Федченко NULL
Такі результати запиту виходять за допомогою іншої операції об'єднання, називаної зовнішнім об'єднанням таблиць, що у команді WHERE позначається символом *=. Зовнішнє об'єднання є розширенням стандартного об'єднання, описаного вище й іноді називаного внутрішнім об'єднанням таблиць. Майте на увазі, що в стандарті SQL1 дане визначення тільки внутрішнього об'єднання, а поняття зовнішнього об'єднання в ньому відсутній.
Розглянемо повне зовнішнє об'єднання таблиць на прикладі наступного запиту:
Цей запит формує інформацію про студентів і коди навчальних предметів, які ними здані. Це внутрішнє об'єднання дає п'ять рядків виводу, показуючи відповідні пари студент - код предмета. Для студента Котенко ніякого висновку ні, оскільки він ніяких дисциплін не здавав.
Тепер припустимо, що необхідно вивести ту ж інформацію, але з урахуванням студентів, яким не відповідає жоден навчальний предмет. Такий запит, побудований за правилами повного зовнішнього об'єднання, буде виглядати так:
У цей вивід потрапили рядка зі значеннями NULL, тобто запис для студента Котенко був як би прив'язан до неіснуючого рядка таблиці USP, що містить тільки значення NULL, і доданий в результат запиту. Як видно із цього прикладу, зовнішнє об'єднання є таким що зберігає інформацію й кожен рядок таблиці - операнда представлена у висновку результатів запиту. Отже, повне зовнішнє об'єднання таблиць виходить із внутрішнього об'єднання двох таблиць звичайним способом, при цьому кожен запис першої таблиці, що не має зв'язку з жодною рядком у другій таблиці, доповнюється в результатах запиту значеннями NULL. У той же час, кожен рядок другої таблиці, що не має зв'язку з жодною рядком першої таблиці, доповнюється в результатах запиту значеннями NULL.
Існують ще так звані ліве й праве зовнішні об'єднання. Якщо повне зовнішнє об'єднання симетрично стосовно обох таблицям-операндам, то ці два типи зовнішніх об'єднань не симетричні щодо двох таблиць.
Ліве зовнішнє об'єднання двох таблиць записується в команді WHERE у вигляді *= і отримується в результаті виконання внутрішнього об'єднання таблиць. При цьому відбуваються дії, аналогічні повному об'єднанню, однак без заміни NULL значеннями інформації, взятої із другої таблиці. Приклад лівого зовнішнього об'єднання фактично нами вже був розглянутий вище в прикладі для викладачів і предметів.
Праве зовнішнє об'єднання двох таблиць записується в команді WHERE у вигляді =* і отримується із звичайного внутрішнього об'єднання таблиць у послідовності реалізації повного об'єднання, при цьому заміна на NULL значення в першій таблиці не відбувається.
На практиці лівого й праве зовнішні об'єднання більше корисні, чим повне зовнішнє об'єднання, особливо при зв'язку таблиць через ключі. Система запису зовнішнього об'єднання дозволяє використати не тільки знак рівності, але й інші знаки відносин. Наприклад, допустимо наступний запит, що представляє, по суті, ліве зовнішнє об'єднання:
SELECT STUDENTS.SFAM, STUDENTS.SNUM,
USP.PNUM, USP.SNUM
FROM STUDENTS, USP
WHERE STUDENTS.SNUM *> USP.SNUM;
Результат цього запиту, наведений не повністю через його значні розміри, буде наступний:
Даний запит виводить інформацію про студентів і про здані навчальні предмети, при цьому дані про предмети вибираються для студентів номер яких менше, ніж поточний. У випадку, якщо таких не виявляється, SQL формує рядок з першої таблиці, заповнюючи NULL значеннями дані, які повинні були бути отримані із другої.
Хоча система запису зовнішнього об'єднання досить зручна, вона має свої недоліки. Справа в тому, що проблеми із записом зовнішніх об'єднань виникають при розширенні об'єднання до трьох або більше таблиць. Наприклад, при записі
TABLE1 *=* TABLE2 *=* TABLE3
фактично виконується зовнішнє об'єднання
(TABLE1 *=* TABLE2) *=* TABLE3
що, загалом кажучи, призведе до різних результатів.
Для розробників стандарту SQL2 зовнішні об'єднання були серйозною проблемою, тому що зовнішні об'єднання є часом єдиним способом надання результатів ряду вкрай необхідних запитів. Тому в стандарті SQL2 був визначений зовсім новий метод підтримки зовнішніх об'єднань: у специфікації стандарту SQL2 підтримка зовнішніх об'єднань здійснюється в команді FROM зі спеціальним синтаксисом, що дозволяє користувачеві точно визначити, як вихідні таблиці повинні бути об'єднані в запиті.
Наприклад, уже розглянутий нами запит внутрішнього об'єднання в стандарті SQL1 виглядає так:
SELECT STUDENTS.SFAM, STUDENTS.SNUM,
USP.PNUM-, USP.SNUM
FROM STUDENTS, USP
WHERE STUDENTS.SNUM = USP.SNUM;
а в стандарті SQL2 - так (хоча використання першого варіанта допускається):
SELECT STUDENTS.SFAM, STUDENTS.SNUM,
USP.PNUM, USP.SNUM
FROM STUDENTS INNER JOIN USP
ON STUDENTS . SNUM = USP.SNUM;
Зверніть увагу на те, що в останньому випадку дві поєднувані таблиці з'єднуються явно за допомогою операції JOIN, а умова пошуку, що описує об'єднання, перебуває тепер у команді ON усередині команди FROM. В умові пошуку, що розміщена за ключовим словом ON, можуть бути задані будь-які критерії порівняння рядків двох поєднуваних таблиць, у тому числі з використанням булевих операторів.
Нарешті, приведемо приклад повного зовнішнього об'єднання (аналогічний запит був уже розглянутий):
Таким чином, у цих двотабличних об'єднаннях все вмістиме команди WHERE просто перейшло в команду ON, отже, нічого принципово нового ON не додає в мову SQL. Однак така структура дозволяє більш точно визначити умову об'єднання.