Опубликован: 28.12.2011 | Уровень: для всех | Доступ: свободно
Лекция 7:

Выборка данных. Фразы ORDER BY, GROUP BY и CONNECT BY предложения SELECT. Множественные операции

< Лекция 6 || Лекция 7: 123456 || Лекция 8 >

Указание ROLLUP, CUBE и GROUPING SETS во фразе GROUP BY

В версии 8.1 Oracle в запросах с GROUP BY в соответствии с предложениями SQL:1999 (но без строгого им следования) была введена возможность заключать признаки группировки в конструкции ROLLUP и CUBE, позволяющие сымитировать в СУБД на основе SQL функциональность OLAP. В версии 9 к ним добавилась конструкция GROUPING SETS.

Всякий запрос с ROLLUP, CUBE или GROUPING SETS может быть переформулирован в запрос с группировками, но без этих конструкций, и тем не менее их использование (а) дает более лаконичную и универсальную формулировку, и (б) специальным образом в Oracle оптимизировано.

Пример группировки с ROLLUP:

SELECT   mgr, job, SUM ( sal ), COUNT ( * ) 
FROM     emp 
GROUP BY ROLLUP ( mgr, job )
;

Результат складывается из группировки по паре значений (MGR, JOB) плюс группировки по значению MGR плюс данных по "собственной группе" из всего множества исходных строк:

    MGR JOB        SUM(SAL)  COUNT(*)
------- --------- --------- ---------
        PRESIDENT      5000         1  ← SUM и COUNT для mgr=NULL и job=PRESIDENT
                       5000         1  ← SUM и COUNT для mgr=NULL
   7566 ANALYST        6000         2  ← SUM и COUNT для mgr=7566 и job=ANALYST
   7566                6000         2  ← SUM и COUNT для mgr=7566
   7698 CLERK           950         1  ← SUM и COUNT для mgr=7698 и job=CLERK
   7698 SALESMAN       5600         4  ← SUM и COUNT для mgr=7698 и job=SALESMAN
   7698                6550         5  ← SUM и COUNT для mgr=7698
   7782 CLERK          1300         1  ← ...
   7782                1300         1
   7788 CLERK          1100         1
   7788                1100         1
   7839 MANAGER        8275         3
   7839                8275         3
   7902 CLERK           800         1
   7902                 800         1
                      29025        14  ← SUM и COUNT для всех начальников и должностей

Такое построение результата порождает в ответе искусственно отсутствующие значения из-за группировок по сокращенным перечням признаков. Обратите внимание, они имеют смысл "значение неприменимо" (unapplicable). Это противоречит упоминавшейся выше рекомендации стандарта SQL использовать NULL в смысле "значение неизвестно" (unknown). Такую специфику отсутствующих значений в результатах с GROUP BY ROLLUP следует учитывать в последующей обработке. Но ведь пропуски значений могли иметься и в исходных данных (например, у сотрудника могла быть не обозначена должность), где они могут иметь иной смысл и требовать иного характера обработки. Программа сама по себе не в состоянии определить смысл NULL.

Для различения отсутствующих значений в строках с обобщенными итогами от значений, отсутствующих в исходных данных, создана специальная индикаторная функция GROUPING, возвращающая 1 на "благоприобретенных" NULL-значениях и 0 на всех остальных. Следующий запрос получен из предыдущего добавлением выдачи функций GROUPING и поясняет, как это работает:

SELECT
  GROUPING ( mgr )
, GROUPING ( job )
, mgr
, job
, SUM ( sal )
, COUNT ( * ) 
FROM
  emp 
GROUP BY
  ROLLUP ( mgr, job )
;

С помощью функции GROUPING и оператора CASE отсутствующие значения в строках промежуточных сумм могут быть заполнены желаемым образом:

COLUMN "Начальник" FORMAT A15
SELECT
  CASE GROUPING ( mgr ) WHEN 1 THEN 'все начальники' ELSE TO_CHAR ( mgr ) END 
  AS "Начальник"
, CASE GROUPING ( job ) WHEN 1 THEN 'все должности' ELSE job END 
  AS "Должность"
, SUM ( sal ) AS "Зарплата в группе"
, COUNT ( * ) AS "Сотрудников в группе"
FROM
  emp 
GROUP BY
  ROLLUP ( mgr, job )
ORDER BY mgr, job
;

Упражнение. Выполните приведенный запрос и посмотрите результат. Замените ROLLUP на CUBE, выполните и посмотрите результат.

В версии 9 вместо функции GROUPING стало возможным использовать функцию GROUPING_ID, возвращающую тот же результат, что и GROUPING, но в виде поразрядной маски. Так, один из предшествующих "пояснительных" запросов можно переписать короче:

SELECT
  GROUPING_ID ( mgr, job )
, mgr
, job
, SUM ( sal )
, COUNT ( * ) 
FROM
  emp 
GROUP BY
  ROLLUP ( mgr, job )
;

