Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Изучаем.SQL

.pdf
Скачиваний:
61
Добавлен:
19.03.2016
Размер:
980.64 Кб
Скачать

|10 |

|10 |

|10 |

|10 |

|10 |

|10 |

|10 |

|13 |

|13 |

|13 |

|16 |

|16 |

|16 |

|16 |

|16 |

|

16 |

+ +

24 rows in set (0.01 sec)

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

Вместо этого можно с помощью блока group by (группировать по) попро сить сервер БД сгруппировать данные. Вот тот же запрос, но с примене нием блока group by для группировки данных о счетах по ID сотрудника:

mysql> SELECT open_emp_id

> FROM account

> GROUP BY open_emp_id;

+ + | open_emp_id | + +

|1 |

|10 |

|13 |

|

16

|

+ +

4

rows in set

(0.00 sec)

Результирующий набор содержит по одной строке для каждого от дельного значения столбца open_emp_id, что в результате дает четыре, а не 24 строки. Этот результирующий набор получился меньшим, по тому что каждый из четырех сотрудников открыл больше одного сче та. Чтобы увидеть, сколько счетов открыл каждый сотрудник, в блоке select можно подсчитать количество строк в каждой группе с помо щью агрегатной функции (aggregate function):

mysql> SELECT open_emp_id, COUNT(*) how_many

> FROM account

> GROUP BY open_emp_id;

+ + + | open_emp_id | how_many | + + +

|

1

|

8

|

|

10

|

7

|

|

13

|

3

|

|

16

|

6

|

+ + + 4 rows in set (0.00 sec)

Агрегатная функция count() подсчитывает количество строк в каждой группе, а звездочка предписывает серверу сосчитать все строки в груп пе. Сочетание блока group by и функции обобщения count() позволяет формировать именно те данные, которые требуются для ответа на при кладной вопрос, без необходимости просматривать необработанные данные.

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

mysql>

SELECT open_emp_id, COUNT(*) how_many

>

FROM account

>

WHERE COUNT(*) > 4

>

GROUP BY open_emp_id, product_cd;

ERROR 1111 (HY000): Invalid use of group function

Агрегатную функцию count(*) нельзя использовать в блоке where, по тому что на момент вычисления блока where группы еще не сформиро ваны. Вместо этого можно поместить условия фильтрации группы в блок having. Вот пример того же запроса с блоком having:

mysql> SELECT open_emp_id, COUNT(*) how_many

> FROM account

> GROUP BY open_emp_id> HAVING COUNT(*) > 4;

+ + + | open_emp_id | how_many | + + +

|

1

|

8

|

|

10

|

7

|

|

16

|

6

|

+ + + 3 rows in set (0.00 sec)

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

Агрегатные функции

Агрегатные функции осуществляют определенную операцию над все ми строками группы. Хотя у всех серверов БД есть собственные набо ры специализированных агрегатных функций, большинством из них реализованы следующие общие агрегатные функции:

Max()

Возвращает максимальное значение из набора.

Min()

Возвращает минимальное значение из набора.

Avg()

Возвращает среднее значение набора.

Sum()

Возвращает сумму значений из набора.

Count()

Возвращает количество значений в наборе.

Вот запрос, использующий все обычные агрегатные функции для ана лиза доступных остатков (available balance) всех текущих счетов:

mysql>

SELECT MAX(avail_balance) max_balance,

>

MIN(avail_balance) min_balance,

>

AVG(avail_balance) avg_balance,

>

SUM(avail_balance) tot_balance,

>

COUNT(*) num_accounts

>

FROM account

>

WHERE product_cd = 'CHK';

+ + + + + + | max_balance | min_balance | avg_balance | tot_balance | num_accounts | + + + + + + | 385527.05 | 122.37 | 7300.800985 | 73008.01 | 10 | + + + + + + 1 row in set (0.09 sec)

Результаты этого запроса сообщают о том, что из десяти текущих сче тов таблицы account максимальный остаток составляет 38 552,05 дол ларов, минимальный остаток – 122,37 долларов, средний остаток – 7 300,80 долларов, а общий остаток (баланс) по всем десяти счетам – 73 008,01 долларов. Надеюсь, теперь роль данных агрегатных функ ций вам ясна; возможности применения этих функций подробно рас смотрены в следующих разделах.

