Псевдонимы и рекурсивные объединения
На практике часто встречается ситуация, когда необходимо объединять таблицу саму с собой. Запрос, выполняющий такое объединение, называется рекурсивным. Например, если мы хотим вывести все пары студентов с одинаковым рейтингом, можно записать следующий запрос:
SELECT
first.cname,
second.cname,
first.rating
FROM
Students first,
Students second
WHERE
first.rating = second.rating;
Здесь в списке вывода SELECT указываются поля cname и rating таблицы first, и поле cname таблицы second. В разделе FROM указывается, что first и second – просто псевдонимы для таблицы Students. Для выполнения запроса сервер создаст две копии таблицы Students, одну с именем first, другую с именем second, выполнит запрос так, как будто это две разные таблицы, и уничтожит копии. Естествнно, сервер физически не создает копий таблиц, но с псевдонимами в запросе он работает так, будто это две разные таблицы.
Можно заметить, что вывод запроса будет повторять каждую пару дважды - сначала «Иванов - Петров», затем «Петров - Иванов». Кроме того, вывод содержит строки «Иванов - Иванов», «Петров - Петров». Это происходит потому, что сервер берет первую строку из первого псевдонима и сравнивает ее со всеми строками из второго псевдонима. Будут выбраны строки «Иванов - Иванов» и «Иванов - Петров». Затем он переходит к следующей строке и снова сравнивает ее со всеми строками из второго псевдонима, и так далее. Будут выбраны строки «Петров - Иванов» и «Петров - Петров».
Чтобы избежать дубликатов, надо наложить еще одно условие, налагающие отношение порядка на сравниваемые строки. Например, сравнивать имена.
SELECT
first.cname,
second.cname,
first.rating
FROM
Students first,
Students second
WHERE
first.rating = second.rating AND
first.cname < second.cname;
Вложенные подзапросы
Вложенные подзапросы так же служат для получения информации из нескольких таблиц. С их помощью можно выполнять рекурсивные запросы. Для чего существует два способа сделать одно и то же действие? Дело в том, что на практике встречаются ситуации, когда запрос выражается очень сложно через соединения, и легко – через вложенный подзапрос, и наоборот. На конкретном сервере БД запрос с использованием JOIN может выполняться очень долго, а с использованием подзапроса – быстро.
Пример запроса с вложенным подзапросом:
SELECT *
FROM Orders
WHERE snum =
( SELECT snum
FROM Salespeople
WHERE sname = 'Motika');
Так как подзапрос стоит после знака равенства, он должен возвращать только одно значение. В случае, если подзапрос вернет более чем одно значение, произойдет ошибка.
Обратите внимание, что при записи подзапроса допустима следующая форма:
<имя/константа> <оператор> <подзапрос>, а не <подзапрос> <оператор> <имя/константа> или < подзапрос > < оператор > < подзапрос >.
Во вложенных подзапросах можно использовать агрегатные функции:
SELECT *
FROM Orders
WHERE amt >
(SELECT AVG (amt)
FROM Orders
WHERE odate = 10/04/1990 );
Ограничение на вложенный подзапрос то же самое – он должен возвращать единственное значение. В случае, если подзапрос возвращает несколько записей, вместо операций сравнения нужно использовать IN:
SELECT *
FROM Orders
WHERE snum IN
( SELECT snum
FROM Salespeople
WHERE city = "LONDON" );
Данный запрос более просто записывается с использованием соединения:
SELECT onum, amt, odate, cnum, Orders.snum
FROM Orders, Salespeople
WHERE Orders.snum = Salespeople.snum
AND Salespeople.city = "London";
Допустимо использовать выражение, основанное на столбце, а не просто
сам столбец в предложении SELECT подзапроса:
SELECT *
FROM Customers
WHERE cnum =
( SELECT snum + 1000
FROM Salespeople
WHERE sname = Serres );
Также допустимы подзапросы в выражении HAVING:
SELECT rating, COUNT ( DISTINCT cnum )
FROM Customers
GROUP BY rating
HAVING rating >( SELECT AVG (rating)
FROM Customers
WHERE city = " San Jose');