Справочник от Автор24
Поделись лекцией за скидку на Автор24

Запросы со связанными подзапросами

  • 👀 296 просмотров
  • 📌 264 загрузки
Выбери формат для чтения
Загружаем конспект в формате pdf
Это займет всего пару минут! А пока ты можешь прочитать работу в формате Word 👇
Конспект лекции по дисциплине «Запросы со связанными подзапросами» pdf
ЛЕКЦИЯ № 10 3.3.2.3. Запросы со связанными подзапросами..1 3.3.2.4. Операторы сравнения с ANY, ALL и SOME 10 3.3.2.5. Предикат SINGULAR............................14 3.3.2.6. Предикат EXISTS...................................16 3.3.2.3. Запросы со связанными подзапросами Вложенный запрос может ссылаться на таблицу, указанную во внешнем (основном) запросе (независимо от его уровня вложенности). Такой вложенный запрос называется зависимым, соотнесенным, коррелированным или связанным изза того, что результат его выполнения зависит от значений, определенных в основном запросе. При этом вложенный запрос выполняется неоднократно, по разу для каждой строки таблицы основного (внешнего) запроса, а не раз, как в случае независимого вложенного запроса. Строка внешнего запроса, для которой внутренний запрос каждый раз будет выполнен, называется текущей строкойкандидатом. Процедура оценки, выполняемой при использовании связанного вложенного запроса, состоит из нескольких шагов. 1. Выбрать очередную строку из таблицы, именованной во внешнем запросе. Это будет текущая строка-кандидат. 2. Сохранить значения из этой строки-кандидата в псевдониме, который задан в секции FROM внешнего запроса. 3. Выполнить вложенный запрос. Везде, где псевдоним, данный для внешнего запроса, найден, использовать значение для текущей строки-кандидата. Использование значения из строки-кандидата внешнего запроса во вложенном запросе называется внешней ссылкой. 4. Если связанный подзапрос используется в секции WHERE или HAVING, то оценить условие поиска внешнего запроса на основе результатов вложенного запроса, выполняемого на шаге 3. Результат вложенного запроса определяет, выбирается ли строка-кандидат для вывода. Если связанный подзапрос используется в секции SELECT, то выводятся столбцы, указанные в списке возвращаемых элементов основного запроса, и результат выполнения вложенного запроса. 5. Повторить процедуру для следующей строки-кандидата основной (внешней) таблицы и т. д., пока все ее строки не будут проверены. Общая структура связанного подзапроса такая же, как и независимого подзапроса (используются те же самые конструкции, и не меняется порядок их следования), однако в секции WHERE или HAVING связанного подзапроса содержится ссылка на столбец таблицы внешнего запроса, и алгоритм выполнения связанного подзапроса совершенно другой. Так как вложенный запрос содержит ссылки на таблицу (таблицы) основного запроса, то вероятность неоднозначных ссылок на имена столбцов достаточно высока. Поэтому, если во вложенном запросе присутствует неполное имя столбца, 1 то сервер БД должен определить, относится ли оно к таблице, указанной в секции FROM самого вложенного запроса, или к таблице, указанной в секции FROM внешнего запроса, содержащего данный вложенный запрос. Возможные неоднозначности при определении столбца устраняются использованием полного имени столбца. Двусмысленность при определении таблицы, используемой для конкретного отбора строк, устраняется с помощью псевдонимов таблиц, указываемых во внешнем и внутреннем запросах. Связанные подзапросы в секции SELECT. Чаще всего в секции SELECT применяются вложенные связанные, а не независимые запросы. Запрос со связанным подзапросом, возвращающий ФИО абонентов и названия улиц, на которых они проживают, имеет вид: SELECT A.Fio, (SELECT S.StreetNM FROM Street S WHERE S.StreetCD = A.StreetCD) AS StreetNM FROM Abonent A; Результат выполнения запроса представлен на рис. 3.121. FIO STREETNM Аксенов С.А. ВОЙКОВ ПЕРЕУЛОК Мищенко Е.В. ВОЙКОВ ПЕРЕУЛОК Конюхов В.С. ВОЙКОВ ПЕРЕУЛОК Тулупова М.И. КУТУЗОВА УЛИЦА Свирина З.А. КУТУЗОВА УЛИЦА Стародубцев Е.В. ТАТАРСКАЯ УЛИЦА Шмаков С.В. ТАТАРСКАЯ УЛИЦА Маркова В.П. ТАТАРСКАЯ УЛИЦА Денисова Е.К. ТАТАРСКАЯ УЛИЦА Лукашина Р.М. МОСКОВСКОЕ ШОССЕ Шубина Т. П. МОСКОВСКОЕ ШОССЕ Тимошкина Н.Г. МОСКОВСКАЯ УЛИЦА Рис. 3.121. Результат выполнения связанного подзапроса в секции SELECT В соответствии с алгоритмом, описанным выше, данный запрос работает в такой последовательности. 1. Внешний запрос выбирает из таблицы Abonent строку с данными об абоненте, проживающем на улице с кодом, равным 3 (первая строка). 2. Сохраняет эту строку как текущую строку-кандидат под псевдонимом A. 3. Выполняет вложенный запрос, просматривающий всю таблицу Street, чтобы найти строку, где значение столбца S.StreetCD такое же, как значение A.StreetCD (3). Из найденной строки таблицы Street извлекается значение столбца StreetNM. 4. Для вывода выбираются значение столбца A.Fio из основного запроса (Аксенов С.А.) и найденное значение столбца S.StreetNM из вложенного запроса (ВОЙКОВ ПЕРЕУЛОК). 5. Повторяются пп.1–4, пока каждая строка таблицы Abonent не будет проверена. 2 Следует отметить, что эту же задачу можно решить с использованием неявного или явного соединения (INNER JOIN) таблиц Abonent и Street. Пусть необходимо вывести для каждого абонента номер его лицевого счета, ФИО и общее количество поданных им заявок (рис. 3.122). Для этого может использоваться запрос SELECT A.AccountCD, A.Fio, (SELECT COUNT (*) FROM Request R WHERE A.AccountCD = R.AccountCD) AS Request_Count FROM Abonent A; ACCOUNTCD FIO REQUEST_COUNT 005488 Аксенов С.А. 3 115705 Мищенко Е.В. 5 015527 Конюхов В.С. 1 443690 Тулупова М.И. 136159 Свирина З.А. 1 443069 Стародубцев Е.В. 2 136160 Шмаков С.В. 2 126112 Маркова В.П. 136169 Денисова Е.К. 2 080613 Лукашина Р.М. 1 080047 Шубина Т. П. 2 080270 Тимошкина Н.Г. 2 Рис. 3.122. Результат соотнесенного вложенного запроса Запрос работает таким образом. 1. Внешний запрос из таблицы Abonent выбирает строку c данными об абоненте, имеющем номер лицевого счета '005488' (первая строка). 2. Сохраняет эту строку как текущую строку-кандидат под псевдонимом A. 3. Выполняется вложенный запрос, просматривающий таблицу Request, чтобы найти все строки, где значение столбца R.AccountCD такое же, как значение A.AccountCD (005488). С помощью агрегатной функции COUNT подсчитывается общее количество таких строк (3). 4. Для вывода выбираются значения столбцов A.AccountCD и A.Fio из основного запроса ('005488', 'Аксенов С.А.') и найденное вложенным запросом количество связанных строк в таблице Request (3). 5. Повторяются пп.1–4, пока каждая строка таблицы Abonent не будет просмотрена. Если во внешнем запросе используется секция GROUP BY, то выражения, указанные в нем, можно использовать внутри подзапросов. С помощью следующего запроса можно получить общие суммы начислений и оплат по услуге с кодом 2 (Электроснабжение) по каждому абоненту, который подавал ремонтные заявки: SELECT R.AccountCD, 3 (SELECT Sum (NachislSum) FROM NachislSumma N WHERE N.AccountCD = R.AccountCD AND N.ServiceCD = 2) AS Nachisl, (SELECT Sum (PaySum) FROM PaySumma P WHERE P.AccountCD = R.AccountCD AND P.ServiceCD = 2) AS Pay FROM Request R GROUP BY R.AccountCD; Здесь в подзапросах вычисляются суммы всех начислений и оплат по услуге с кодом 2 для каждого абонента, отобранного внешним запросом из таблицы Request. Затем возвращенные подзапросами значения выводятся по каждому абоненту в результирующих столбцах Nachisl и Pay. Результат выполнения запроса представлен на рис. 3.123. ACCOUNTCD NACHISL PAY 005488 160,70 154,70 015527 NULL NULL 080047 160,00 160,00 080270 46,00 30,00 080613 56,00 58,50 115705 598,70 598,70 136159 NULL NULL 136160 76,00 76,00 136169 78,70 78,70 443069 118,50 118,50 Рис. 3.123. Результат соотнесенного вложенного запроса Следует отметить, что можно допустить ошибку, если попытаться получить аналогичные данные, используя запрос с соединением таблиц, например, такого вида: SELECT R.AccountCD, Sum (N.NachislSum) AS Nachisl, Sum (P.PaySum) AS Pay FROM Request R LEFT JOIN NachislSumma N ON N.AccountCD = R.AccountCD AND N.ServiceCD = 2 LEFT JOIN PaySumma P ON P.AccountCD = R.AccountCD AND P.ServiceCD = 2 GROUP BY R.AccountCD; Этот запрос выдаст некорректные данные, так как в результате соединения строки с начислениями и оплатами будут дублироваться. Корректные результаты будет выдавать запрос, в котором начисления и оплаты вычисляются отдельно во вложенных связанных подзапросах в секции SELECT (аналогичный запросу, представленному ранее). В запросе можно использовать независимые и связанные подзапросы. Если в предыдущем примере код услуги неизвестен, то его можно определить, используя независимый подзапрос, например: SELECT R.AccountCD, 4 (SELECT Sum (NachislSum) FROM NachislSumma N WHERE N.AccountCD = R.AccountCD AND N.ServiceCD = (SELECT ServiceCD FROM Services WHERE Servicenm = 'Электроснабжение')) AS Nachisl, (SELECT Sum (PaySum) FROM PaySumma P WHERE P.AccountCD = R.AccountCD AND P.ServiceCD = (SELECT ServiceCD FROM Services WHERE Servicenm = 'Электроснабжение')) AS Pay FROM Request R GROUP BY R.AccountCD; Если независимый подзапрос вынести в секцию WITH, то получится более короткий запрос: WITH Service AS (SELECT ServiceCD FROM Services WHERE Servicenm = 'Электроснабжение') SELECT R.AccountCD, (SELECT Sum (NachislSum) FROM NachislSumma N WHERE N.AccountCD = R.AccountCD AND N.ServiceCD = (SELECT ServiceCD FROM Service)) AS Nachisl, (SELECT Sum (PaySum) FROM PaySumma P WHERE P.AccountCD = R.AccountCD AND P.ServiceCD = (SELECT ServiceCD FROM Service)) AS Pay FROM Request R GROUP BY R.AccountCD; Следующий пример демонстрирует добавление соединений в запрос Firebird: SELECT A.Fio, (SELECT S.StreetNM FROM Street S WHERE S.StreetCD= A.StreetCD) ||', д.'||HouseNo||', кв.'||FlatNo AS Adress, R.IncomingDate FROM Abonent A, Request R WHERE R.AccountCD=A.AccountCD ORDER BY 1; В этом запросе со связанным подзапросом соединяются таблицы Abonent и Street для получения ФИО абонентов и их адреса, а также выполняется внешнее соединение с таблицей Request, чтобы возвратить даты подачи ими ремонтных заявок. Связанные подзапросы в секциях WHERE и HAVING. При использовании связанного вложенного запроса в условиях поиска секций WHERE и HAVING он может представлять собой <скалярный_подзапрос>, <подзапрос_столбца> или <табличный_подзапрос>, как и для независимых вложенных запросов. Поскольку запрос связанный, то внутренний запрос выполняется отдельно для каждой строки внешнего запроса (текущая строка-кандидат). Рассмотрим примеры, в которых используются <скалярный_подзапрос> и <подзапрос_столбца>. Подзапросы, представляющие собой <табличный_подзапрос>, будут рассмотрены позднее при изучении предикатов EXISTS и SINGULAR. Например, чтобы вывести все данные об абонентах, которые 17 декабря 2011 г. подали заявки на ремонт газового оборудования (рис. 3.124), можно использовать связанный вложенный запрос: 5 SELECT * FROM Abonent Out WHERE '17.12.2011' IN (SELECT IncomingDate FROM Request Inn WHERE Out.AccountCD = Inn.AccountCD); ACCOUNTCD STREETCD HOUSENO FLATNO FIO PHONE 005488 3 4 1 Аксенов 556893 С.А. 080270 6 35 6 Тимошкина 321002 Н.Г. Рис. 3.124. Результат связанного вложенного запроса в секции WHERE В этом примере Out и Inn – это соответственно псевдонимы таблиц Abonent и Request (могут задаваться произвольно). Так как значение в столбце AccountCD внешнего запроса меняется (при переборе строк), внутренний запрос должен выполняться отдельно для каждой строки внешнего запроса. В этом примере SQL осуществляет следующую процедуру. 1. Выбирает строку c данными об абоненте, имеющем номер лицевого счета '005488' (первая строка), из таблицы Abonent. 2. Сохраняет эту строку как текущую строку-кандидат под псевдонимом Out. 3. Выполняет вложенный запрос, просматривающий всю таблицу Request, чтобы найти строки, где значение столбца Inn.AccountCD – такое же, как значение Out.AccountCD (005488). Затем из каждой такой строки таблицы Request извлекается значение столбца IncomingDate. В результате вложенный запрос, представляющий собой <подзапрос_столбца>, формирует набор значений столбца IncomingDate для текущей строки-кандидата. 4. После получения набора всех значений столбца IncomingDate для AccountCD='005488' анализируется условие поиска основного запроса, чтобы проверить, имеется ли значение 17 декабря 2011 в наборе всех значений столбца IncomingDate. Если это так (а это так), то выбирается строка с номером лицевого счета '005488' для вывода ее из основного запроса. 5. Повторяются пп.1–4 (для второй строки с номером лицевого счета '015527' и т. д.), пока каждая строка таблицы Abonent не будет проверена. Ту же самую задачу в Firebird, например, можно решить, используя естественное соединение таблиц Abonent и Request: SELECT AccountCD, StreetCD, HouseNo, FlatNo, Fio, Phone FROM Abonent NATURAL JOIN Request Inn WHERE Inn.IncomingDate = '17.12.2011'; Результат выполнения будет совпадать с результатом, представленным на рис. 3.124. Однако следует обратить внимание на наличие существенных различий между соединением таблиц и вложенными соотнесенными запросами. Дело в том, что запросы с использованием соединения таблиц формируются СУБД как строки из декартова произведения таблиц, перечисленных в секции FROM. В случае же с вложенным соотнесенным запросом строки из произведения таблиц не вычисляются благодаря использованию механизма строки-кандидата. Вывод в связанном вложенном запросе формируется в секции SELECT внешнего запроса, в то время 6 как соединения могут выводить строки из обеих соединяемых таблиц (при указании символа * в секции SELECT). Но даже если столбцы для вывода при соединении таблиц указаны явно (см. предыдущий пример), то сначала все равно формируется декартово произведение. Каждый SQL-запрос можно оценить с точки зрения используемых ресурсов сервера БД. На практике большинство СУБД подзапросы выполняют более эффективно. Тем не менее, при проектировании комплекса программ с критичными требованиями по быстродействию разработчик должен проанализировать план выполнения SQL-запроса для конкретной СУБД. Тестирование в реальных условиях – единственный надежный способ решить, что лучше работает для конкретных потребностей. Рассмотрим пример сравнения значения, возвращаемого вложенным запросом, с константой. Вывести информацию об исполнителях, назначенных на выполнение 4-х и более ремонтных заявок (рис. 3.125), можно с помощью запроса EXECUTORCD FIO 1 Стародубцев Е.М. 3 Шубин В.Г. 4 Шлюков М.К. Рис. 3.125. Результат сравнения вложенного запроса с константой SELECT * FROM Executor E WHERE 4<= (SELECT COUNT(R.RequestCD) FROM Request R WHERE E.ExecutorCD = R.ExecutorCD); В данном примере связанный подзапрос в условии поиска представляет собой <скалярный_подзапрос>. Он возвращает одно-единственное значение (количество ремонтных заявок) для текущей строки-кандидата, выбранной из таблицы Executor. Если это значение больше или равно 4, то текущая строка-кандидат выбирается для вывода из основного запроса. Эта процедура повторяется, пока каждая строка таблицы Executor не будет проверена. В SQL имеется возможность использовать соотнесенный вложенный запрос, основанный на той же самой таблице, что и основной запрос. Это позволяет использовать соотнесенные вложенные запросы для извлечения сложных форм производной информации. Например, в Firebird вывести для каждого абонента размеры начислений, превышающие среднее значение всех его начислений, можно с помощью следующего запроса (в результирующий НД необходимо включить только первые 8 строк): SELECT FIRST 8 F.NachislSum, (SELECT AVG(D.NachislSum) FROM NachislSumma D WHERE F.AccountCD = D.AccountCD) AS Avg_d, A.AccountCD, A.Fio FROM Abonent A, NachislSumma F WHERE F.NachislSum > (SELECT AVG(S.NachislSum) 7 FROM NachislSumma S WHERE F.AccountCD = S.AccountCD) AND A.AccountCD = F.AccountCD ORDER BY 3; Результат выполнения запроса представлен на рис. 3.126. NACHISLSUM AVG_D ACCOUNTCD FIO 279,80 128,22 005488 Аксенов С.А. 266,70 128,22 005488 Аксенов С.А. 343,36 269,95 015527 Конюхов В.С. 580,10 269,95 015527 Конюхов В.С. 611,30 269,95 015527 Конюхов В.С. 80,00 75,49 080047 Шубина Т. П. 80,00 75,49 080047 Шубина Т. П. 271,60 75,49 080047 Шубина Т. П. Рис. 3.126. Результат использования одной и той же таблицы В этом примере производится одновременная оценка среднего значения для всех строк, удовлетворяющих условию поиска в секции WHERE вложенного связанного запроса, одной и той же таблицы со значениями строки-кандидата. Выбирается первая строка-кандидат из таблицы NachislSumma и сохраняется под псевдонимом F. Выполняется вложенный запрос, просматривающий ту же самую таблицу NachislSumma с самого начала, чтобы найти все строки, где значение столбца S.AccountCD – такое же, как значение F.AccountCD. Затем по всем таким строкам в таблице NachislSumma вложенный запрос (<скалярный_подзапрос>) подсчитывает среднее значение столбца NachislSum. Анализируется условие поиска основного запроса, чтобы проверить, превышает ли значение столбца NachislSum из текущей строки-кандидата среднее значение, вычисленное вложенным запросом. Если это так, то текущая строка-кандидат выбирается для вывода. Таким образом, производятся одновременно и вычисление среднего, и отбор строк, удовлетворяющих условию. 1. Примечание. Запрос, использующий агрегатную функцию в условии поиска основного запроса (данная функция является возвращаемым элементом вложенного запроса), нельзя сформулировать с помощью техники соединения таблиц. Рассмотрим использование соотнесенного вложенного запроса в условии поиска секции HAVING. Условие поиска секции HAVING в подзапросе оценивается для каждой группы из внешнего запроса, а не для каждой строки. Следовательно, вложенный запрос будет выполняться только раз для каждой группы, выведенной внешним запросом, а не для каждой строки (как это было при использовании в секции WHERE). Например, чтобы ACCOUNTCD SUM(N.NACHISLSUM) подсчитать общие суммы 136159 964,99 начислений за услуги для 443069 1106,24 абонентов, чьи ФИО Рис. 3.127. Результат использования начинаются с буквы C (рис. соотнесенного подзапроса 3.127), можно использовать в секции HAVING соотнесенный вложенный 8 запрос: SELECT N.AccountCD, SUM(N.NachislSum) FROM NachislSumma N GROUP BY N.AccountCD HAVING N.AccountCD = (SELECT A.AccountCD FROM Abonent A WHERE A.AccountCD = N.AccountCD AND A.Fio LIKE 'С%'); Последовательность выполнения: основной запрос группирует таблицу NachislSumma по полю AccountCD; затем для каждой группы выполняется связанный вложенный запрос, возвращая единственное значение столбца AccountCD таблицы Abonent (столбец AccountCD содержит уникальные значения). 3.3.2.4. Операторы сравнения с ANY, ALL и SOME Операции сравнения можно расширить до многократного сравнения с использованием кванторов ANY и ALL. В MS SQL Server может также использоваться квантор SOME, эквивалентный квантору ANY. Это расширение используется при сравнении значений определенного столбца со значениями, возвращаемыми вложенным запросом (<подзапрос_столбца>). Квантор существования ANY, указанный после знака любой из операций сравнения, означает, что будет возвращено TRUE, если хотя бы для одного значения из подзапроса результат сравнения истинен. Квантор всеобщности ALL требует, чтобы результат сравнения был истинен для всех значений, возвращаемых подзапросом. Оператор NOT не может использоваться с ANY и ALL. Рассмотрим использование квантора ANY с независимым подзапросом. Например, требуется вывести всю информацию об оплатах абонентами за услугу с кодом, равным 4, за период до 2013 г., размер которых превышает хотя бы одно значение оплат за эту же услугу за 2013 г. Соответствующий запрос будет выглядеть так: SELECT * FROM PaySumma WHERE PaySum > ANY (SELECT PaySum FROM PaySumma WHERE PayYear=2013 AND ServiceCD=4) AND PayYear<2013 AND ServiceCD=4; Результат выполнения запроса представлен на рис. 3.128. В MS SQL Server данный запрос может быть записан так: SELECT * FROM PaySumma WHERE PaySum > SOME (SELECT PaySum FROM PaySumma WHERE PayYear=2013 AND ServiceCD=4) AND PayYear<2013 AND ServiceCD=4; 9 PAYFA ACCO SERVIC PAYS PAYDATE PAYMONTH PAYYEAR CTCD UNTC ECD UM D 65 015527 4 580,10 08.08.2012 7 2012 67 080270 4 444,50 18.04.2011 3 2011 68 080270 4 450,00 14.07.2012 6 2012 70 115705 4 553,85 02.02.2012 1 2012 75 443069 4 466,69 03.06.2012 5 2012 77 443690 4 485,00 05.09.2011 8 2011 Рис. 3.128. Результат использования квантора ANY В этом примере вложенный запрос выполняется только раз, возвращая все значения столбца PaySum, для которых истинно условие PayYear=2013 и ServiceCD=4 (611.30, 460.00…). Затем значения, выбранные подзапросом, последовательно сравниваются со значением столбца PaySum для каждой строки из таблицы PaySumma основного запроса. При первом обнаруженном совпадении сравнение прекращается и соответствующая строка выводится. Условие «> ANY» равносильно утверждению «больше, чем минимальное из существующих», а условие «< ANY» – «меньше, чем максимальное из существующих». Становится очевидным, что эти условия можно записать иначе, используя агрегатные функции MIN и MAX. Предыдущий запрос можно переписать: SELECT * FROM PaySumma WHERE PaySum > (SELECT MIN (PaySum) FROM PaySumma WHERE PayYear=2013 AND ServiceCD=4) AND PayYear<2013 AND ServiceCD=4; Результат выполнения будет совпадать с результатом, представленным на рис. 3.128. Следует отметить, что использование сравнения «= ANY» эквивалентно использованию предиката IN. Рассмотрим использование квантора ALL с независимым подзапросом. Например, требуется вывести всю информацию о ремонтных заявках, дата регистрации которых ранее даты регистрации всех заявок с кодом неисправности газового оборудования, равным 7. Соответствующий запрос: SELECT * FROM Request WHERE IncomingDate < ALL (SELECT IncomingDate FROM Request WHERE FailureCD=7); Результат выполнения запроса представлен на рис. 3.129. 10 REQU ACCO EXEC FAIL INCOMINGDA EXECUTIONDATE ESTC UNTC UTOR UREC TE D D CD D 10 136159 3 12 01.04.2011 03.04.2011 13 005488 5 8 04.09.2010 05.12.2010 15 115705 4 5 20.09.2010 23.09.2010 Рис. 3.129. Результат использования квантора ALL EXECUTED False True True Если требуется вывести всю информацию о ремонтных заявках, дата выполнения которых позднее даты выполнения всех заявок с кодом неисправности, равным 2, то запрос будет выглядеть так: SELECT * FROM Request WHERE ExecutionDate > ALL (SELECT ExecutionDate FROM Request WHERE FailureCD=2); В процессе выполнения данного запроса подзапросом формируется набор значений столбца ExecutionDate, взятых из строк, где FailureCD=2. В результате условие поиска внешнего запроса будет выглядеть следующим образом: ExecutionDate > ALL (24.10.2012, 10.08.2011, 11.10.2011, 14.09.2011). Результат выполнения запроса представлен на рис. 3.130. REQU ACCO EXEC FAIL INCOM EXECUTIONDATE EXECUTED ESTC UNTC UTOR UREC INGDA D D CD D TE 11 136160 1 6 12.01.20 12.01.2013 True 13 14 005488 4 6 04.04.20 13.04.2013 True 13 18 115705 2 3 28.12.20 04.01.2013 True 12 Рис. 3.130. Результат использования квантора ALL В НД не включены строки, где столбец ExecutionDate имеет NULL, так как проверка условия «NULL > ALL(…)» всегда возвращает результат FALSE, а выводятся только те строки, для которых условие поиска истинно. Условие «> ALL» равносильно утверждению «больше, чем максимальное», а условие «< ALL» – «меньше, чем минимальное». Становится очевидным, что такие условия можно записать иначе, используя агрегатные функции MAX и MIN. Таким образом, предыдущий запрос можно переписать: SELECT * FROM Request WHERE ExecutionDate > (SELECT MAX (ExecutionDate) FROM Request WHERE FailureCD=2); Результат выполнения будет таким же, как и в предыдущем примере. 11 Следует отметить, что использование сравнения «<> ALL» эквивалентно использованию предиката NOT IN независимо от того, независимый или связанный подзапрос используется. Рассмотрим использование связанного подзапроса с квантором ALL. Пусть требуется вывести наименования неисправностей газового оборудования, все ремонтные заявки которых зарегистрированы после 1 мая 2011 г. (рис. 3.131). Соответствующий запрос: SELECT D.FailureNM FROM Disrepair D WHERE '01.05.2011' < ALL (SELECT R.IncomingDate FROM Request R WHERE D.FailureCD=R.FailureCD); Так как в этом примере используется связанный подзапрос, то он выполняется для каждой текущей строки из таблицы Disrepair (эта строка сохраняется во внешнем запросе под псевдонимом D). Вложенный запрос просматривает всю таблицу Request, чтобы найти строки, где значение столбца R.FailureCD такое же, как значение столбца D.FailureCD, и формирует набор значений столбца IncomingDate для текущей строки-кандидата. Затем анализируется условие поиска основного запроса, чтобы проверить, меньше ли значение '01-MAY-2013' всех значений столбца IncomingDate, полученных подзапросом. Если это так, то текущая строка-кандидат выбирается для вывода ее из основного запроса. FAILURENM Засорилась водогрейная колонка Не горит АГВ Неисправна печная горелка Плохое поступление газа на горелку плиты Течет из водогрейной колонки Туго поворачивается пробка крана плиты Рис. 3.131. Результат использования связанного подзапроса с ALL При отсутствующих данных следует иметь в виду различие реакции на них кванторов ANY и ALL. Когда правильный подзапрос не возвращает результатов, квантор ALL автоматически принимает значение TRUE, а квантор ANY – FALSE. Например, запрос SELECT * FROM Request WHERE ExecutionDate > ANY (SELECT ExecutionDate FROM Request WHERE FailureCD=4); не возвращает выходных данных (в учебной базе нет заявок с неисправностью с кодом 4), в то время как запрос SELECT * FROM Request WHERE ExecutionDate > ALL (SELECT ExecutionDate FROM Request WHERE FailureCD=4); 12 полностью воспроизводит таблицу Request. 3.3.2.5. Предикат SINGULAR В СУБД Firebird совместно с подзапросами можно использовать предикат SINGULAR, который проверяет, возвращает ли подзапрос в точности одно значение. Если возвращается NULL или более одного значения, то SINGULAR дает FALSE (NOT SINGULAR дает TRUE). Предикат SINGULAR похож на предикат с квантором ALL, за исключением того, что он проверяет наличие одного и только одного соответствующего значения в наборе. С предикатом SINGULAR могут использоваться как независимые, так и соотнесенные подзапросы, однако часто использование независимого подзапроса не имеет логического смысла. Следующим запросом выводятся все данные об услугах, если оплата в размере 40 руб. производилась только единожды: SELECT * FROM Services S WHERE SINGULAR (SELECT PayFactCD FROM PaySumma P WHERE PaySum=40); Такой запрос не имеет практического смысла, так как данные об услугах и о значениях оплат никак не связаны. Поэтому с предикатом SINGULAR в основном используют соотнесенные подзапросы. Например, следующий запрос отыскивает всех абонентов, которые имеют только одну ремонтную заявку (рис. 3.132): SELECT * FROM Abonent A WHERE SINGULAR (SELECT RequestCD FROM Request R WHERE A.AccountCD=R.AccountCD); ACCOUNTC D 015527 STREETC D 3 HOUSENO FLATNO 1 FIO PHONE 65 Конюхов 761699 В.С. 136159 7 39 1 Свирина NULL З.А. 080613 8 35 11 Лукашина 254417 Р.М. Рис. 3.132. Результат использования связанного подзапроса с предикатом SINGULAR Если использовать в предыдущем запросе предикат NOT SINGULAR, то будут выведены абоненты, у которых имеется более одной ремонтной заявки или вообще нет заявок (рис. 3.133): SELECT * FROM Abonent A WHERE NOT SINGULAR (SELECT RequestCD FROM Request R WHERE A.AccountCD=R.AccountCD); 13 ACCOUNTC D 005488 STREETC D 3 HOUSENO FLATNO 4 FIO PHONE 1 Аксенов 556893 С.А. 115705 3 1 82 Мищенко 769975 Е.В. 443690 7 5 1 Тулупова 214833 М.И. 443069 4 51 55 Стародубцев 683014 Е.В. 136160 4 9 15 Шмаков NULL С.В. 126112 4 7 11 Маркова 683301 В.П. 136169 4 7 13 Денисова 680305 Е.К. 080047 8 39 36 Шубина 257842 Т. П. 080270 6 35 6 Тимошкина 321002 Н.Г. Рис. 3.133. Результат использования подзапроса с NOT SINGULAR 3.3.2.6. Предикат EXISTS Условие EXISTS означает проверку существования. В SQL условие поиска с проверкой существования представляется выражением [NOT] EXISTS (<табличный_подзапрос>). Результат условия считается истинным только тогда, когда результат выполнения <табличный_подзапрос> является непустым множеством, т. е. когда существует какая-либо строка в таблице, указанной в секции FROM запроса, удовлетворяющая условию поиска секции WHERE вложенного запроса. Другими словами, EXISTS – это предикат, который возвращает значение, равное TRUE или FALSE, в зависимости от наличия вывода из вложенного запроса. Он может работать автономно в условии поиска или в комбинации с другими логическими выражениями, использующими логические операции AND, OR и NOT. Он берет вложенный запрос как аргумент и оценивает его:  как верный, если тот производит любой вывод;  как неверный, если тот не делает этого. Этим он отличается от других предикатов в условии поиска, где он не может быть неизвестным. Существует возможность с помощью следующего запроса решить, извлекать ли некоторые данные из таблицы Abonent, если хотя бы у одного из абонентов имеются непогашенные заявки на ремонт газового оборудования: SELECT AccountCD, Fio FROM Abonent WHERE EXISTS (SELECT * FROM Request WHERE Executed IS FALSE); 14 В этом примере вложенный запрос выбирает все данные о непогашенных ремонтных заявках. ACCOUNTCD FIO Предикат EXISTS в условии Аксенов С.А. поиска внешнего запроса 005488 «отмечает», что вложенным Мищенко Е.В. запросом был произведен 115705 некоторый вывод, и, так как Конюхов В.С. предикат EXISTS был одним в 015527 условии поиска, делает условие Тулупова М.И. поиска основного запроса верным. 443690 Поскольку в таблице заявок Свирина З.А. имеются (EXISTS) строки с 136159 Executed, принимающим значение Стародубцев Е.В. False, то в НД представлены все 443069 строки таблицы Abonent. Результат Шмаков С.В. выполнения запроса представлен на 136160 рис. 3.134. 126112 Маркова В.П. 136169 Денисова Е.К. 080613 Лукашина Р.М. 080047 Шубина Т. П. 080270 Тимошкина Н.Г. Рис. 3.134. Результат использования условия EXISTS В соотнесенном вложенном запросе предикат EXISTS оценивается отдельно для каждой строки таблицы, имя которой указано во внешнем запросе, т. е. алгоритм выполнения запроса с предикатом EXISTS и связанным подзапросом точно такой же, как и для всех запросов с соотнесенными подзапросами в условии поиска. Например, с помощью следующего запроса можно вывести коды неисправностей, которые возникали у газового оборудования нескольких абонентов: SELECT DISTINCT FailureCD FROM Request Out WHERE EXISTS (SELECT * FROM Request Inn WHERE Inn.FailureCD = Out.FailureCD AND Inn.AccountCD <> Out.AccountCD); Результат выполнения запроса представлен на рис. 3.135. Для каждой строкикандидата внешнего запроса внутренний 15 запрос находит строки, совпадающие со значением в столбце FailureCD и соответствующие разным абонентам (условие AND Inn.AccountCD <> Out.AccountCD). FAILURECD 1 2 6 7 8 12 Рис. 3.135. Результат использования условия EXISTS при соотнесенном вложенном запросе Если любые такие строки найдены внутренним запросом, то это означает, что имеются 2 разных абонента, газовое оборудование которых имело текущую неисправность (неисправность в текущей строке-кандидате из внешнего запроса). Предикат EXISTS возвращает TRUE для текущей строки (результат выполнения подзапроса является непустым множеством), и код неисправности из таблицы, указанной во внешнем запросе, будет выведен. Если DISTINCT не указывать, то каждая из этих неисправностей будет выбираться для каждого абонента, у которого она произошла (у некоторых несколько раз). Использование NOT EXISTS указывает на инверсию результатов запроса. Как предикат EXISTS можно использовать во всех случаях, когда необходимо определить, имеется ли вывод из вложенного запроса. Поэтому можно использовать предикат EXISTS и в соединении таблиц. С помощью следующего запроса можно вывести не только коды, но и названия неисправностей, которые возникали у газового оборудования нескольких абонентов: SELECT DISTINCT D.* FROM Disrepair D, Request Out WHERE EXISTS (SELECT * FROM Request Inn WHERE Inn.FailureCD = Out.FailureCD AND Inn.AccountCD <> Out.AccountCD) AND D.FailureCD = Out. FailureCD; В этом примере внешний запрос – это соединение таблицы Disrepair с таблицей Request. Результат выполнения запроса представлен на рис. 3.136. FAILURECD FAILURENM 1 Засорилась водогрейная колонка 2 Не горит АГВ 6 Плохое поступление газа на горелку плиты 7 Туго поворачивается пробка крана плиты 8 При закрытии краника горелка плиты не гаснет 12 Неизвестна Рис. 3.136. Результат использования условия EXISTS при соединении 16 Следующий запрос иллюстрирует принцип извлечения из одной таблицы значений, которых нет в другой таблице (разность таблиц), с помощью предиката EXISTS: SELECT S.StreetCD FROM Street S WHERE NOT EXISTS (SELECT StreetCD FROM Abonent A WHERE S.StreetCD =A.StreetCD); Он возвращает данные об улицах, на которых не проживают абоненты. Простая перестройка вышеприведенного запроса позволит извлечь улицы, присутствующие и в таблице Street, и в таблице Abonent (пересечение таблиц). С помощью предиката NOT EXISTS можно реализовать стандартную операцию реляционной алгебры – деление. Пусть требуется найти абонентов, которые оплачивали все услуги. Используя принцип построения запросов с отрицанием, можно преобразовать задание таким образом: отобрать тех абонентов из таблицы PaySumma (делимое), для которых не существует тех услуг из таблицы Services (делитель), для которых не существует оплат в таблице PaySumma для этого абонента и этой услуги. С использованием предиката проверки на существование это задание реализуется следующим запросом: SELECT DISTINCT P.AccountCD FROM PaySumma P WHERE NOT EXISTS (SELECT ServiceCD FROM Services S WHERE NOT EXISTS (SELECT ServiceCD FROM PaySumma P1 WHERE P1.AccountCD = P.AccountCD AND P1.ServiceCD = S.ServiceCD)); Результат выполнения запроса представлен на рис. 3.137. ACCOUNTCD 080270 136169 Рис. 3.137. Результат множественного использования условия NOT EXISTS Решение данной задачи, основанное на группировке по номеру лицевого счета с подсчетом уникальных кодов оплаченных им услуг и отборе только тех абонентов, у которых это количество равно общему числу услуг: SELECT AccountCD FROM PaySumma GROUP BY AccountCD HAVING COUNT(DISTINCT ServiceCD) = (SELECT COUNT(ServiceCD) FROM Services); . Для получения такого же результата можно отобрать абонентов, для которых разность между таблицами Services и PaySumma не содержит строк: SELECT DISTINCT P.AccountCD FROM PaySumma P WHERE ServiceCD = ALL (SELECT ServiceCD FROM Services S WHERE ServiceCD NOT IN (SELECT ServiceCD FROM Paysumma P1 WHERE P1.AccountCD=P.AccountCD AND P1.ServiceCD = S.ServiceCD)); . Примечание. Предикат EXISTS нельзя использовать в случае, если вложенный запрос возвращает значение агрегатной функции. 17
«Запросы со связанными подзапросами» 👇
Готовые курсовые работы и рефераты
Купить от 250 ₽
Решение задач от ИИ за 2 минуты
Решить задачу
Помощь с рефератом от нейросети
Написать ИИ

Тебе могут подойти лекции

Смотреть все 588 лекций
Все самое важное и интересное в Telegram

Все сервисы Справочника в твоем телефоне! Просто напиши Боту, что ты ищешь и он быстро найдет нужную статью, лекцию или пособие для тебя!

Перейти в Telegram Bot