Сравнение неявных и явных групп

В предыдущем примере все значения, возвращаемые по запросу, фор мируются агрегатной функцией, а сами агрегатные функции применя ются к группе строк, определенной условием фильтрации product_cd =

'CHK'. Поскольку блок group by отсутствует, имеется единственная не явная группа (все возвращенные запросом строки).

Однако в большинстве случаев потребуется извлекать и другие столбцы, а не только сформированные агрегатными функциями. Что если, к при меру, заставить предыдущий запрос выполнить эти же пять агрегатных функций для каждого типа счетов, а не только для текущих счетов? Для такого запроса пришлось бы извлекать столбец product_cd в дополнение к столбцам, сформированным пятью агрегатными функциями:

SELECT product_cd,

MAX(avail_balance) max_balance,

MIN(avail_balance) min_balance,

AVG(avail_balance) avg_balance,

SUM(avail_balance) tot_balance,

COUNT(*) num_accounts

FROM account;

Однако если попытаться выполнить этот запрос, будет получена сле дующая ошибка:

ERROR 1140 (42000): Mixing of GROUP columns (MIN(),MAX(),COUNT( ),...)

with no GROUP columns is illegal if there is no GROUP BY clause

Хотя для разработчика очевидно, что он хочет применить агрегатные функции к множеству счетов каждого типа, выявленного в таблице account, этот запрос дает сбой, потому что не был явно задан способ груп пировки данных. Следовательно, необходимо добавить блок group by и определить в нем группу строк, к которой следует применять агре гатные функции:

mysql>

SELECT product_cd,

>

MAX(avail_balance) max_balance,

>

MIN(avail_balance) min_balance,

>

AVG(avail_balance) avg_balance,

>

SUM(avail_balance) tot_balance,

>

COUNT(*) num_accts

>

FROM account

>

GROUP BY product_cd;

+ + + + + + + | product_cd | max_balance | min_balance | avg_balance | tot_balance | num_accts | + + + + + + +

| BUS

|

9345.55

|

0.00

| 4672.774902 |

9345.55

|

2

|

| CD

|

10000.00

|

1500.00

| 4875.000000

|

19500.00

|

4

|

| CHK

|

38552.05

|

122.37

| 7300.800985

|

73008.01

|

10

|

| MM

|

9345.55

|

2212.50

| 5681.713216

|

17045.14

|

3

|

| SAV

|

767.77

|

200.00

| 463.940002

|

1855.76

|

4

|

| SBL

|

50000.00

|

50000.00

| 50000.000000 |

50000.00 |

1 |

+ + + + + + + 6 rows in set (0.00 sec)

Если есть блок group by, сервер знает, что сначала надо сгруппировать строки с одинаковым значением в столбце product_cd, а затем приме нить пять агрегатных функций к каждой из шести групп.

Подсчет уникальных значений

При использовании функции count() для определения числа членов в каждой группе существует выбор: или пересчитать все члены груп пы, или посчитать только уникальные (distinct) значения столбца из всех членов группы. Рассмотрим, например, следующие данные, кото рыми представлены сотрудники, ответственные за открытие каждого счета:

mysql> SELECT account_id, open_emp_id

> FROM account

> ORDER BY open_emp_id;

+ + + | account_id | open_emp_id | + + +

|

8

|

1

|

|

9

|

1

|

|

10

|

1

|

|

12

|

1

|

|

13

|

1

|

|

17

|

1

|

|

18

|

1

|

|

19

|

1

|

|

1

|

10

|

|

2

|

10

|

|

3

|

10

|

|

4

|

10

|

|

5

|

10

|

|

14

|

10

|

|

22

|

10

|

|

6

|

13

|

|

7

|

13

|

|

24

|

13

|

|

11

|

16

|

|

15

|

16

|

|

16

|

16

|

|

20

|

16

|

|

21

|

16

|

|

23

|

16

|

+ + + 24 rows in set (0.00 sec)

