Sql подзапросы в select: Подзапросы в основных командах SQL

MySQL. Вложеные запросы. JOIN LEFT/RIGHT….

В SQL подзапросы — или внутренние запросы, или вложенные запросы — это запрос внутри другого запроса SQL, который вложен в условие WHERE.


Вложеные запросы


SQL подзапрос — это запрос, вложенный в другой запрос.

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


Существует несколько правил, которые применяются к подзапросам:

  • Подзапросы должны быть заключены в круглые скобки.
  • Подзапрос может иметь только один столбец в условии SELECT, если только несколько столбцов не указаны в основном запросе для подзапроса для сравнения выбранных столбцов.
  • Подзапросы, которые возвращают более одной строки, могут использоваться только с несколькими операторами значений, такими как оператор IN.
  • Команда ORDER BY не может использоваться в подзапросе, хотя в основном запросе она использоваться может. В подзапросе может использоваться команда GROUP BY для выполнения той же функции, что и ORDER BY.
  • С подзапросом не может использоваться оператор BETWEEN. Однако оператор BETWEEN может использоваться внутри подзапроса.
  • Не рекомендуется создавать запросы со степенью вложения больше трех. Это приводит к увеличению времени выполнения и к сложности восприятия кода.

Синтаксис

SELECT

Подзапросы чаще всего используются с инструкцией SELECT. При этом используется следующий синтаксис


					
    SELECT имя_столбца FROM имя_таблицы WHERE часть условия IN
        (SELECT имя_столбца FROM имя_таблицы WHERE часть условия IN
            (SELECT имя_столбца FROM имя_таблицы WHERE условие)
                ...
		)
		;
					
				


Ниже представлена струтура таблицы для демонстрации примеров

Пример таблицы продавцов SALES
snumsnamecitycomm
1КоловановМосква10
2ПетровТверь25
3ПлотниковМосква22
4КучеровСанкт-Петербург28
5МалкинСанкт-Петербург18
6ШипачевЧелябинск30
7МозякинОдинцово25
8ПроворовМосква25

Пример таблицы покупателей CUSTOMERS
cnumcnamecityratingsnum
1ДесновМосква906
2КрасновМосква957
3КирилловТверь963
4ЕрмолаевОбнинск983
5КолесниковСерпухов985
6ПушкинЧелябинск904
7БелыйОдинцово851
8ЧудиновМосква893
9ПроворовМосква952
10ЛосевОдинцово758

Пример таблицы заказов ORDERS
onumamtodate(YEAR)cnumsnum
1001420201394
10026532005107
1003960201621
1004320201633
1005200201554
10062560201454
10071200201371
100850201713
1009564201237
1010900201868

Вывести суммы заказов и даты, которые проводил продавец с фамилией «Плотников».

Начнем с такого примера и для начала вспомним, как бы делали этот запрос ранее: посмотрели бы в таблицу SALES(или выполнили отдельный запрос), определили бы snum продавца «Плотников» — он равен 3. И выполнили бы запрос SQL с помощью условия WHERE.



					
    SELECT amt, odate
    FROM orders 
    WHERE snum = 3
					
				

Результат работы
amtodate
3202016
502017

Такой запрос, очевидно, не очень универсален, если нам захочется выбрать тоже самое для другого продавца, то всегда придется определять его snum. В SQL предусмотрена возможность объединять такие запросы в один путем превращения одного из них в подзапрос (вложенный запрос).



					
    SELECT amt, odate
    FROM orders
    WHERE snum = (  SELECT snum
                    FROM sales
                    WHERE sname = 'Плотников')
					
				

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


Показать уникальные номера и фамилии продавцов, которые провели сделки в 2016 году.


					
    SELECT snum, sname
    FROM sales
    WHERE snum IN ( SELECT snum
                    FROM orders
                    WHERE odate = 2016)
					
				

Этот SQL запрос отличается тем, что вместо знака = здесь используется оператор IN.

Оператор IN следует использовать в том случае, если вложенный подзапрос SQL возвращает несколько значений.

То есть в запросе происходит проверка, содержится ли идентификатор snum из таблицы SALES в массиве значений, который вернул вложенный запрос. Если содержится, то SQL выдаст фамилию этого продавца.

Результат запроса
snumsname
1Колованов
3Плотников

Предыдущие примеры, которые мы уже рассмотрели, сравнивали в условии WHERE одно поле. Это конечно хорошо, но стоит отметить, что в SQL предусмотрена возможность сравнения сразу нескольких полей, то есть можно использовать вложенный запрос с несколькими параметрами.


Вывести пары покупателей и продавцов, которые осуществили сделку между собой, но не позднее 2014 года

Запрос чем то похож на предыдущий, только теперь мы добавляем еще одно поле для сравнения.


					
    SELECT cname as 'Покупатель', sname as 'Продавец'
    FROM customers cus, sales sal
    WHERE (cus.cnum, sal.snum) IN ( SELECT cnum, snum
                                    FROM orders
                                    WHERE odate < 2014 )
					
				

Список пар покупатель — продавец
ПокупательПродавец
ПроворовКучеров
ЛосевМозякин
БелыйКолованов
КирилловМозякин

В этом примере мы сравниваем сразу два поля одновременно по идентификаторам. То есть из таблицы ORDERS берутся те строки, которые удовлетворяют условию не позднее 2014 года, затем вместо идентификаторов подставляются значение имен покупателей и продавцов.


На самом деле, такой запрос SQL используется крайне редко, обычно используют оператор INNER JOIN.


Оператор as нужен для того, чтобы при выводе SQL показывал не имена полей, а то, что мы зададим. И после оператора FROM за именами таблиц стоят сокращения, которые потом используются — это псевдонимы. Псевдонимы можно называть любыми именами, в этом запросе они используются для явного определения поля, так как мы несколько раз обращаемся к одному и тому же полю, только из разных таблиц.

Подзапросы могут использоваться с инструкциями SELECT, INSERT, UPDATE и DELETE вместе с операторами типа =, <, >, >=, <=, IN, BETWEEN и т. д.


Далее будут показы примеры использования вложеных запросов с использованием базы данных world. БД находится в папке _lec\7\db вместе с лекцией.


CREATE

