Пример 9: вычисление среднего значения агрегированных данных
Есть ещё одна проблема с разницами дат, о которой стоит помнить: в повседневной жизни мы привыкли округлять эти значения, в то время как СУБД этой операции не выполняет. Проверьте себя: сколько лет прошло между 2011-01-01 и 2012-01-01? Один год, верно? А между 2011-01-01 и
2012-12-31?
Исследование 2.1.9.EXP.A. Проверка поведения СУБД при вычислении расстояния между двумя датами в годах.
MySQL Исследование 2.1.9.EXP.A
1SELECT YEAR('2012-01-01') - YEAR('2011-01-01') - (
2DATE_FORMAT('2012-01-01', '%m%d') <
3DATE_FORMAT('2011-01-01', '%m%d'))
1SELECT YEAR('2012-12-31') - YEAR('2011-01-01') - (
2DATE_FORMAT('2012-12-31', '%m%d') <
3DATE_FORMAT('2011-01-01', '%m%d'))
MS SQL Исследование 2.1.9.EXP.A
1 SELECT DATEDIFF(year, '2011-01-01', '2012-01-01')
1 SELECT DATEDIFF(year, '2011-01-01', '2012-12-31')
Oracle Исследование 2.1.9.EXP.A
1SELECT FLOOR(MONTHS_BETWEEN(DATE '2012-01-01', DATE '2011-01-01') / 12)
2FROM dual;
1SELECT FLOOR(MONTHS_BETWEEN(DATE '2012-12-31', DATE '2011-01-01') / 12)
2FROM dual;
Все шесть запросов 2.1.6.EXP.A вернут один и тот же результат: 1, т.е. между указанными датами с точки зрения СУБД прошёл один год. И это правда: прошёл «один полный год».
Также обратите внимание, насколько по-разному решается задача вычисления разницы между датами в годах в различных СУБД. Особенно интересны строки 2-3 в запросах для MySQL: они позволяют получить корректный результат, когда значения года различны, но на самом деле год ещё не прошёл (например, 2011-05-
01 и 2012-04-01).
Задание 2.1.9.TSK.A: показать, сколько в среднем экземпляров книг есть в библиотеке.
Задание 2.1.9.TSK.B: показать в днях, сколько в среднем времени читатели уже зарегистрированы в библиотеке (временем регистрации считать диапазон от первой даты получения читателем книги до текущей даты).
Работа с MySQL, MS SQL Server и Oracle в примерах © EPAM Systems, RD Dep, 2016–2018 Стр: 60/545
Пример 10: использование группировки данных
2.1.10.Пример 10: использование группировки данных
Задача 2.1.10.a{61}: показать по каждому году, сколько раз в этот год читатели брали книги.
Задача 2.1.10.b{62}: показать по каждому году, сколько читателей в год воспользовалось услугами библиотеки.
Задача 2.1.10.c{63}: показать, сколько книг было возвращено и не возвращено в библиотеку.
Ожидаемый результат 2.1.10.a.
year |
books_taken |
|
2011 |
2 |
|
2012 |
3 |
|
2014 |
3 |
|
2015 |
3 |
|
|
Ожидаемый результат 2.1.10.b. |
|
year |
subscribers |
|
2011 |
1 |
|
2012 |
3 |
|
2014 |
2 |
|
2015 |
2 |
|
|
Ожидаемый результат 2.1.10.c. |
|
status |
books |
Returned |
6 |
Not returned |
5 |
Как следует из названия главы, все три задачи будут решаться с помощью
группировки данных, т.е. использования GROUP BY.
Решение 2.1.10.a{61}.
MySQL |
Решение 2.1.10.a |
|
|
||
1 |
SELECT |
YEAR(`sb_start`) AS |
`year`, |
||
2 |
|
|
COUNT(`sb_id`) |
AS |
`books_taken` |
3 |
FROM |
`subscriptions` |
|
|
|
4 |
GROUP |
BY `year` |
|
|
|
5 |
ORDER |
BY `year` |
|
|
|
|
|
|
|
||
MS SQL |
Решение 2.1.10.a |
|
|
||
1 |
SELECT |
YEAR([sb_start]) AS |
[year], |
||
2 |
|
|
COUNT([sb_id]) |
AS |
[books_taken] |
3 |
FROM |
[subscriptions] |
|
|
|
4 |
GROUP |
BY YEAR([sb_start]) |
|
||
5 |
ORDER |
BY [year] |
|
|
|
Работа с MySQL, MS SQL Server и Oracle в примерах © EPAM Systems, RD Dep, 2016–2018 Стр: 61/545
Пример 10: использование группировки данных
Oracle |
|
Решение 2.1.10.a |
|
|
|
1 |
SELECT |
EXTRACT(year FROM "sb_start") AS |
"year", |
||
2 |
|
|
COUNT("sb_id") |
AS |
"books_taken" |
3 |
FROM |
"subscriptions" |
|
|
|
4 |
GROUP |
BY EXTRACT(year FROM "sb_start") |
|
||
5 |
ORDER |
BY "year" |
|
|
|
|
|
|
|
|
|
Обратите внимание на разницу в 4-й строке в запросах 2.1.10.a: MySQL позволяет в GROUP BY сослаться на имя только что вычисленного выражения, в то время как MS SQL Server и Oracle не позволяют этого сделать.
Ранее мы уже рассматривали логику работы группировок{20}, но подчеркнём это ещё раз. После извлечения значения года и «объединения ячеек по признаку равенства значений» полученный СУБД результат условно можно представить так:
year |
результат группировки |
сколько ячеек объединено |
|
2011 |
2011 |
2 |
|
2011 |
|||
|
|
||
2012 |
|
|
|
2012 |
2012 |
3 |
|
2012 |
|
|
|
2014 |
|
|
|
2014 |
2014 |
3 |
|
2014 |
|
|
|
2015 |
|
|
|
2015 |
2015 |
3 |
|
2015 |
|
|
Решение 2.1.10.b{61}.
MySQL |
Решение 2.1.10.b |
|
||
1 |
SELECT |
YEAR(`sb_start`) |
AS `year`, |
|
2 |
|
|
COUNT(DISTINCT `sb_subscriber`) AS `subscribers` |
|
3 |
FROM |
`subscriptions` |
|
|
4 |
GROUP |
BY `year` |
|
|
5 |
ORDER |
BY `year` |
|
|
|
|
|
||
MS SQL |
Решение 2.1.10.b |
|
||
1 |
SELECT |
YEAR([sb_start]) |
AS [year], |
|
2 |
|
|
COUNT(DISTINCT [sb_subscriber]) AS [subscribers] |
|
3 |
FROM |
[subscriptions] |
|
|
4 |
GROUP |
BY YEAR([sb_start]) |
|
|
5 |
ORDER |
BY [year] |
|
|
|
|
|
|
|
Oracle |
|
Решение 2.1.10.b |
|
|
1 |
SELECT |
EXTRACT(year FROM "sb_start") |
AS "year", |
|
2 |
|
|
COUNT(DISTINCT "sb_subscriber") AS "subscribers" |
|
3 |
FROM |
"subscriptions" |
|
|
4 |
GROUP |
BY EXTRACT(year FROM "sb_start") |
|
|
5 |
ORDER |
BY "year" |
|
|
|
|
|
|
|
Работа с MySQL, MS SQL Server и Oracle в примерах © EPAM Systems, RD Dep, 2016–2018 Стр: 62/545
Пример 10: использование группировки данных
Решение 2.1.10.b{62} очень похоже на решение 2.1.10.a{61}: разница лишь в том, что в первом случае нас интересуют все записи за каждый год, а во втором — количество идентификаторов читателей без повторений за каждый год. Покажем это графически:
year |
sb_subscriber |
группировка по году |
число уникальных id читателя |
|
2011 |
1 |
2011 |
1 |
|
2011 |
1 |
|||
|
|
|||
2012 |
1 |
|
|
|
2012 |
3 |
2012 |
3 |
|
2012 |
4 |
|
|
|
2014 |
1 |
|
|
|
2014 |
3 |
2014 |
2 |
|
2014 |
3 |
|
|
|
2015 |
1 |
|
|
|
2015 |
4 |
2015 |
2 |
|
2015 |
4 |
|
|
Решение 2.1.10.c{61}.
MySQL |
Решение 2.1.10.c |
|
||
1 |
SELECT |
IF(`sb_is_active` = 'Y', 'Not returned', 'Returned') AS `status`, |
||
2 |
|
|
COUNT(`sb_id`) |
AS `books` |
3 |
FROM |
`subscriptions` |
|
|
4 |
GROUP |
BY `status` |
|
|
5 |
ORDER |
BY `status` DESC |
|
|
|
|
|
|
|
MS SQL Решение 2.1.10.c
1SELECT (CASE
2WHEN [sb_is_active] = 'Y'
3THEN 'Not returned'
4ELSE 'Returned'
5 |
|
|
END) |
AS [status], |
6 |
|
|
COUNT([sb_id]) AS [books] |
|
7 |
|
FROM |
[subscriptions] |
|
8 |
|
GROUP |
BY (CASE |
|
9 |
|
|
|
WHEN [sb_is_active] = 'Y' |
10 |
|
|
|
THEN 'Not returned' |
11 |
|
|
|
ELSE 'Returned' |
12 |
|
|
END) |
|
13 |
|
ORDER |
BY [status] DESC |
|
Oracle Решение 2.1.10.c
1SELECT (CASE
2WHEN "sb_is_active" = 'Y'
3THEN 'Not returned'
4ELSE 'Returned'
5 |
|
|
END) |
AS "status", |
6 |
|
|
COUNT("sb_id") |
AS "books" |
7 |
|
FROM |
"subscriptions" |
|
8 |
|
GROUP |
BY (CASE |
|
9 |
|
|
WHEN "sb_is_active" = 'Y' |
|
10 |
|
|
THEN 'Not |
returned' |
11 |
|
|
ELSE 'Returned' |
|
12 |
|
|
END) |
|
|
|
|
|
|
13ORDER BY "status" DESC
Врешении 2.1.10.c есть одна сложность: нужно на основе значений поля sb_is_active Y (книга на руках, т.е. не возвращена) и N (книга возвращена) полу-
чить человекочитаемые осмысленные надписи «Not returned» и «Returned».
Работа с MySQL, MS SQL Server и Oracle в примерах © EPAM Systems, RD Dep, 2016–2018 Стр: 63/545
Пример 10: использование группировки данных
В MySQL все необходимые действия помещаются в одну строку (строка 1), а благодаря способности MySQL ссылаться на имя вычисленного выражения в
GROUP BY, нет необходимости повторять всю эту конструкцию в строке 4.
MS SQL Server и Oracle поддерживают одинаковый, но чуть более громоздкий синтаксис: строки 1-5 запросов для этих СУБД содержат необходимые преобразования, а дублирование этого же кода в строках 8-12 вызвано тем, что эти СУБД
не позволяют в GROUP BY сослаться на вычисленное выражение по его имени.
И снова покажем графически, с какими данными приходится работать СУБД:
sb_is_active |
status |
группировка |
подсчёт |
|
(исходное значение) |
(результат преобразования) |
|
|
|
N |
Returned |
|
|
|
N |
Returned |
|
|
|
N |
Returned |
Returned |
6 |
|
N |
Returned |
|||
|
|
|||
N |
Returned |
|
|
|
N |
Returned |
|
|
|
Y |
Not returned |
|
|
|
Y |
Not returned |
|
|
|
Y |
Not returned |
Not returned |
5 |
|
Y |
Not returned |
|
|
|
Y |
Not returned |
|
|
Исследование 2.1.10.EXP.A. Как быстро, в принципе, работает группировка на больших объёмах данных? Используем базу данных «Большая библиотека» и посчитаем, сколько книг находится на руках у каждого читателя.
MySQL Исследование 2.1.10.EXP.A
1SELECT `sb_subscriber`,
2COUNT(`sb_id`) AS `books_taken`
3 |
|
FROM |
`subscriptions` |
4 |
|
WHERE |
`sb_is_active` = 'Y' |
5 |
|
GROUP |
BY `sb_subscriber` |
|
|
|
|
MS SQL Исследование 2.1.10.EXP.A
1SELECT [sb_subscriber],
2COUNT([sb_id]) AS [books_taken]
3 |
|
FROM |
[subscriptions] |
4 |
|
WHERE |
[sb_is_active] = 'Y' |
5 |
|
GROUP |
BY [sb_subscriber] |
|
|
|
|
Oracle Исследование 2.1.10.EXP.A
1SELECT "sb_subscriber",
2COUNT("sb_id") AS "books_taken"
3 FROM "subscriptions"
4WHERE "sb_is_active" = 'Y'
5GROUP BY "sb_subscriber"
Медианы времени после выполнения по сто раз каждого из запросов
2.1.10.EXP.A:
MySQL |
MS SQL Server |
Oracle |
34.333 |
8.189 |
2.306 |
С одной стороны, результаты не выглядят пугающе, но, если объём данных увеличить в 10, 100, 1000 раз и т.д. — время выполнения уже будет измеряться часами или даже днями.
Работа с MySQL, MS SQL Server и Oracle в примерах © EPAM Systems, RD Dep, 2016–2018 Стр: 64/545