Как видите, все множество счетов было открыто четырьмя разными сотрудниками (с ID = 1, 10, 13 и 16). Допустим, хочется подсчитать число открывших счета сотрудников – не вручную, а с помощью за проса. Если к столбцу open_emp_id применить функцию count(), увидим следующие результаты:

mysql> SELECT COUNT(open_emp_id)

> FROM account;

+ + | COUNT(open_emp_id) | + +

|

24 |

+ + 1 row in set (0.00 sec)

В этом случае столбец open_emp_id задан как столбец, который должен быть пересчитан. При этом полученный результат ничем не отличает ся от результата выполнения функции count(*). Если требуется под считать уникальные значения в группе, а не просто пересчитать число строк в ней, нужно указать ключевое слово distinct:

mysql> SELECT COUNT(DISTINCT open_emp_id)

> FROM account;

+ + | COUNT(DISTINCT open_emp_id) | + +

|

4 |

+ + 1 row in set (0.00 sec)

Следовательно, если задано ключевое слово distinct, функция count() проверит значение столбца для каждого члена группы, а не просто подсчитает число значений в ней.

Использование выражений

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

mysql> SELECT MAX(pending_balance avail_balance) max_uncleared

> FROM account;

+ + | max_uncleared | + +

|

660.00 |

+ +

1 row in set (0.00 sec)

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

Обработка значений Null

При агрегировании, да и при вычислении любого численного выраже ния, всегда следует учитывать влияние значения null на результат вы числения. Для иллюстрации создадим простую таблицу для хранения числовых данных и заполним ее набором {1, 3, 5}:

mysql> CREATE TABLE number_tbl

> (val SMALLINT);

Query OK, 0 rows affected (0.01 sec)

mysql> INSERT INTO number_tbl VALUES (1);

Query OK, 1 row affected (0.00 sec)

mysql> INSERT INTO number_tbl VALUES (3);

Query OK, 1 row affected (0.00 sec)

mysql> INSERT INTO number_tbl VALUES (5);

Query OK, 1 row affected (0.00 sec)

Рассмотрим следующий запрос, применяющий пять агрегатных функ ций к этому набору чисел:

mysql> SELECT COUNT(*) num_rows,

> COUNT(val) num_vals,> SUM(val) total,

> MAX(val) max_val,> AVG(val) avg_val> FROM number_tbl;

+ + + + + + | num_rows | num_vals | total | max_val | avg_val | + + + + + +

|

3 |

3 |

9 |

5 |

3 |

+ + + + + + 1 row in set (0.00 sec)

Как и следовало ожидать, результаты таковы: и count(*), и count(val) возвращают значение 3, sum(val) – значение 9, max(val) – 5, а avg(val) – 3. Теперь добавим в таблицу number_tbl значение null и выполним запрос еще раз:

mysql> INSERT INTO number_tbl VALUES (NULL);

Query OK, 1 row affected (0.01 sec)

mysql> SELECT COUNT(*) num_rows,

> COUNT(val) num_vals,> SUM(val) total,

> MAX(val) max_val,> AVG(val) avg_val> FROM number_tbl;

+ + + + + + | num_rows | num_vals | total | max_val | avg_val | + + + + + +

|

4 |

3 |

9 |

5 |

3 |

+ + + + + +

1 row in set (0.00 sec)

Даже при добавлении в таблицу значения null функции sum(), max() и avg() возвращают те же значения; это означает, что они игнорируют все встречающиеся значения null. Функция count(*) теперь возвращает значение 4, что является правильным, поскольку в таблице number_tbl четыре строки, тогда как функция count(val) по прежнему возвращает значение 3. Разница в том, что функция count(*) считает строки и по этому не подвержена влиянию значений null, содержащихся в строке. А вот функция count(val) считает значения в столбце val, пропуская все встречающиеся значения null.

Формирование групп

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

Формирование общих показателей для географического региона, например общий объем продаж по Европе.

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

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

Чтобы ответить на запросы подобных типов, вам потребуется попросить сервер БД сгруппировать строки по одному или более столбцам или вы ражениям. Как уже было показано в нескольких примерах, механиз мом группировки данных в рамках запроса является блок group by. В этом разделе рассматриваются группировка данных по одному или более столбцам, группировка данных с помощью выражений и форми рование обобщений в рамках группы.