Иногда использование GROUPING_ID позволяет проще, нежели GROUPING, сформулировать в запросе фильтр.

Еще одна функция, GROUP_ID, также введена в версии 9 и позволяет в запросе с группировкой отметить повторяющиеся строки с агрегатами (которые могут иногда возникать), указывая "степень повторения" > 0, например:

SELECT   mgr, job, SUM ( sal ), COUNT ( * ), GROUP_ID ( )
FROM     emp 
GROUP BY mgr, ROLLUP ( mgr, job )
;

В версии 9 появилась возможность выдавать в запросе только промежуточные агрегаты без усложнений текста, необходимых в таких ситуациях при использовании ROLLUP и CUBE. Для этого введено специальное указание GROUPING SETS:

SELECT   mgr, job, SUM ( sal ), COUNT ( * )
FROM     emp
GROUP BY GROUPING SETS ( mgr, job )
;

Сравните с результатом запроса:

SELECT   mgr, job, SUM ( sal ), COUNT ( * )
FROM     emp
GROUP BY GROUPING SETS ( mgr ), GROUPING SETS ( job )
;

Несамостоятельность группировки с обобщениями ROLLUP, CUBE и GROUPING SETS

Использование в группировках ROLLUP, CUBE и GROUPING SETS дает гибкость формулировки и эффективность исполнения, но не логическую новизну. Так, следующие запросы "по определению" равносильны по результату:

SELECT a, b
FROM Z
GROUP BY ROLLUP ( a, b )
SELECT    a,   b  FROM Z GROUP BY a, b UNION ALL
SELECT    a, NULL FROM Z GROUP BY a    UNION ALL
SELECT NULL, NULL FROM Z
SELECT a, b
FROM Z
GROUP BY CUBE ( a, b )
SELECT    a,    b FROM Z GROUP BY a, b UNION ALL
SELECT    a, NULL FROM Z GROUP BY a    UNION ALL
SELECT NULL,    b FROM Z GROUP BY b    UNION ALL
SELECT NULL, NULL FROM Z
SELECT a, b
FROM Z
GROUP BY 
   GROUPING SETS ( a, b )
SELECT    a, NULL FROM Z GROUP BY a    UNION ALL
SELECT NULL,    b FROM Z GROUP BY b

В списке выражений для ROLLUP, CUBE и GROUPING SETS также возможно группирование. Например, возможен запрос типа (AGG — условное обозначение произвольной агрегирующей функции):

SELECT a, b, c, AGG ( d )
FROM Z
GROUP BY ROLLUP ( a, ( b, c ) )

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

SELECT    a,    b,    c, AGG ( d ) FROM Z GROUP BY a, b, c UNION ALL
SELECT    a, NULL, NULL, AGG ( d ) FROM Z GROUP BY a       UNION ALL
SELECT NULL, NULL, NULL, AGG ( d ) FROM Z

То есть при подсчете обобщений пары (тройки, четверки, …) выражений будут приниматься как целое ("составной столбец"). Группирование может быть произвольным: ( ( a, b ), c ), ( a, ( b, c ), d ), ( ( a, b ), ( c, d, e ) ) и так далее. Равным образом сказанное распространяется на CUBE и GROUPING SETS. Возможно также и комбинирование типа следующего:

SELECT a, b, c, AGG ( d )
FROM   Z
GROUP BY GROUPING SETS ( a, ROLLUP ( b, c ) )
Это равносильно формулировке
SELECT a, NULL, NULL, AGG ( d ) FROM Z GROUP BY a
UNION ALL
SELECT NULL, b,    c, AGG ( d ) FROM Z GROUP BY ROLLUP ( b, c )
< Лекция 6 || Лекция 7: 123456 || Лекция 8 >
Ярослав Прозоров
Ярослав Прозоров

В лекции № 7 "Введение в Oracle SQL" в подразделе "Несамостоятельность группировки с обобщениями ROLLUP, CUBE и GROUPING SETS"  представленная таблица сравнения содержит ошибки - окончания запросов пропущены. Видимо, ошибки вызваны некорректным переносом материала лекции.

Володимир Миколайчук
Володимир Миколайчук
Помогите разобраться поетапно с логикой запроса
-------TOOLS
NAME PRICE TYPE
drill 155 A
sawzall 192 N
mitre saw 292 M
router 86 I
RAD 145 M
jigsaw 128 I
screwdriver 77 P
------TOOL_TYPES
TYPE USAGE
A Always
I Often
M Sometimes
N Rarely
P Never

Запрос SQL:
SELECT t.type, SUM(t.price)
FROM tools t
GROUP BY t.type
HAVING SUM(t.price) >= (SELECT AVG(price)
FROM tools
WHERE type IN (SELECT type
FROM tool_types
WHERE usage = 'Often'));

И сколько строк он все таки вернет
Жанбек Сарсенов
Жанбек Сарсенов
Россия, Москва, Московский Государственный Университет имени Ломоносова М.В., 2002