Конструкция оператора SELECT
В SQL, оператор SELECT
состоит из блоков, определяющих детали выражения.
В MySQL единственным обязательным блоком является первый: SELECT
.
Блоки оператора SELECT
:
SELECT
– указывает, что должно быть выбрано. Отчасти соответствует операциям проекции и переименования реляционной алгебрыFROM
– указывает, откуда должны быть выбраны данные. Соответствует аргументу операции реляционной алгебры.WHERE
– указывает условие, которому выбранные данные должны удовлетворять. Соответствует операции выборки реляционной алгебрыGROUP BY
– используется для группировки результатов по одинаковым значениям столбцов. Часто используется со статистическими запросами.HAVING
– аналогиченWHERE
, но используется со статистическими запросами (WHERE
с ними не работает)ORDER BY
– сортировка выдачи.
Блок SELECT
Как проекция:
SELECT col1, col2, ...
Выбирает перечисленные столбцы. Можно использовать форму SELECT *
для выборки всех столбцов аргумента.
Как переименование:
SELECT col1 as name1, col2 as name2, ...
Выбирает перечисленные столбцы и переименовывает их.
Это базовые формы использования блока SELECT
. Однако, его возможности не ограничиваются только этим. Кроме прочего, можно подставлять значения или функции (включая операторы). Например,
SELECT 'Hello World!' as Hello;
Вернет такой результат:
Hello |
---|
Hello World! |
А если, допустим, есть табличка operands
a | b |
---|---|
1 | 10 |
2 | 15 |
3 | 20 |
Тогда запрос
SELECT a, b, a+b as c FROM operands
вернет
a | b | c |
---|---|---|
1 | 10 | 11 |
2 | 15 | 17 |
3 | 20 | 23 |
Существует множество встроенных в SQL функций, включая функции преобразования типов, работы с временны́ми данными, статистические функции и др.
Блок FROM
Блок FROM
может быть использован для уточнения аргумента блока SELECT
. В простейшем варианте, это название таблицы (отношения).
Кроме того, в соответствии с правилами реляционной алгебры, аргументом FROM
может являться результат другого запроса (подзапрос). Для использование подзапроса, необходимо назначить ему псевдоним:
SELECT a+b FROM (SELECT 1 as a, 2 as b) as tbl1;
Блок FROM
так же используется для реализации соединений и декартовых произведений. Для этого используется бинарный оператор JOIN
.
Пусть дана таблица bin:
a |
---|
0 |
1 |
Тогда запрос
SELECT * FROM bin b1 JOIN bin b2 JOIN bin b3;
вернет декартово произведение bin×bin×bin:
a | a | a |
---|---|---|
0 | 0 | 0 |
1 | 0 | 0 |
0 | 1 | 0 |
1 | 1 | 0 |
0 | 0 | 1 |
1 | 0 | 1 |
0 | 1 | 1 |
1 | 1 | 1 |
Собственно, JOIN
– это оператор декартова произведения. Существуют несколько его вариантов. По умолчанию используется вариант INNER JOIN
. Так же есть OUTER FULL JOIN
, OUTER LEFT JOIN
, OUTER RIGHT JOIN
и NATURAL
-вариатны – о них чуть позже.
Для получения θ-соединения, используется уточнение к JOIN
, называемое ON
, и для всех применений работающее как WHERE
, но в применении только к операндам JOIN
:
SELECT * FROM bin b1 JOIN bin b2 ON b1.a=b2.a;
a | a |
---|---|
0 | 0 |
1 | 1 |
Важно, что оптимизатор БД ожидает в операторе ON
только условия θ-соединения, а в операторе WHERE
только условия выборки по результату.
Принципиально,
SELECT * FROM bin b1 JOIN bin b2 ON b1.a=b2.a;
SELECT * FROM bin b1 JOIN bin b2 WHERE b1.a=b2.a;
дают одинаковые результаты. Однако, второй вариант менее эффективен, поскольку оптимизатор сначала строит полное декартово произведение, и только потом применяет выборку, в то время как в первом варианте, полное декартово произведение не строится вообще.
Блок WHERE
По сути, применяет реляционную операцию выборки.
Например,
SELECT * FROM bin WHERE a>0;
вернет
a |
---|
1 |
Важно, что любые операции переименования применяются после выборки, то есть выражение
SELECT a as b FROM bin WHERE b>0;
является неверным: блок WHERE
не “знает” о переименовании, а “знает” только об аргументе FROM.
Как следствие, блок WHERE
не может быть использован без блока FROM
:
SELECT 1 WHERE TRUE;
не сработает.
Впрочем, если очень хочется, можно использовать “ненастоящую” таблицу dual
.
SELECT 1 FROM dual WHERE TRUE;
сработает.
Таблица dual
является “пустой таблицей”. При попытке выбрать что-либо непосредственно из нее, возвращается ошибка. Однако, она может быть указана в случаях, когда синтаксис SQL требует указания таблицы.
Аргументом WHERE
может быть любое выражение, которое может быть преобразовано в BOOL.
Особого рассмотрения требует значение NULL. Во-первых, любая операция с NULL возвращает NULL. Во-вторых, при преобразовании NULL в BOOL всегда получается FALSE. Таким образом, NULL=NULL
это FALSE
, NULL!=NULL
это FALSE
, NOT NULL=NULL
это FALSE
. Как же найти значения NULL? Существует два специальных оператора:
x IS NULL
x IS NOT NULL
Первый срабатывает, если значение x
– это NULL, второй – если значение x
– не NULL.
Следует заметить, что никакое значение не равно NULL (включая сам NULL).
Блок GROUP BY
Этот блок позволяет группировать выдачу по значению некого выражения. Группировка означает немного не то что можно было бы подумать. Например,
select * from bin b1 join bin b2 join bin b3 group by b3.a;
вернет
a | a | a |
---|---|---|
0 | 0 | 0 |
0 | 0 | 1 |
Это не совсем то, что можно было бы ожидать.
Смысл группировки в применении статистических или агрегатных функций – эти функции будут применены к каждой из групп.
В частности, функция COUNT
возвращает количество ненулевых (не NULL) элементов множества.
select COUNT(*), b3.a from bin b1 join bin b2 join bin b3 group by b3.a;
COUNT(*) | a |
---|---|
4 | 0 |
4 | 1 |
Агрегатные функции включают:
AVG(x)
– Среднее значениеBIT_AND(x)
– Побитовая конъюнкцияBIT_OR(x)
– Побитовая дизъюнкцияBIT_XOR(x)
– Побитовая строгая дизъюнкцияCOUNT(DISTINCT x)
– Возвращает количество различных значенийCOUNT(x)
– количество значений (возможно одинаковых)GROUP_CONCAT(x)
– Объединяет строки группы (конкатенация)MAX(x)
– Максимальное значение в группеMIN(x)
– Минимальное значение в группеSTDDEV_POP(x)
– Стандартное отклонение популяцииSTDDEV_SAMP(x)
– Стандартное отклонение выборкиSUM(x)
– СуммированиеVAR_POP(x)
– Дисперсия популяцииVAR_SAMP(x)
– Дисперсия выборки
В качестве небольшой справки,
\[V_p(X) = \frac{\sum_{i=1}^n (X_i-X_ \mathrm{avg})^2}{n}\]
\[V_s(X) = \frac{\sum_{i=1}^n (X_i-X_ \mathrm{avg})^2}{n-1}\]
\[S_p(X) = \sqrt{V_p(X)}\]
\[S_s(X) = \sqrt{V_s(X)}\]
Дисперсия популяции считается по всему набору значений, в то время как дисперсия выборки – по некому подмножеству всего набора значений. Вторая является оценкой первой. Разница вычисления существует чтобы не занижать оценку.
Существует особая форма COUNT(*)
, которая получает общее количество записей в выборке. COUNT(x)
учитывает только записи, у которых x
не NULL
. Вообще, значение NULL
агрегатными функциями игнорируется.
Пример:
select COUNT(DISTINCT b1.a), b3.a from bin b1 join bin b2 join bin b3 group by b3.a;
COUNT(DISTINCT b1.a) | a |
---|---|
2 | 0 |
2 | 1 |
select COUNT(DISTINCT b1.a, b2.a), b3.a from bin b1 join bin b2 join bin b3 group by b3.a;
COUNT(DISTINCT b1.a, b2.a) | a |
---|---|
4 | 0 |
4 | 1 |
Возможно указание нескольких выражений в GROUP BY через запятую. Тогда группировка будет осуществляться по значению кортежа перечисленных выражений (то есть, для попадания в одну группу, все значения кортежа должны быть равны)
Следует заметить, что группировка автоматически приводит к сортировке, что может быть нежелательно ввиду того, что сортировка может быть не быстрой. Чтобы этого избежать, можно добавить ORDER BY NULL
в конец выражения.
К GROUP BY
можно добавить модифиактор WITH ROLLUP
, который так же добавит в конец запись, применяющую соответствующие статистические функции ко всем результирующим записям (то есть, по сути, добавит строку “Итого”). Если в параметрах GROUP BY
указано несколько выражений, то WITH ROLLUP
посчитает итог для каждого из выражений по очереди.
Например:
SELECT
AS idClient,
reserves.idClient AS name,
clients.name YEAR(reserves.startTime) AS year,
SUM(RESERVEPRICE(clients.id, tariffs.idCourt,reserves.duration)) AS income
FROM
reservesJOIN clients ON clients.id = reserves.idClient
JOIN tariffs ON clients.isMember = tariffs.isMember
AND reserves.idCourt = tariffs.idCourt
JOIN prices ON tariffs.idPrice = prices.id
GROUP BY reserves.idClient, YEAR(reserves.startTime) WITH ROLLUP;
idClient | name | year | income |
---|---|---|---|
1 | Иванов Иван Иванович | 2015 | 1200.0000 |
1 | Иванов Иван Иванович | NULL | 1200.0000 |
2 | Петров Петр Петрович | 2015 | 2000.0000 |
2 | Петров Петр Петрович | NULL | 2000.0000 |
3 | Сидоров Сергей Сергеевич | 2015 | 2000.0000 |
3 | Сидоров Сергей Сергеевич | NULL | 2000.0000 |
NULL | Сидоров Сергей Сергеевич | NULL | 5200.0000 |
Надо заметить, что в “итоговых” строках, колонки, по которым строится итог, устанавливаются в NULL
. Это, однако не относится к колонкам, по которым нет ни статистики, ни агрегации – там оказывается последнее выражение.
Для использования ROLLUP
данные должны быть отсортированы.
Блок HAVING
Поскольку блок SELECT
применяется после блока WHERE
, подобная конструкция не сработает:
SELECT COUNT(*), b3.a FROM bin b1 JOIN bin b2 JOIN bin b3 GROUP BY b3.a WHERE COUNT(*) < 1;
Для выборки по статистическим функциям, используется блок HAVING
.
Важно, что использование агрегатной функции в SELECT
наличие блока HAVING
автоматически подразумевает GROUP BY NULL
, если не указано иное, то есть все значения попадают в одну группу.
HAVING
нельзя использовать вместо WHERE
, поскольку оператор HAVING
применяется к группам, в то время как WHERE
– к записям.
Блок ORDER BY
Этот блок позволяет сортировать результаты выдачи по каким-то выражениям в возрастающем (ascending) и убывающем (descending) порядке.
Общий синтаксис ORDER BY expr1, expr2 ([ASC]|DESC)
В качестве аргумента может быть использовано одно или несколько выражений, для которых определено сравнение. В частности, это могут быть числовые, строковые или временны́е типы.
При указании нескольких выражений, результат сначала сортируется по первому, потом каждая из групп сортируется по второму, и так далее.
По умолчанию, сортировка производится в возрастающем порядке. Это можно явно указать, добавив спецификатор ASC
. В то же время, возможно указать спецификатор DESC
для сортировки в убывающем порядке.
Например:
select * from bin b1 join bin b2 join bin b3 ORDER BY b1.a, b2.a, b3.a;
a | a | a |
---|---|---|
0 | 0 | 0 |
0 | 0 | 1 |
0 | 1 | 0 |
0 | 1 | 1 |
1 | 0 | 0 |
1 | 0 | 1 |
1 | 1 | 0 |
1 | 1 | 1 |
Построение условий
Условие в блоках WHERE
, JOIN ... ON
и HAVING
строятся при помощи операторов сравнения, булевых операторов, названий столбцов, и встроенных функций. Выражения могут быть заключены в скобки.
Имеется стандартный набор операторов сравнения:
=
– оператор равенства!=
– оператор неравенства<
– оператор “меньше”>
– оператор “больше”<=
– оператор “меньше или равно”>=
– оператор “больше или равно”
и булевых операторов:
NOT
– инверсияAND
– конъюнкцияOR
– дизъюнкцияXOR
– строгая дизъюнкция
Кроме того, есть специальные операторы сравнения:
<=>
– сравнение с учетомNULL
:NULL <=> NULL ≡ TRUE
x <=> NULL ≡ FALSE
,x <=> y ≡ x = y
x BETWEEN low AND high
–x
находится в диапазоне [low
,high
]x IN set
–x
является элементом множестваset
.set
может быть задан как набор значений в скобках через запятую, либо как результат подзапроса.x NOT IN set
– аналогNOT (x IN set)
x LIKE mask
– проверяет соответствие строкиx
некой маскеmask
.x REGEXP regex
– проверяет соответствие строкиx
регулярному выражениюregex
.
Примеры:
дана табличка dates (date DATE)
date |
---|
2001-01-01 |
2010-05-08 |
2015-09-23 |
2016-12-31 |
select * from dates where date between '2010-05-08' and '2016-12-31';
date |
---|
2010-05-08 |
2015-09-23 |
2016-12-31 |
select * from dates where date in ('2015-09-23', '2016-12-31');
date |
---|
2015-09-23 |
2016-12-31 |
select * from dates where date in (select * from dates);
date |
---|
2001-01-01 |
2010-05-08 |
2015-09-23 |
2016-12-31 |
Сравнение с помощью подзапросов
Существует возможность выборки по результатам подзапросов. Для этого в SQL есть несколько специальных операторов. Оператор IN
был рассмотрен в предыдущем разделе.
Существуют операторы, применяемые непосредственно к подзапросу:
ANY
(илиSOME
)ALL
EXISTS
Синтаксис ANY
/ALL
:
SELECT ... WHERE expr op {ANY|ALL} (SELECT ...)
где op
– это оператор сравнения, {ANY|ALL}
– оператор ANY
или ALL
.
Оператор ANY
означает, что expr
удовлетворяет op
хотя бы с одной записью из подзапроса. Оператор ALL
означает, что expr
удовлетворяет op
со всеми записями из подзапроса.
Оператор IN
по сути эквивалентен = ANY
, а NOT IN
– != ALL
.
Cинтаксис EXISTS
:
SELECT ... WHERE EXISTS (SELECT ...)
Если подзапрос возвращает хотя бы одну запись, то EXISTS
возвращает TRUE
. Иначе – FALSE
.
Сравнение строк по маске/регулярному выражению
Сравнение строк по маске осуществляется оператором LIKE
. В маске используются следующие обозначения:
%
– любое количество любых символов_
– ровно один любой символ
Таким образом, например, маска ___-__-__
выберет все строки, состоящие из 9 символов, в которых 4-й и 7-й символы – дефисы. F%
выберет все строки, начинающиеся на F
, а, скажем, %воз%
– все строки, содержащие подстроку воз
.
Сравнение строк по регулярному выражению осуществляется оператором REGEXP
. Рассмотрение синтаксиса регулярных выражений выходит за рамки этого курса, однако несколько примеров можно привести:
^[A-Z]
– строка начинается на буквы сA
поZ
20[0-9]5
– строка содержит какую-либо подстроку из2005
,2015
,2025
, …2095
.a.*z
– строка содержит подстроку, начинающуюся наa
и заканчивающуюсяz
a.z
– строка содержит подстроку, начинающуюся наa
и заканчивающуюсяz
из трех символов
Соответствие символам маски следующее: | Маска | Регулярное выражение | |:——|:———————| | %
| .*
| | _
| .
|
Следует заметить, что для точного соответствия, регулярное выражение должно иметь маркеры начала (^
) и конца ($
) строки, поскольку маска всегда проверяется на совпадение со всей строкой, в то время как регулярное выражение – на совпадение с любой подстрокой.
В общем и целом, регулярные выражения – очень мощный инструмент, однако и очень сложный. В 80-х и начале 90-х годов была популярна шутка, которая в целом отражает суть регулярных выражений:
У Вас есть проблема. Вы решили использовать регулярные выражения для ее решения. Теперь у Вас две проблемы.
Типы соедниений
Как уже говорилось, есть несколько типов соединений
- “Внутреннее”
- Три “внешних”
- “Естественные” варианты
С последним разобраться проще всего. Если Вы пишите NATURAL ... JOIN
, то спецификатор ON
автоматически определяется следующим образом: все атрибуты, имеющие одинаковые имена, должны иметь одинаковые значения.
В общем, в практике стараются избегать NATURAL
-соединений, поскольку они могут приводить к ошибкам за счет “встроенной” неявности.
С “внутренним” соединением тоже должно быть все понятно: это вариант “по умолчанию” и по сути есть выборка по декартову произведению.
Внешних существует три варианта:
LEFT OUTER JOIN ... ON
RIGHT OUTER JOIN ... ON
FULL OUTER JOIN ... ON
Каждое из них включает в результат не только записи, удовлетворяющие условию ON
, но и некоторые другие:
LEFT
включает все записи левой таблицы. Для тех, для которых условиеON
не выполняется, все атрибуты правой таблицы устанавливаются вNULL
RIGHT
включает все записи правой таблицы. Для тех, для которых условиеON
не выполняется, все атрибуты левой таблицы устанавливаются вNULL
FULL
по сути является объединением двух предыдущих.
Следует заметить, что MySQL не поддерживает FULL OUTER JOIN
.
Примеры:
есть две таблички
id | name |
---|---|
1 | Cat1 |
2 | Cat2 |
3 | Cat3 |
id | text | catId |
---|---|---|
1 | ‘Post 1’ | 1 |
2 | ‘Post 2’ | 1 |
3 | ‘Post 3’ | 1 |
4 | ‘Post 4’ | 2 |
5 | ‘Post 5’ | 2 |
select categories.name, count(posts.id) from categories left join posts on catId=categories.id group by categories.id;
name | count(posts.id) |
---|---|
cat1 | 3 |
cat2 | 2 |
cat3 | 0 |
В то время как
select categories.name, count(posts.id) from categories join posts on catId=categories.id group by categories.id;
name | count(posts.id) |
---|---|
cat1 | 3 |
cat2 | 2 |
Ограничение числа записей
MySQL имеет расширение LIMIT
, позволяющее ограничить число записей, выбираемых оператором SELECT
. Синтаксис такой:
SELECT ... LIMIT max_num OFFSET shift
max_num
– максимальное количество выбираемых записей, shift
– количество записей, которые надо пропустить.
Это расширение доступно не везде. MS SQL Server имеет аналогичный синтаксис SELECT TOP
, а Oracle использует атрибут ROWNUM
.
Объединение
Можно объединить результаты нескольких запросов, что будет соответствовать операции объединения реляционной алгебры.
Для объединения, все объединяемые запросы должны иметь одинаковое количество колонок. Названия колонок будут взяты из первого запроса. Типы будут взяты либо из первого запроса, либо будут автоматически приведены к “общему знаменателю”.
Существует два типа объединения: DISTINCT
и ALL
. В случае первого, в объединении будут только различные записи, то есть, повторяющиеся записи будут удалены. В случае второго, все записи будут включены в результат.
Синтаксис:
SELECT ...
UNION
SELECT ...
SELECT ...
UNION DISTINCT
SELECT ...
SELECT ...
UNION ALL
SELECT ...
Просто UNION
и UNION DISTINCT
– синонимы.
Условия в выражениях SQL
Выражения SQL допускают ветвление по условию. Оно несколько отличается от более привычного процедурного ветвления (как, например, в Java или C++) тем, что результатом ветвления является значение. Реализуется оно при помощи выражений CASE
и IF
.
CASE
позволяет проверять несколько условий на истинность, и в зависимости от них возвращает некое выражение. Общий синтаксис:
CASE expr
WHEN expr_comp_1 THEN expr_res_1
WHEN ...
ELSE expr_res_n
END
либо
CASE
WHEN cond_1 THEN expr_res_1
WHEN ...
ELSE expr_res_n
END
Здесь expr
– это некое выражение, которое сравнивается с expr_comp_*
, и, при совпадении, получается expr_res_*
. ELSE
срабатывает, если других совпадений не найдено.
Если подходящего условия не найдено, и отсутствует блок ELSE
, то результатом будет NULL
.
Пример:
SELECT (CASE 1
WHEN 0 THEN 'YES'
WHEN 2 THEN 'YES'
ELSE 'NO'
END) AS value;
вернет
value |
---|
NO |
А, скажем,
SELECT (CASE
WHEN NOW() > '1999-12-31' THEN 'Third'
ELSE 'Second'
END) AS Millenium;
вернет
Millenium |
---|
Third |
Второй условный оператор – это IF
. Имеет очень простой синтаксис:
IF(cond, expr_true, expr_false);
Если условие cond
истинно, то результатом будет expr_true
, иначе – expr_false
.
Так же для удобства есть варианты IFNULL
и NULLIF
:
IFNULL(expr1, expr2)
возвращает expr1
, если оно не NULL
, иначе – expr2
.
NULLIF(expr1, expr2)
возвращает NULL
, если expr1=expr2
, иначе возвращает expr1
.
CASE
и IF
часто используют для составления так называемых “перекрестных запросов” – запросов, в которых значения используются в качестве колонок. В новых стандартах SQL существует операция PIVOT
, которая делает примерно то же самое, однако она пока поддерживается не везде.
Пример с IF
:
SELECT
AS idClient,
reserves.idClient AS name,
clients.name YEAR(reserves.startTime) AS year,
SUM(NULLIF(MONTH(reserves.startTime) = 1, RESERVEPRICE(clients.id, tariffs.idCourt,reserves.duration), NULL)) AS `1`,
SUM(IF(MONTH(reserves.startTime) = 2, RESERVEPRICE(clients.id, tariffs.idCourt,reserves.duration), NULL)) AS `2`,
SUM(IF(MONTH(reserves.startTime) = 3, RESERVEPRICE(clients.id, tariffs.idCourt,reserves.duration), NULL)) AS `3`,
SUM(IF(MONTH(reserves.startTime) = 4, RESERVEPRICE(clients.id, tariffs.idCourt,reserves.duration), NULL)) AS `4`,
SUM(IF(MONTH(reserves.startTime) = 5, RESERVEPRICE(clients.id, tariffs.idCourt,reserves.duration), NULL)) AS `5`,
SUM(IF(MONTH(reserves.startTime) = 6, RESERVEPRICE(clients.id, tariffs.idCourt,reserves.duration), NULL)) AS `6`,
SUM(IF(MONTH(reserves.startTime) = 7, RESERVEPRICE(clients.id, tariffs.idCourt,reserves.duration), NULL)) AS `7`,
SUM(IF(MONTH(reserves.startTime) = 8, RESERVEPRICE(clients.id, tariffs.idCourt,reserves.duration), NULL)) AS `8`,
SUM(IF(MONTH(reserves.startTime) = 9, RESERVEPRICE(clients.id, tariffs.idCourt,reserves.duration), NULL)) AS `9`,
SUM(IF(MONTH(reserves.startTime) = 10, RESERVEPRICE(clients.id, tariffs.idCourt,reserves.duration), NULL)) AS `10`,
SUM(IF(MONTH(reserves.startTime) = 11, RESERVEPRICE(clients.id, tariffs.idCourt,reserves.duration), NULL)) AS `11`,
SUM(IF(MONTH(reserves.startTime) = 12, RESERVEPRICE(clients.id, tariffs.idCourt,reserves.duration), NULL)) AS `12`,
FROM
reservesJOIN clients ON clients.id = reserves.idClient
JOIN tariffs ON clients.isMember = tariffs.isMember
AND reserves.idCourt = tariffs.idCourt
JOIN prices ON tariffs.idPrice = prices.id
GROUP BY reserves.idClient , YEAR(reserves.startTime);
idClient | name | year | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | Иванов Иван Иванович | 2015 | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | 1200.0000 | NULL | NULL |
2 | Петров Петр Петрович | 2015 | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | 2000.0000 | NULL | NULL |
3 | Сидоров Сергей Сергеевич | 2015 | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | 2000.0000 | NULL | NULL | NULL |
Пример с использованием PIVOT
SELECT idClient, name, year, `1`, `2`, `3`, `4`, `5`, `6`, `7`, `8`, `9`, `10`, `11`, `12` FROM
SELECT reserves.idClient AS idClient,
(AS name,
clients.name YEAR(reserves.startTime) AS year,
MONTH(reserves.startTime) AS month,
id, tariffs.idCourt,reserves.duration) as cost
RESERVEPRICE(clients.AS p
)
PIVOT
(SUM(cost)
FOR month IN
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 )
( AS pvt; )
INTERSECT и EXCEPT
Стандарт SQL предполагает еще два оператора работы с множествами, помимо UNION
/UNION ALL
.
INTERSECT
строит пересечение двух множеств, а EXCEPT
– разность.
Однако, MySQL не поддерживает эти операторы.
Тем не менее, INTERSECT
по сути представляет собой NATURAL INNER JOIN
, поскольку выбираются только записи с совпадающими атрибутами, а EXCEPT
можно записать как выборку по NATURAL LEFT JOIN
:
SELECT a.* FROM a NATURAL LEFT JOIN b WHERE b.pk IS NULL
где b.pk
– первичный ключ таблицы b
(который, ясно, может быть NULL только в результате OUTER JOIN
).
Так же это можно реализовать через подзапросы и EXISTS
/NOT EXISTS
, однако это в большинстве случаев будет менее эффективно.