Группировка по одному столбцу

Формирование группы по одному столбцу – самый простой и наиболее распространенный тип группировки. Например, если требуется найти общие остатки (total balance) для всех типов счетов, нужно всего лишь провести группировку по столбцу account.product_cd:

mysql> SELECT product_cd, SUM(avail_balance) prod_balance

> FROM account

> GROUP BY product_cd;

+ + +

| product_cd | prod_balance |

+ + +

|

BUS

|

9345.55

|

|

CD

|

19500.00

|

| CHK

|

73008.01

|

| MM

|

17045.14

|

|

SAV

|

1855.76

|

|

SBL

|

50000.00

|

+ + + 6 rows in set (0.00 sec)

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

Группировка по нескольким столбцам

В некоторых случаях может понадобиться сформировать группы, ох ватывающие более одного столбца. Развивая предыдущий пример, представим, что требуется найти общие остатки не только по каждому типу счетов, но и по отделениям (например: каков общий остаток для всех текущих счетов, открытых в отделении Woburn?). Следующий пример демонстрирует, как это может быть реализовано:

mysql>

SELECT product_cd, open_branch_id,

>

SUM(avail_balance) tot_balance

>

FROM account

>

GROUP BY product_cd, open_branch_id;

+ + + + | product_cd | open_branch_id | tot_balance | + + + +

| BUS

|

2

|

9345.55

|

| BUS

|

4

|

0.00

|

| CD

|

1

|

11500.00

|

| CD

|

2

|

8000.00

|

| CHK

|

1

|

782.16

|

| CHK

|

2

|

3315.77

|

| CHK

|

3

|

1057.75

|

| CHK

|

4

|

67852.33

|

| MM

|

1

|

14832.64

|

| MM

|

3

|

2212.50

|

| SAV

|

1

|

767.77

|

| SAV

|

2

|

700.00

|

| SAV

|

4

|

387.99

|

| SBL

|

3

|

50000.00

|

+ + + + 14 rows in set (0.00 sec)

Этот вариант запроса формирует 14 групп, по одной для каждого обна руженного в таблице account сочетания типа счетов и отделения. Стол бец open_branch_id добавлен в блок select, а также введен в блок group by, поскольку он извлекается из таблицы, а не формируется агрегатной функцией.

Группировка посредством выражений

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

mysql> SELECT EXTRACT(YEAR FROM start_date) year,

> COUNT(*) how_many> FROM employee

> GROUP BY EXTRACT(YEAR FROM start_date);

+ + + | year | how_many | + + +

| 2000 |

3

|

| 2001

|

2

|

| 2002

|

8

|

|

2003

|

3

|

|

2004

|

2

|

+ + +

5 rows in set (0.00 sec)

Этот запрос для группировки строк таблицы employee использует до вольно простое выражение, которое с помощью функции extract() из всей даты извлекает только значение года.

Формирование обобщений

Ранее в этой главе в разделе «Группировка по нескольким столбцам» был показан пример формирования общих остатков счетов по каждо му типу счетов и отделению. Однако допустим, что кроме общих остат ков для каждого сочетания тип счетов/отделение требуется получить и общие остатки по каждому отдельному типу счетов. Можно было бы выполнить дополнительный запрос и объединить результаты, или за грузить результаты запроса в электронную таблицу, или создать сце нарий на Perl или Java программу, или применить какой либо другой механизм для получения данных и проведения дополнительных вы числений. Но все таки лучше всего использовать вариант with rollup (с обобщением), заставив выполнить всю эту работу сервер БД. Вот из мененный запрос, использующий with rollup в блоке group by:

mysql> SELECT product_cd, open_branch_id,

> SUM(avail_balance) tot_balance> FROM account

> GROUP BY product_cd, open_branch_id WITH ROLLUP;

+ + + + | product_cd | open_branch_id | tot_balance | + + + +

| BUS

|

2

|

9345.55

|

| BUS

|

4

|

0.00

|

|

BUS

|

NULL |

9345.55

|

|

CD

|

1

|

11500.00

|