Шрифт:
Интервал:
Закладка:
Рис. 22.7. Результат простого реентерабельного соединения
Подзапросы
Подзапрос - это специальный вид выражения, которое фактически является запросом SELECT к другой таблице, включенным в спецификацию основного запроса. Выражение включенного запроса называют подзапросом, вложенным запросом, встроенным запросом, а иногда (ошибочно) Sub-SELECT.
В Firebird версии 1.5 и выше выражения подзапроса используются тремя способами:
* для получения одной строки или многострочного входного набора для операции INSERT (его синтаксис описан в разд. "Оператор INSERT" главы 20);
* для задания в процессе выполнения выходного столбца только для чтения для запроса SELECT;
* для получения значений или условий для предикатов поиска.
В версиях Firebird после 1.5 появляется четвертый вариант подзапросов- виртуальная таблица, который кратко обсуждается в главе 24.
Задание столбца при использовании подзапроса
Выходной столбец времени выполнения может быть задан запросом одного столбца из другой таблицы. Выходному столбцу должен быть назначен новый идентификатор, который для завершенности синтаксиса может быть отмечен необязательным ключевым словом AS (см. разд. "Наследуемые поля и алиасы столбцов"главы 21).
Вложенный запрос всегда должен иметь условие в предложении WHERE для ограничения вывода одним столбцом из одной строки (называется скалярным запросом); иначе вы увидите подобное сообщение об ошибке: "Multiple rows in singleton select" (Запрос, который должен вернуть одну строку, вернул множество строк).
Следующий запрос использует подзапрос для получения выходного столбца.
SELECT
LAST_NAME,
FIRST_NAME,
ADDRESS1,
ADDRESS2,
POSTCODE,
(SELECT START_TIME FROM ROUTES
WHERE POSTCODE = '2261' AND DOW = 'MONDAY') AS START_TIME
FROM MEMBERSHIP
WHERE POSTCODE = '2261';
Этот подзапрос указывает одно значение POSTCODE для получения значения поля START_TIME транспортного маршрута (таблица ROUTES). Столбцы POSTCODE в главном запросе и в подзапросе могут быть заменены параметрами. Чтобы сделать запрос более общим и более полезным, мы можем использовать коррелированные подзапросы.
Коррелированные подзапросы
Когда элемент данных, полученный из вложенного подзапроса, должен быть выбран в контексте связи значения с текущей строкой главного запроса, возможно использование коррелированного подзапроса. Firebird требует явно указанных идентификаторов в коррелированных подзапросах.
В следующем примере связываемые столбцы в главном запросе и в подзапросе являются коррелированными; здесь использованы алиасы таблиц для устранения какой- либо неясности:
SELECT
M.LAST_NAME,
M.FIRST_NAME,
M.ADDRESS1,
M.ADDRESS2,
M.POSTCODE,
(SELECT R.START_TIME FROM ROUTES R
WHERE R.POSTCODE = M.POSTCODE AND M.DOW = 'MONDAY') AS START_TIME
FROM MEMBERSHIP M
WHERE . . .
Этот запрос возвращает одну строку для каждого выбранного элемента, независимо от того, существует ли соответствие между полями POSTCODE обеих таблиц. Если нет соответствия, то поле START_TIME будет иметь значение NULL.
Подзапрос или соединение?
Запрос из предыдущего примера может быть составлен с использованием левого соединения вместо подзапроса:
SELECT
M.LAST_NAME,
M.FIRST_NAME,
M.ADDRESS1,
M.ADDRESS2,
M.POSTCODE,
R. START_TIME
FROM MEMBERSHIP M LEFT JOIN ROUTES R
ON R.POSTCODE = M.POSTCODE
WHERE M.DOW = 'MONDAY';
Относительная стоимость этого запроса и предыдущего, использующего подзапрос, практически одинакова. Хотя каждый может достигать результата разными путями, оба требуют полного сканирования полученного потока при преобразовании исследуемого потока.
Разница в стоимости может стать значительной, если коррелированный запрос будет использован вместо внутреннего соединения:
SELECT
M.LAST_NAME,
M.FIRST_NAME, M.ADDRESS1,
M.ADDRESS2,
M.POSTCODE,
R.START _Т1МЕ
FROM MEMBERSHIP M
JOIN ROUTES R
ON R.POSTCODE = M.POSTCODE
WHERE M.DOW = 'MONDAY';
Внутреннее соединение не требует просмотра каждой строки исследуемого потока, потому что оно отбрасывает любую строку исследуемого потока (ROUTES), которая не соответствует условию поиска. В противоположность этому контекст коррелированного подзапроса меняется с каждой строкой, и ни при каких условиях не исключает из процесса сканирования несовпадающее поле POSTCODE. Следовательно, коррелированный подзапрос должен быть выполнен для каждой строки в наборе.
Если выходной набор данных потенциально большой, посмотрите, насколько важна необходимость выполнить включающий поиск. Хорошо продуманный коррелированный запрос полезен для малых наборов. Не существует никаких численных оценок для выбора одного или другого. Как обычно, тестирование в реальных условиях - единственно надежный способ решить, что лучше работает для ваших конкретных потребностей.
Когда не надо использовать подзапросыПодход, основанный на подзапросах, становится странным, когда вам нужно получить более одного поля из той же таблицы. Подзапрос может вернуть одно и только одно поле. Для получения нескольких полей требуется отдельный коррелированный подзапрос и его алиас для каждого выбираемого поля. Если левое соединение способно выполнить эти условия, то его всегда следует использовать.
Поиск с использованием подзапроса
Использование существующих предикатов в подзапросах- особенно предиката EXISTS О- обсуждалось в главе 21. Подзапросы могут также быть использованы другими способами в предикатах условий поиска в предложениях WHERE и группирования.
Реентерабельные подзапросы
Запрос может использовать реентерабельный подзапрос для задания условия поиска в той же таблице. Использование алиасов таблиц является обязательным. В следующем примере оператор выполняет подзапрос для поиска в главной таблице даты самой последней транзакции для задания условия поиска главного запроса.
SELECT
AL.COL1,
A1.COL2,
A1. TRANSACTION_DATE
FROM ATABLE A1
WHERE AL.TRANSACTION_DATE = (SELECT MAX(A2.TRANSACTION_DATE) FROM ATABLE A2);
Добавление данных с использованием подзапроса с соединениями
В главе 20 мы рассматривали встроенный метод выборки для передачи данных в оператор INSERT, например,
INSERT INTO ATABLE (
COLUMN2, COLUMN3, COLUMN4)
SELECT BCOLUMN, CCOLUMN, DCOLUMN
FROM BTABLE WHERE
Этот метод не ограничен однопоточным запросом. Ваш входной подзапрос может быть соединенным. Эта возможность очень полезна, когда вам нужно экспортировать ненормализованные данные во внешнюю таблицу для использования в другом приложении, таком как электронная таблица, программа работы с базой данных или программа бухгалтерского учета. Например:
INSERT INTO EXTABLE (
TRANDATE, INVOICE_NUMBER, EXT_CUST_CODE, VALUE)
SELECT
INV. TRANSACTION_DATE,
INV. INVOICE_NUMBER,
CUS.EXT_CUS_CODE,
SUM (INVD. PRICE * INVD.NO_ITEMS)
FROM INVOICE INV
JOIN CUSTOMER CUS
ON INV.CUST_ID = CUS.CUST_ID
JOIN INVOICE_DETAIL INVD
ON INVD.INVOICE_ID = INV.INVOICE_ID
WHERE ...
GROUP BY . . .;
Оператор UNION
Оператор UNION может быть использован для объединения результатов двух или более операторов SELECT и создания единого набора только для чтения, состоящего из строк, полученных из разных таблиц или из разных наборов, запрошенных из той же таблицы. Множество запросов объединяются, каждое подмножество спецификаций связывается со следующим с помощью ключевого слова UNION. Данные должны быть выбраны так, а запрос сконструирован таким образом, чтобы все участвующие входные наборы были совместимы для объединения.
Наборы, совместимые для объединения
Для каждой операции SELECT, создающей входной поток для UNION, спецификация должна содержать список столбцов, одинаковый для всех других операций (количество и порядок столбцов) с соответствующими типами данных. Предположим, мы имеем следующие спецификации двух таблиц:
CREATE TABLE CURRENT_TITLES (
ID INTEGER NOT NULL,
TITLE VARCHAR(60) NOT NULL,
AUTHOR_LAST_NAME VARCHAR (40),
AUTHOR_FIRST_NAMES VARCHAR(60),
EDITION VARCHAR(10) ,
PUBLICATION_DATE DATE,
PUBLISHER_ID INTEGER,
ISBN VARCHAR(15) ,
LIST_PRICE DECIMAL(9,2));
/**/
CREATE TABLE PERIODICALS (
PID INTEGER NOT NULL,
PTITLE VARCHAR(60) NOT NULL,
EDITOR_LAST_NAME VARCHAR(40),
EDITOR_FIRST_NAMES VARCHAR (60),
ISSUE_NUMBER VARCHAR (10),
PUBLICATION_DATE DATE,
PUBLISHER_ID INTEGER,
LIST_PRICE DECIMAL (9, 2) ) ;
Эти таблицы совместимы для объединения, потому что мы можем создать запрос для получения наборов с соответствующей "геометрией":
SELECT ID,
TITLE,
AUTHOR_LAST_NAME,
AUTHOR_FIRST_NAMES,
EDITION VARCHAR (10),
LIST_PRICE
FROM CURRENT_TITLES
UNION SELECT
ID,
TITLE,
EDITOR_LAST_NAME,
EDITOR_FIRST_NAMES,
ISSUE_NUMBER,
LIST_PRICE
FROM PERIODICALS;
UNION, содержащий SELECT * FROM таблица, не будет работать, поскольку структуры таблиц различны - вторая таблица не содержит колонки ISBN.
Использование столбцов времени выполнения в объединениях
Идентификаторы столбцов выходного набора данных задаются в первой спецификации SELECT. Если вы хотите использовать альтернативные имена столбцов, алиасы столбцов могут быть использованы в выходном списке первой спецификации SELECT. Дополнительно, если необходимо, поля, полученные из констант или переменных, могут быть включены в предложение SELECT каждого объединяемого потока. Следующий запрос содержит наиболее удобный список публикаций из наших двух таблиц:
- Delphi. Учимся на примерах - Сергей Парижский - Программирование
- Сделай видеоигру один и не свихнись - Слава Грис - Программирование / Руководства
- Психбольница в руках пациентов - Алан Купер - Программирование