Данный пример несовсем относится к теме занятия, но по жизни он очень может пригодиться.


Задача — создать копию существующей таблицы.

Копия существующей таблицы может быть создана с помощью комбинации CREATE TABLE и SELECT.

Новая таблица будет имеет те же определение столбцов, могут быть выбраны все столбцы или отдельные столбцы. При создании новой таблицы с помощью существующей таблицы, новая таблица будет заполняться с использованием существующих значений в старой таблице.



					
    CREATE TABLE NEW_TABLE_NAME AS
                                    SELECT [ column1, column2. ..columnN ]
                                    FROM EXISTING_TABLE_NAME
                                    [ WHERE ]
					
				


Создадим копию таблицы city. Вопрос — почему 1=0?


					
    CREATE TABLE city_bkp AS
                            SELECT *
                            FROM city
                            WHERE 1=0
					
				


INSERT

Задача — создать копию существующей таблицы.

Подзапросы также могут использоваться с инструкцией INSERT. Инструкция INSERT использует данные, возвращаемые из подзапроса, для вставки в другую таблицу. Выбранные в подзапросе данные могут быть изменены. Основной синтаксис следующий.



					
    INSERT INTO table_name [ (column1 [, column2 ]) ]
                SELECT [ *|column1 [, column2 ]
                FROM table1 [, table2 ]
                [ WHERE VALUE OPERATOR ]
					
				

Копирование всей таблицы полностью


					
    INSERT INTO city_bkp SELECT * FROM city
					
				

Копируем города которые находся в стране с численостью не меньше 500тыс. человек, но не больше 1 миллиона.


					
    INSERT INTO city_bkp 
                SELECT * FROM city 
                WHERE CountryCode IN 
                                (SELECT Code FROM country 
                                 WHERE Population < 1000000 AND Population > 500000)
					
				

UPDATE

Подзапрос может использоваться в сочетании с инструкцией UPDATE. Один или несколько столбцов в таблице могут быть обновлены при использовании подзапроса с помощью инструкции UPDATE. Основной синтаксис следующий.



					
    UPDATE table
    SET column_name = new_value
        [ WHERE OPERATOR [ VALUE ]
        (SELECT COLUMN_NAME
         FROM TABLE_NAME)
         [ WHERE) ]
					
				


Исходя из того, что у нас есть таблица CITY_BKP, которая является резервной копией таблицы CITY, в следующем примере для всех записей, для которых Population больше или равно 100000, применяет коэффициент 0,25.


					
    UPDATE city_bkp SET Population = Population * 0.25 
    WHERE Population IN (
                         SELECT Population FROM city 
                         WHERE Population >= 100000 ) 
					
				


DELETE

Подзапрос может использоваться в сочетании с инструкцией DELETE, так же как и со всеми описанными выше инструкциями. Основной синтаксис следующий.



					
    DELETE FROM TABLE_NAME
    [ WHERE OPERATOR [ VALUE ]
        (SELECT COLUMN_NAME
         FROM TABLE_NAME)
         [ WHERE) ]
					
				


Далее будут показы примеры использования вложеных запросов с использованием базы данных world. БД находится в папке _lec\7\db вместе с лекцией.


Внутреннее объединение

Вывести идентификатор и название города, а так же страну нахождения

Для этого проще всего обратиться к таблице CITY



				
    SELECT ID, Name, CountryCode FROM city
				
			


Но, что если нам необходимо, чтобы в ответе на запрос был не код страны, а её название? Вложенные запросы нам не помогут. А нам надо получить данные из двух таблиц и объединить их в одну. Запросы, которые позволяют это сделать, в SQL называются объединениями. Синтаксис самого простого объединения следующий:



				
    SELECT city.ID, city.Name, country.Name FROM city, country
				
			


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


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



				
    SELECT city.ID, city.Name, country.Name 
    FROM city, country 
    WHERE city.CountryCode = country.Code
				
			


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

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

JOIN LEFT/RIGHT

JOIN — оператор языка SQL, который является реализацией операции соединения реляционной алгебры. Входит в предложение FROM операторов SELECT, UPDATE и DELETE.

JOIN используется для объединения строк из двух или более таблиц на основе соответствующего столбца между ними.

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

Особенности операции соединения

  • в схему таблицы-результата входят столбцы обеих исходных таблиц
  • каждая строка таблицы-результата является «сцеплением» строки из одной таблицы со строкой второй таблицы

Определение того, какие именно исходные строки войдут в результат и в каких сочетаниях, зависит от типа операции соединения и от явно заданного условия соединения. Условие соединения, то есть условие сопоставления строк исходных таблиц друг с другом, представляет собой логическое выражение (предикат).


Ниже представлена струтура таблицы для демонстрации примеров

Таблица персонала Person
idnamecity_id
1Колованов1
2Петров3
3Плотников12
4Кучеров4
5Малкин2
6Иванов13

Ниже представлена струтура таблицы для демонстрации примеров

Таблица городов City
idnamepopulation
1Москва100
2Нижний Новгород25
3Тверь22
4Санкт-Петербург80
5Выборг18
6Челябинск30
7Одинцово5
8Павлово5

INNER JOIN

Оператор внутреннего соединения INNER JOIN соединяет две таблицы. Порядок таблиц для оператора неважен, поскольку оператор является симметричным.

Заголовок таблицы-результата является объединением (конкатенацией) заголовков соединяемых таблиц.



					
    SELECT * FROM Person
    INNER JOIN
        City
    ON Person.city_id = City.id
					
				


Результат запроса
Person.idPerson.namePerson.city_idCity.idCity.nameCity.population
1Колованов11Москва100
2Петров33Тверь22
4Кучеров44Санкт-Петербург80
5Малкин22Нижний Новгород25

INNER JOIN

Тело результата логически формируется следующим образом. Каждая строка одной таблицы сопоставляется с каждой строкой второй таблицы, после чего для полученной «соединённой» строки проверяется условие соединения (вычисляется предикат соединения). Если условие истинно, в таблицу-результат добавляется соответствующая «соединённая» строка.

В SQL существуют разные типы объединений. Мы рассмотрим только некоторые из них.


LEFT JOIN

Возвращает все строки из левой таблицы, даже если в правой таблице нет совпадений.



LEFT JOIN


					
    SELECT * FROM Person
    LEFT JOIN
        City
    ON Person.city_id = City.id
					
				

Для записей неудовлетворяющих условия объединения поля правой таблицы заполняются значениями NULL



Результат запроса
Person.idPerson. namePerson.city_idCity.idCity.nameCity.population
1Колованов11Москва100
2Петров33Тверь22
3Плотников12NULLNULLNULL
4Кучеров44Санкт-Петербург80
5Малкин22Нижний Новгород25
6Иванов13NULLNULLNULL

RIGHT JOIN

Возвращает все строки из правой таблицы, даже если в левой таблице нет совпадений.



RIGHT JOIN


					
    SELECT * FROM Person
    RIGHT JOIN
        City
    ON Person.city_id = City.id
					
				

Для записей неудовлетворяющих условия объединения поля левой таблицы заполняются значениями NULL



Результат запроса
Person. idPerson.namePerson.city_idCity.idCity.nameCity.population
1Колованов11Москва100
2Петров33Тверь22
4Кучеров44Санкт-Петербург80
5Малкин22Нижний Новгород25
NULLNULLNULL5Выборг18
NULLNULLNULL6Челябинск30
NULLNULLNULL7Одинцово5
NULLNULLNULL8Павлово5

2.14. Подзапросы — Transact-SQL В подлиннике : Персональный сайт Михаила Флёнова

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

Рассмотрим пример, как можно определить номера телефонов, у которых установлен тип ‘Сотовый рабочий’. Для этого сначала необходимо узнать, какой первичный ключ у нужного типа телефона в таблице tbPhoneType:


SELECT idPhoneType 
FROM tbPhoneType 
WHERE vcTypeName = ('Сотовый рабочий')

После этого уже находим все записи в таблице tbPhoneNumbers, где поле «idPhoneType» содержит найденное значение типа телефона:


SELECT * 
FROM tbPhoneNumbers
WHERE idPhoneType = идентификатор

Эта задача достаточно просто решается с помощью подзапросов:


SELECT * 
FROM tbPhoneNumbers
WHERE idPhoneType = 
 (
  SELECT idPhoneType 
  FROM tbPhoneType 
  WHERE vcTypeName = ('Сотовый рабочий')
 )

В данном примере мы выбираем все записи из таблицы tbPhoneNumbers. При этом, поле «idPhoneType» сравнивается с результатом подзапроса, который пишется в круглых скобках. Так как стоит знак равенства, то результат подзапроса должен быть из одного поля и одной строки. Если результатом будет два поля или более одной строки, то сервер вернет нам ошибку.

Попробуем выполнить следующий запрос:


SELECT * 
FROM tbPhoneNumbers
WHERE idPhoneType =
 (
  SELECT idPhoneType 
  FROM tbPhoneType 
  WHERE vcTypeName in ('Сотовый рабочий', 'Сотовый домашний')
 )

Этот запрос вернет две строки с двумя значениями первичного ключа. В ответ на это, сервер вернет ошибку:

Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.

(Подзапрос возвращает более чем 1 значение. Это не позволено, когда подзапрос следует после знаков =, !=, <, <= , >, >= или когда подзапрос используется как выражение)

А что же тогда можно использовать? Если немного подумать, то для такого запроса знак равенства нужно заменить на оператор IN:


SELECT * 
FROM tbPhoneNumbers
WHERE idPhoneType IN
 (
  SELECT idPhoneType 
  FROM tbPhoneType 
  WHERE vcTypeName in ('Сотовый рабочий', 'Сотовый домашний')
 )

Сначала SQL выполнит внутренний запрос, который расположен в скобках и результат подставит во внешний запрос.

Я уже намекнул на то, что результат подзапроса должен состоять только из одной колонки. Это значит, что вы не можете написать во внутреннем запросе SELECT *, а можно только SELECT ИмяОдногоПоля. Помните, что имя должно быть только одно и тип его должен совпадать с типом сравниваемого значения. Подзапросы нужно использовать очень аккуратно, потому что они могут привести к ошибке.

Очень важно, что подзапрос находиться в скобках и справа от знака равенства. Стандарт не позволяет писать подзапросы слева. Это значит, что следующий запрос не верный:


SELECT * 
FROM tbPhoneNumbers
WHERE 
 (
  SELECT idPhoneType 
  FROM tbPhoneType 
  WHERE vcTypeName = ('Сотовый рабочий')
 ) = idPhoneType

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

Основной запрос (так же называемый внешним) может обращаться к подзапросу (внутренний запрос). Для этого таблицам необходимо указать псевдонимы. Посмотрим на следующий запрос:


SELECT * 
FROM tbPhoneNumbers ot
WHERE idPhoneType IN
 (
  SELECT idPhoneType 
  FROM tbPhoneType it
  WHERE vcTypeName in ('Сотовый рабочий', 'Сотовый домашний')
   AND ot.vcPhoneNumber LIKE '(923)%'
 )

Обратите внимание на предпоследнюю строку:


AND ot.vcPhoneNumber LIKE '(923)%'

Здесь происходит сравнение поя «vcPhoneNumber» таблицы ot с шаблоном. Самое интересное здесь в том, что ot – это псевдоним таблицы tbPhoneNumbers, которая описана в секции FROM внешнего запроса. Но, не смотря на это, мы можем из подзапроса обращаться по псевдониму к столбцам внешних запросов. Таким образом, можно наводить достаточно сложные связи между запросами.

Такой запрос будет выполняться по следующему алгоритму:

  1. Выбрать строку из таблицы tbPhoneNumbers в внешнем запросе. Это будет текущая строка-кандидат.
  2. Сохранить значения из этой строки-кандидата в псевдониме с именем ot.
  3. Выполнить подзапрос, при этом, во время поиска участвует и внешний запрос.
  4. Оценить «idPhoneType» внешнего запроса на основе результатов подзапроса выполняемого в предыдущем шаге. Он определяет — выбирается ли строка-кандидат для вывода на экран.

Подзапросы могут быть не только в секции WHERE, но и в секции SELECT и в секции FROM. Давайте рассмотрим сначала подзапросы из FROM, для этого поставим достаточно сложную, но интересную задачу. Во время группировки мы смогли научиться определять количество пользователей с именем Андрей. А что если создать таблицу, которая будет состоять из трех колонок:

  • Имя;
  • Количество пользователей с таким именем;
  • Количество номеров телефонов для каждого имени.

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


SELECT p.vcName, COUNT (*) as PeopleNumber
FROM tbPeoples p
GROUP BY p. vcName

Такой запрос мы уже разбирали.

Теперь определим количество телефоном для каждого имени. Именно имени, а не работника. Нас интересует, сколько номеров телефонов у всех Андреев, Иванов и т.д. Эта задача решается с помощью связи из таблиц работников и телефонов, группировкой по имени и подсчетом количества телефонов:


SELECT pl.vcName, COUNT(vcPhoneNumber) AS PhoneNum
FROM tbPeoples pl, tbPhoneNumbers pn
WHERE pl.idPeoples *= pn.idPeoples
GROUP BY vcName

У нас получилось две разных таблицы. А как теперь их объединить в одно целое? Попробуйте самостоятельно решить эту задачу. Мое решение можно увидеть в листинге 2.3.

Листинг 2.3. Получение количества имен и количества телефонов у каждого имени


SELECT *
FROM
  (SELECT p.vcName, COUNT(*) as PeopleNumber
   FROM tbPeoples p
   GROUP BY p.vcName) p1,

  (SELECT pl.vcName, COUNT(vcPhoneNumber) AS PhoneNum
   FROM tbPeoples pl, tbPhoneNumbers pn
   WHERE pl. idPeoples *= pn.idPeoples
   GROUP BY vcName) p2
WHERE p1.vcName=p2.vcName

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

Для таких запросов, есть только одно ограничение – у каждого поля в подзапросе секции FROM должно быть имя. У нас есть поля, подсчитывающие количество записей и для таких полей имя не устанавливается, поэтому я установил псевдонимы. В первом подзапросе колонка с количеством записей названа как PeopleNumber, а во втором подзапросе — PhoneNum.

Внешний объединяющий запрос связывает обе полученные таблицы через поле имени «vcName», а результатом будет общая таблица (см. рис. 2.7), состоящая из четырех колонок – имя и количество из первого запроса, и имя и количество из второго запроса. Одну из колонок имен можно убрать, потому что они идентичны, но я решил оставить, чтобы вы могли увидеть связь.

Результат объединения двух таблиц

Теперь посмотрим, как можно писать подзапросы в секции SELECT. Я не нашел интересного примера, поэтому просто решил вывести всех работников и их телефоны. Но при этом, не наводить связь между таблицами работников и телефонами в секции FROM, а искать номера с помощью подзапросов в секции SELECT:


SELECT pl.*, 
 (
  SELECT vcPhoneNumber 
  FROM tbPhoneNumbers pn 
  WHERE pn.idPhoneNumbers=pl.idPeoples
 )
FROM tbPeoples pl

В секции SELECT сначала запрашиваем все колонки из таблицы tbPeoples (pl.*). После этого, вместо очередного поля в скобках указывается подзапрос, который выбирает данные. При этом в подзапросе в секции WHERE наводиться связь между таблицами. Получается, что и из этого подзапроса мы можем обращаться к полям внешнего запроса.

Единственное ограничение – подзапрос в секции SELECT должен возвращать только одну строку. Если результатом будет несколько строк, то запрос возвращает ошибку.

Все примеры, которые мы рассматривали выше, достаточно просто реализовать, без использования подзапросов. Например, посмотрим на следующий запрос:


SELECT * 
FROM tbPhoneNumbers
WHERE idPhoneType = 
 (
  SELECT idPhoneType 
  FROM tbPhoneType 
  WHERE vcTypeName = ('Сотовый рабочий')
 )

Эта же задача решается следующим образом:


SELECT pn.* 
FROM tbPhoneNumbers pn, tbPhoneType pt
WHERE pn.idPhoneType = pt.idPhoneType 
 AND vcTypeName = ('Сотовый рабочий')

Просто связываем обе таблицы и указываем то же самое условие. Таким образом, мы избавились от подзапроса, и теперь сервер сможет выполнить задачу быстрее. Большинство задач можно решить без подзапросов, если правильно связать таблицы. Именно поэтому, в данной книге я постараюсь минимально пользоваться подзапросами, и все постараемся решать одним оператором SELECT.

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


SELECT * 
FROM tbPeoples 
WHERE idPeoples=
    (SELECT MAX(idPeoples) FROM tbPeoples)

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

подзапросов в инструкциях SELECT

подзапросов в инструкциях SELECT

Руководство Informix по SQL: Учебное пособие
Составление Advanced SELECT
Заявления

Следующие ситуации определяют типы подзапросов, которые поддерживает сервер баз данных:

  • Оператор SELECT , вложенный в список SELECT других ВЫБЕРИТЕ оператор
  • оператор SELECT , вложенный в предложение WHERE другого оператора SELECT (или в оператор INSERT , DELETE или UPDATE )

Каждый подзапрос должен содержать предложение SELECT и предложение FROM . Подзапросы могут быть коррелированными или некоррелированными . Подзапрос (или внутренний оператор SELECT ) коррелируется, когда значение, которое он производит, зависит от значения, созданного оператором .0054 внешний SELECT оператор, который его содержит. Любой другой вид подзапроса считается некоррелированным.

Важной особенностью коррелированного подзапроса является то, что, поскольку он зависит от значения из внешнего SELECT , он должен выполняться многократно, по одному разу для каждого значения, которое производит внешний SELECT . Некоррелированный подзапрос выполняется только один раз.

Вы можете создать оператор SELECT с подзапросом для замены двух отдельных SELECT операторов.

Подзапросы в операторах SELECT позволяют выполнять следующие действия:

  • Сравните выражение с результатом другого оператора SELECT
  • Определить, содержат ли результаты другого оператора SELECT выражение.
  • Определить, выбирает ли другая инструкция SELECT какие-либо строки

Необязательное предложение WHERE в подзапросе часто используется для сужения условия поиска.

Подзапрос выбирает и возвращает значения первому или внешнему оператору SELECT . Подзапрос может не возвращать значения, одно значение или набор значений, как показано ниже:

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

Подзапросы в списке выбора

Подзапрос может появиться в списке выбора другого оператора SELECT . Запрос 5-20 показывает, как вы можете использовать подзапрос в списке выбора для возврата общей стоимости доставки (из таблицы заказов ) для каждого клиента в таблице клиентов . Вы также можете написать этот запрос как соединение между двумя таблицами.

Запрос 5-20

    ВЫБЕРИТЕ customer.customer_num,
    (ВЫБЕРИТЕ СУММУ (ship_charge)
    ОТ заказов
    ГДЕ клиент.номер_клиента = заказы.номер_клиента)
    КАК total_ship_chg
    ОТ заказчика

    Результат запроса 5-20

подзапросов в разделах WHERE

В этом разделе описываются подзапросы, которые встречаются в виде оператора SELECT , вложенного в предложение WHERE другого оператора SELECT .

Следующие ключевые слова вводят подзапрос в предложение WHERE оператора SELECT :

  • ВСЕ
  • ЛЮБОЙ
  • В
  • СУЩЕСТВУЕТ

Вы можете использовать любой оператор отношения с ВСЕ и ЛЮБОЙ для сравнения чего-либо с каждым из ( ВСЕ ) или с любым из ( ЛЮБОЙ ) значений, выдаваемых подзапросом. Вы можете использовать ключевое слово SOME вместо ANY . Оператор IN эквивалентен = ANY . Чтобы создать противоположное условие поиска, используйте ключевое слово NOT или другой оператор отношения.

Оператор EXISTS проверяет подзапрос на наличие каких-либо значений; то есть он спрашивает, не является ли результат подзапроса нулевым. Вы не можете использовать EXISTS ключевое слово в подзапросе, который содержит столбец с типом данных TEXT или BYTE .

Синтаксис, который вы используете для создания условия с подзапросом, см. в Informix Guide to SQL : Syntax .

Использование ВСЕХ

Используйте ключевое слово ALL перед подзапросом, чтобы определить, верно ли сравнение для каждого возвращаемого значения. Если подзапрос не возвращает значений, условие поиска равно true . (Если он не возвращает никаких значений, условие истинно для всех нулевых значений.)

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

Запрос 5-21

    ВЫБЕРИТЕ order_num, stock_num, manu_code, total_price
    ИЗ товаров
    WHERE total_price < ALL
    (ВЫБЕРИТЕ total_price ИЗ товаров
    WHERE order_num = 1023)

Результат запроса 5-21

Использование ЛЮБОГО

Используйте ключевое слово ANY (или его синоним SOME ) перед подзапросом, чтобы определить, верно ли сравнение хотя бы для одного из возвращаемых значений. Если подзапрос не возвращает никаких значений, условием поиска является false . (Поскольку значений не существует, условие не может быть истинным ни для одного из них.)

Запрос 5-22 находит номер заказа для всех заказов, содержащих товар, общая цена которого больше, чем общая цена любой один из позиций в заказе № 1005.

Запрос 5-22

    ВЫБЕРИТЕ ОТЛИЧНЫЙ номер_заказа
    ИЗ элементов
    ГДЕ общая_цена > ЛЮБОЙ
    (ВЫБЕРИТЕ общую цену
    ИЗ элементов
    ГДЕ номер_заказа = 1005)

    Результат запроса 5-22

Однозначные подзапросы

Вам не нужно включать ключевое слово ВСЕ или ЛЮБОЙ , если вы знаете, что подзапрос может вернуть ровно одно значение в запрос внешнего уровня. Подзапрос, возвращающий ровно одно значение, можно рассматривать как функцию. Этот вид подзапроса часто использует агрегатную функцию, поскольку агрегатные функции всегда возвращают одиночные значения.

Запрос 5-23 использует агрегатную функцию MAX в подзапросе, чтобы найти order_num для заказов, включающих максимальное количество волейбольных сеток.

Запрос 5-23

    ВЫБЕРИТЕ order_num ИЗ товаров
    ГДЕ stock_num = 9
    И количество =
    (ВЫБЕРИТЕ МАКС (количество)
    ИЗ товаров
    ГДЕ stock_num = 9)

Результат запроса 5-23

Запрос 5-24 использует агрегатную функцию MIN в подзапросе для выбора товаров, общая цена которых превышает минимальную цену более чем в 10 раз.

Запрос 5-24

    ВЫБЕРИТЕ order_num, stock_num, manu_code, total_price
    ИЗ элементов x
    ГДЕ total_price >
    (ВЫБЕРИТЕ 10 * MIN (общая_цена)
    ИЗ элементов
    ГДЕ order_num = x. order_num)

    Результат запроса 5-24

Коррелированные подзапросы

Запрос 5-25 — это пример коррелированного подзапроса, который возвращает список из 10 последних дат доставки в заказов стол. Он включает предложение ORDER BY после подзапроса для упорядочения результатов, поскольку вы не можете включить ORDER BY в подзапрос.

Запрос 5-25

    ВЫБЕРИТЕ po_num, ship_date ИЗ основных заказов
    ГДЕ 10 >
    (ВЫБЕРИТЕ СЧЕТЧИК (DISTINCT ship_date)
    ИЗ заказов sub
    ГДЕ sub.ship_date < main.ship_date)
    AND ship_date НЕ НУЛЕВОЕ
    ORDER BY ship_date, po_num

Подзапрос коррелирован, потому что число, которое он выдает, зависит от main.ship_date , значения, которое выдает внешний SELECT . Таким образом, подзапрос должен выполняться повторно для каждой строки, которую рассматривает внешний запрос.

Запрос 5-25 использует функцию COUNT для возврата значения в основной запрос. Затем предложение ORDER BY упорядочивает данные. Запрос находит и возвращает 16 строк с 10 последними датами доставки, как показано в результате запроса 5-25.

Результат запроса 5-25

Если вы используете коррелированный подзапрос, такой как запрос 5-25, в большой таблице, вы должны проиндексировать столбец ship_date для повышения производительности. В противном случае этот оператор SELECT неэффективен, поскольку он выполняет подзапрос один раз для каждой строки таблицы. Сведения об индексации и проблемах с производительностью см. в Руководстве администратора и в вашем Руководство по производительности .

Использование EXISTS

Ключевое слово EXISTS известно как квалификатор существования , потому что подзапрос истинен только в том случае, если внешний SELECT , как показывает запрос 5-26а, находит хотя бы одну строку.

Запрос 5-26a

    ВЫБЕРИТЕ УНИКАЛЬНОЕ имя_производителя, время_выпуска
    ОТ производителя
    ГДЕ СУЩЕСТВУЕТ
    (ВЫБЕРИТЕ * ИЗ ЗАПАСА
    ГДЕ описание СООТВЕТСТВУЕТ '*обуви*'
    И код_производства = код_запаса.производства)

Вы часто можете построить запрос с EXISTS , который эквивалентен тому, который использует IN . В запросе 5-26b используется предикат IN для создания запроса, возвращающего тот же результат, что и в запросе 5-26a.

Запрос 5-26b

    ВЫБЕРИТЕ УНИКАЛЬНОЕ manu_name, lead_time
    FROM stock, manufact
    WHERE manu_code IN
    (SELECT manu_code FROM stock
    WHERE description MATCHES '*shoe*')
    AND stock. manu_code = manufact.manu_code

Запрос 5-26a и запрос 5-26b возвращают строки для производителей, которые производят определенную обувь, а также время выполнения заказа на продукт. Результат запроса 5-26 показывает возвращаемые значения.

Результат запроса 5-26

Добавьте ключевое слово NOT к IN или к EXISTS , чтобы создать условие поиска, противоположное условию в предыдущих запросах. Вы также можете заменить != ВСЕ для НЕ В .

Запрос 5-27 показывает два способа сделать одно и то же. Один способ может позволить серверу базы данных выполнять меньше работы, чем другой, в зависимости от структуры базы данных и размера таблиц. Чтобы узнать, какой запрос может быть лучше, используйте команду SET EXPLAIN , чтобы получить список плана запроса. SET EXPLAIN обсуждается в Руководстве по производительности и Informix Guide to SQL : Syntax .

Запрос 5-27

    ВЫБЕРИТЕ номер_клиента, компанию ОТ клиента
    ГДЕ номер_клиента НЕ В
    (ВЫБЕРИТЕ номер_клиента ИЗ заказов
    ГДЕ номер_клиента = номер_заказа)

    ВЫБЕРИТЕ номер_клиента, компанию ИЗ клиента
    ГДЕ НЕ СУЩЕСТВУЕТ
    (ВЫБЕРИТЕ * ИЗ заказов
    ГДЕ клиент. номер_клиента = заказы.номер_клиента)

Каждый оператор в запросе 5-27 возвращает строки, показанные в результате запроса 5-27, которые идентифицируют клиентов, которые не размещали заказы.

Результат запроса 5-27

Ключевые слова EXISTS и IN используются для операции множества, известной как пересечение , а ключевые слова NOT EXISTS и NOT IN используются для операции множества, известной как разность . Эти концепции обсуждаются в разделе Операции над множествами.

Запрос 5-28 выполняет подзапрос к таблице элементов , чтобы идентифицировать все элементы в сток столик который еще не заказал.

Запрос 5-28

    ВЫБЕРИТЕ * ИЗ СКЛАДА
    ГДЕ НЕ СУЩЕСТВУЕТ
    (ВЫБЕРИТЕ * ИЗ товаров
    ГДЕ stock.stock_num = items.stock_num
    AND stock.manu_code = items.manu_code)

Запрос 5-28 возвращает строки, показанные в Результате Запроса 5-28.

Результат запроса 5-28

Не существует логического ограничения на количество подзапросов a Оператор SELECT может иметь, но размер любого оператора физически ограничен, когда он рассматривается как строка символов. Однако этот предел, вероятно, больше, чем любое практическое утверждение, которое вы, вероятно, сочините.

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

Запрос 5-29

    ВЫБЕРИТЕ * ИЗ товаров
    ГДЕ общая_цена != количество *
    (ВЫБЕРИТЕ цену за единицу ИЗ запаса
    ГДЕ запас.номер_запаса = номер_запаса
    И код_запаса = код_к_запаса)

Запрос 5-29 возвращает только те строки, для которых общая цена товара в заказе не равна цене единицы запаса, умноженной на количество заказа. Если скидка не применялась, вероятно, такие строки были неправильно введены в базу данных. Запрос возвращает строки только при возникновении ошибок. Если информация правильно вставлена ​​в базу данных, строки не возвращаются.

Результат запроса 5-29


Informix Guide to SQL: Tutorial , Version 9. 2
Авторские права Informix Software, Inc., 1999. Все права защищены.

Подзапросы SQL — Полное руководство

Подзапросы SQL позволяют создавать запросы, которые являются более динамичными и управляемыми данными. Думайте о них как о запросе внутри другого запроса.

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

Работая с примерами, помните, что они основаны на Microsoft SQL Server Management Studio и базе данных AdventureWorks2012. Начните использовать эти бесплатные инструменты с помощью моего руководства  Начало работы с SQL Server .

Содержание

  • Подзапросы в SQL
  • Обзор подзапроса
    • Первый обход
    • Второй обход
  • Подзапросы в предложении SELECT
    • Простой подзапрос 9 для расчета среднего0033
    • Простой подложка в выражении
  • Подг.
  • Заключение

Подзапросы в SQL

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

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

Например, вы можете использовать подзапросы в предложениях SELECT, FROM, WHERE или HAVING. Подзапрос может возвращать либо одно значение, либо несколько строк.

Одиночное значение также называется скалярным значением.

Обзор подзапроса

Подзапросы позволяют создавать более динамичные и управляемые данными запросы. Например, используя подзапрос, вы можете вернуть все продукты, у которых ListPrice больше, чем средняя ListPrice для всех продуктов.

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

Первая прогулка по

Давайте разберем этот запрос, чтобы вы могли увидеть, как он работает.

Шаг 1:   Сначала запустим подзапрос:

 ВЫБЕРИТЕ СРЕДНЮЮ (СписочнаяЦена)
ОТ Производства.Товар 

Возвращает 438,6662 как среднюю ListPrice

Шаг 2:   Найдите товары с ценой выше средней, вставив среднее значение ListPrice в сравнение нашего запроса.

 ВЫБЕРИТЕ ProductID,
Имя,
Список цен
ИЗ   производства.Продукт
ГДЕ ListPrice >  438,6662  
Огромное преимущество подзапросов SQL

Как видите, с помощью подзапроса мы объединили два шага вместе. Подзапрос избавил нас от необходимости находить среднюю ListPrice, а затем подставлять ее в наш запрос.

Это огромно! Это означает, что наш запрос автоматически адаптируется к изменяющимся данным и новым средним значениям.

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

Мы позволяем подзапросу делать всю работу за нас! Подзапрос вычисляет среднее значение на лету; нам не нужно «обновлять» среднее значение в запросе.

Второе прохождение

Возможность динамически создавать критерий для запроса очень удобна. Здесь мы используем подзапрос для получения списка всех клиентов, на территории которых объем продаж ниже 5 000 000 долларов США.

Мы строим список «вживую» с помощью оператора IN, что избавляет нас от необходимости жестко кодировать значения.

Может быть полезно посмотреть, как выполнить этот запрос шаг за шагом:

Шаг 1:   Запустите подзапрос, чтобы получить список территорий, в которых с начала года было продано менее 5 000 000:

 ВЫБЕРИТЕ ID территории
ОТ   Sales.SalesTerritory
ГДЕ Продажи с начала года < 5000000 

Это возвращает 2,3,5,7,8 в виде списка значений.

Шаг 2: Теперь, когда у нас есть список значений, мы можем подставить их в оператор IN:

 ВЫБЕРИТЕ ОТЛИЧНЫЙ ИД клиента
ОТ   Sales.SalesOrderHeader
ГДЕ TerritoryID В (  2,3,5,7,8  ) 

Опять же, мы могли бы просто разбить это на несколько шагов, но недостаток в том, что нам пришлось бы постоянно обновлять список территорий.

Теперь, когда вы увидели, как мы используем подзапрос в операторе SQL, давайте рассмотрим их использование в предложении WHERE.

Подзапросы в предложении SELECT

Запросы, используемые в списке столбцов, должны возвращать одно значение. В этом случае вы можете думать о подзапросе как о выражении с одним значением. Возвращаемый результат ничем не отличается от выражения «2 + 2». Конечно, подзапросы также могут возвращать текст, но суть вы поняли!

Основной оператор SQL называется внешним запросом; он вызывает подзапрос. Заключите подзапросы в круглые скобки, это обязательно, но также облегчает их обнаружение.

Будьте осторожны при использовании подзапросов. Их может быть интересно использовать, но по мере того, как вы добавляете больше к своему запросу, они могут начать замедлять ваш запрос.

Простой подзапрос для вычисления среднего значения

Давайте начнем с простого запроса, чтобы показать SalesOrderDetail и сравнить его с общим средним значением SalesOrderDetail LineTotal. Оператор SELECT, который мы будем использовать:

 ВЫБЕРИТЕ SalesOrderID,
LineTotal,
(ВЫБЕРИТЕ СРЕДНЕЕ(СуммаЛинии)
FROM Sales.SalesOrderDetail) AS AverageLineTotal
ОТ   Sales.SalesOrderDetail; 

Этот запрос возвращает результаты как:

Средняя сумма строки вычисляется с помощью подзапроса, показанного красным цветом.

 ВЫБЕРИТЕ СРЕДНЕЕ(СуммаЛинии)
ОТ   Sales.SalesOrderDetail 

Затем этот результат снова вставляется в список столбцов, и запрос продолжается.

Наблюдения

Есть несколько вещей, на которые я хочу обратить внимание:

  1. SQL требует, чтобы подзапросы заключались в круглые скобки.
  2. Подзапросы, используемые в SELECT, могут возвращать только одно значение. Это должно иметь смысл, простой выбор столбца возвращает одно значение для строки, и нам нужно следовать тому же шаблону.
  3. Как правило, подзапрос выполняется только один раз для всего запроса, а его результат используется повторно. Это связано с тем, что результат запроса не меняется для каждой возвращаемой строки.
  4. Важно использовать псевдонимы для имен столбцов, чтобы улучшить читаемость.

Простой подзапрос в выражении

Результат подзапроса можно использовать в других выражениях. Опираясь на предыдущий пример, давайте воспользуемся подзапросом, чтобы определить, насколько наш LineTotal отличается от среднего.

Дисперсия - это просто общая сумма строк минус средняя сумма строк. В следующем подзапросе я выделил его синим цветом. Вот формула дисперсии:

 LineTotal - (ВЫБРАТЬ AVG(LineTotal)
               ОТ   Sales.SalesOrderDetail) 

Оператор SELECT, заключенный в круглые скобки, является подзапросом. Как и в предыдущем примере, этот запрос выполняется один раз, возвращает числовое значение, которое затем вычитается из каждого значения LineTotal.

Вот запрос в окончательной форме:

 ВЫБЕРИТЕ SalesOrderID,
       LineTotal,
       (ВЫБЕРИТЕ СРЕДНЕЕ(СуммаЛинии)
          ОТ   Sales.SalesOrderDetail) AS AverageLineTotal,
       LineTotal - (SELECT AVG(LineTotal)
ОТ   Sales.SalesOrderDetail) AS Отклонение
ОТ   Sales.SalesOrderDetail 

Вот результат:

При работе с подзапросами в операторах select я обычно сначала создаю и тестирую подзапрос SQL. Сделайте это, чтобы устранить неполадки, прежде чем собирать сложный запрос! Лучше наращивать их постепенно. Создавая и тестируя различные части по отдельности, это действительно помогает при отладке.

Подробнее: Коррелированные подзапросы>>

Подзапросы в предложении WHERE

Давайте посмотрим, как написать подзапрос в предложении where. Для этого мы будем использовать оператор IN, чтобы проверить, является ли значение частью результата, возвращаемого подзапросом.

Вот общий формат:

 ВЫБЕРИТЕ столбец1, столбец2,..  
ИЗ таблицы1 
ГДЕ столбец2 В (подзапрос1) 

Пример поясняет это. Предположим, мы хотим найти все заказы на продажу, сделанные продавцом с бонусом более 5000 долларов.

Для этого напишем два запроса. Внешний для поиска заказов на продажу и внутренний (подзапрос SQL) для возврата SalesPersonID для получателей бонусов на сумму 5000 долларов США и выше.

Я выделил подзапрос цветом, чтобы вы могли его легко увидеть: 

 ВЫБЕРИТЕ SalesOrderID
               ,Дата заказа
               ,Номер счета
               ,Пользовательский ИД
               ,SalesPersonID
               ,TotalDue
ИЗ Sales.SalesOrderHeader
ГДЕ SalesPersonID IN (ВЫБЕРИТЕ BusinessEntityID
ОТ Sales.SalesPerson
ГДЕ Бонус > 5000
) 

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

Подробнее: Использование операторов Where EXISTS в SQL >>, SQL ANY и ВСЕ операторы >>

Подзапросы в пункте FROM

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

В этом примере мы собираемся выбрать территории и их средние бонусы.

 ВЫБЕРИТЕ ID территории,
       Средний бонус
ОТ   (ВЫБЕРИТЕ  TerritoryID,
Avg(Bonus) AS Средний бонус
ОТ     Sales.SalesPerson
СГРУППИРОВАТЬ ПО TerritoryID) AS TerritorySummary
ЗАКАЗАТЬ ПО MediumBonus 

Псевдоним подзапроса — TerritorySummary, он выделен красным цветом.

Присоединение к подзапросу

При выполнении этого запроса первым запускается подзапрос и создаются результаты. Затем результаты используются в предложении FROM, как если бы это была таблица. Когда эти типы запросов используются сами по себе, они не очень интересны; однако при использовании в сочетании с другими таблицами они таковы.

Давайте добавим соединение к нашему примеру выше.

 ВЫБЕРИТЕ SP.TerritoryID,
SP.BusinessEntityID,
СП.Бонус,
TerritorySummary. AverageBonus
ОТ   (ВЫБЕРИТЕ  TerritoryID,
AVG(Bonus) AS Средний бонус
ОТ     Sales.SalesPerson
СГРУППИРОВАТЬ ПО TerritoryID) AS TerritorySummary
ВНУТРЕННЕЕ СОЕДИНЕНИЕ
Sales.SalesPerson AS SP
ON SP.TerritoryID = TerritorySummary.TerritoryID
 

Подробнее: В чем разница между соединением и подзапросом?

Связи таблиц с запросом

С точки зрения моделирования данных этот запрос выглядит следующим образом:

Существует связь между TerritorySummary и присоединенной таблицей SalesPerson. Конечно, TerritorySummary существует только как часть этой инструкции SQL, поскольку она является производной.

Используя производные таблицы, мы можем подводить итоги, используя один набор полей, и сообщать о другом. В этом случае мы суммируем по TerritoryID, но сообщаем по каждому продавцу (BusinessEntityID).

Вы можете подумать, что можете просто реплицировать этот запрос с помощью ВНУТРЕННЕГО СОЕДИНЕНИЯ, но вы не можете этого сделать, так как окончательный GROUP BY для запроса должен включать BusinessEntityID, что затем отбрасывает вычисление среднего.

Подробнее: Производные таблицы >>

Подзапросы в предложении HAVING

Вы можете использовать подзапросы в предложении SQL HAVING для фильтрации групп записей. Точно так же, как предложение WHERE используется для фильтрации строк записей, предложение HAVING используется для фильтрации групп. Из-за этого он становится очень полезным при фильтрации совокупных значений, таких как средние значения, суммирование и количество.

Преимущество использования подзапроса в предложении HAVING заключается в том, что теперь вам не нужно жестко кодировать значения в сравнениях. Вы можете положиться на результаты подзапроса, чтобы сделать это за вас.

Например, теперь можно сравнивать среднее значение группы с общим средним значением. Мы всегда могли использовать среднее значение группы в предложении HAVING, но не могли вычислить общее среднее значение. Теперь, используя подзапросы, это возможно.

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

Здесь я написал запрос без использования подзапроса

 ВЫБЕРИТЕ   JobTitle,
         AVG(Часы отпуска) AS AverageЧасы отпуска
ОТ     HumanResources.Employee
СГРУППИРОВАТЬ ПО Должности
HAVING   AVG(часы отпуска) > 50 

Подзапрос заменяет текст, выделенный красным. Теперь вот полный оператор, включая подзапрос:

 ВЫБЕРИТЕ   JobTitle,
         AVG(Часы отпуска) AS AverageЧасы отпуска
ОТ     HumanResources.Employee
СГРУППИРОВАТЬ ПО Должности
ИМЕЕТ   AVG(часы отпуска) > (ВЫБРАТЬ AVG(часы отпуска)
                               ОТ HumanResources.Employee) 

Этот запрос выполняется как:

  1. Рассчитайте оставшееся среднее количество часов отпуска для всех сотрудников. (подзапрос)
  2. Групповые записи по JobTitle и компьютеру о среднем количестве часов отпуска.
  3. Оставляйте только те группы, среднее количество часов отпуска которых превышает общее среднее значение.

Повторное рассмотрение важных моментов

  • Подзапрос — это просто оператор SELECT внутри другого.
  • Заключите подзапросы в круглые скобки () .
  • Псевдоним подзапросов в списке столбцов, чтобы упростить чтение и ссылки на результаты.
  • Подзапрос возвращает одно значение или таблицу. Помните о том, какой тип возвращает ваш запрос, чтобы убедиться, что он работает правильно в данной ситуации.
  • Подзапрос, который возвращает более одного значения, обычно используется там, где список значений, например, используется в операторе и IN.
  • Внимание! Подзапросы могут быть очень неэффективными. Если есть более прямые средства для достижения того же результата, например, использование внутреннего соединения, вам лучше.
  • Вы можете вкладывать подзапросы до тридцати двух уровней в глубину на сервере SQL. Честно говоря, я не могу представить, почему! Я вижу максимум на четыре глубины. На самом деле я думаю, что вы углубитесь только в один или два раза.

Не волнуйтесь, если сейчас многое кажется запутанным. Вам все еще нужно узнать, где использовать подзапросы в операторе SELECT.