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

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

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

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

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

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

Синтаксис

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