Справочник от Автор24
Поделись лекцией за скидку на Автор24

Средства выбора вариантов

  • 👀 298 просмотров
  • 📌 231 загрузка
Выбери формат для чтения
Загружаем конспект в формате pdf
Это займет всего пару минут! А пока ты можешь прочитать работу в формате Word 👇
Конспект лекции по дисциплине «Средства выбора вариантов» pdf
ЛЕКЦИЯ № 7 3.2.3.5. Средства выбора вариантов.................1 3.2.4. Секция GROUP BY..........................................10 3.2.5. Секция HAVING..............................................17 3.2.6. Секция ORDER BY..........................................21 3.2.7. Статистический анализ данных в SQL...........26 3.2.3.5. Средства выбора вариантов В стандарте SQL определены средства для выбора вариантов действий в зависимости от значений данных. К таким средствам относятся операция выбора CASE, а также различные функции выбора вариантов. В Firebird в качестве функций выбора вариантов используются функции COALESCE, NULLIF, IIF и DECODE, 3 из которых (COALESCE, NULLIF и DECODE) определены в стандарте SQL, а одна (IIF) является расширением языка в СУБД Firebird. Эти функции выбора вариантов при определенных условиях фактически являются сокращенными формами операции CASE. Они всегда могут быть заменены эквивалентными конструкциями CASE, но более сложно записанными. Средства выбора вариантов могут использоваться в списке возвращаемых столбцов секции SELECT, в секции WHERE, а также в качестве элементов списка сортировки секции ORDER BY (см. разд. 3.2.6) и списка группировки секции GROUP BY (см. разд. 3.2.4). Средства выбора вариантов применимы также в запросах языка DML (см. раздел 5.2.1) СУБД Oracle поддерживает определенную стандартом операцию CASE и стандартные функции выбора COALESCE, NULLIF и DECODE и свою функцию NVL. СУБД MS SQL Server поддерживает операцию CASE и функции COALESCE, NULLIF и IIF, а также, дополнительно, свою собственную функцию выбора CHOOSE. Рассмотрим подробно различные средства выбора вариантов. Операция выбора CASE. Операция выбора CASE позволяет определить результат, исходя из определенных условий. Имеются две формы операции CASE: простая и с поиском. Простая форма имеет синтаксис: CASE <выражение> {WHEN <значение1> THEN результат1} [{WHEN <значение2> THEN результат2}] ... [ ELSE результат(N+1)] END. В этой форме последовательно сравниваются значения при фразах WHEN со значением заданного выражения. При первом же совпадении возвращается значение при соответствующей фразе THEN. Если совпадений нет, то возвращается результат(N+1) при фразе ELSE, если она используется. Синтаксис операции CASE с поиском: CASE {WHEN <условие_поиска1> THEN результат1} [{ WHEN <условие_поиска2> THEN результат2}]... [ ELSE результат(N+1) ] END. 1 В случае использования операции CASE с поиском последовательно проверяются все условия при фразах WHEN. При первом истинном выражении возвращается результат соответствующей фразы THEN. Если ни одно условие при фразах WHEN не оказалось истинным, возвращается результат(N+1) при фразе ELSE, если она указана. Условие в CASE с поиском аналогично условию отбора строк в секции WHERE. В CASE в качестве результата после слов THEN или ELSE может быть задано либо выражение (которое может включать константы, имена столбцов, функции, а также арифметические операции и операцию конкатенации), либо NULL. Если фраза ELSE отсутствует, то операция CASE при отсутствии совпадения (или истинного условия) возвращает NULL. Рассмотрим примеры использования операции CASE в списке возвращаемых элементов секции SELECT. Приведем пример простой операции CASE. Пусть необходимо вывести следующую информацию о ремонтных заявках абонента, имеющего лицевой счет с номером '115705': номер заявки, номер лицевого счета абонента, подавшего заявку, код неисправности. В зависимости от значения столбца Executed необходимо вывести, погашена заявка или нет. Запрос в Firebird будет выглядеть таким образом (рис. 3.53): SELECT RequestCD, (' Номер л/с абонента '|| AccountCD) AS "Ab_Info", (' Код неисправности '|| FailureCD) AS "Failure", CASE Executed WHEN False THEN 'Не погашена' ELSE 'Погашена' END FROM Request WHERE AccountCD='115705'; REQUESTCD Ab_Info 2 Номер л/с абонента 115705 15 Номер л/с абонента 115705 16 Номер л/с абонента 115705 17 Номер л/с абонента 115705 18 Номер л/с абонента 115705 Failure CASE Код неисправности 1 Погашена Код неисправности 5 Погашена Код неисправности 3 Не погашена Код неисправности 5 Погашена Код неисправности 3 Погашена Рис. 3.53. Результат выполнения запроса с простой операцией CASE Пусть необходимо вывести информацию об оплатах со значением от 50 до 100 с указанием срока давности оплаты: если оплата была произведена до 2011 г., то вывести 'Давно', если оплата была произведена в 2011 г. или 2 2012 г., то вывести 'Не очень давно', если позднее – 'Недавно'. Запрос в Firebird с использованием операции CASE с поиском (рис. 3.54): SELECT PayFactCD, AccountCD, PaySum, (CASE WHEN PayDate < '01.01.2011' THEN 'Давно' WHEN PayDate BETWEEN '01.01.2011' AND '31.12.2012' THEN 'Не очень давно' ELSE 'Недавно' END) AS "Oplata" FROM PaySumma WHERE PaySum BETWEEN 50 AND 100; PAYFACTtCD 1 3 7 9 10 12 14 15 16 27 29 37 42 ACCOUNTCD 005488 005488 136160 080047 080047 080613 115705 136169 443069 080270 005488 080270 080270 PAYSUM 58,70 56,00 56,00 80,00 80,00 58,50 58,70 58,70 80,00 60,00 65,00 58,10 60,00 Oplata Не очень давно Недавно Недавно Не очень давно Не очень давно Не очень давно Не очень давно Не очень давно Не очень давно Не очень давно Давно Не очень давно Не очень давно Рис. 3.54. Результат выполнения запроса с операцией CASE с поиском Примером использования SUM по CASE может быть запрос SELECT COUNT(*) AS "Всего заявок" , SUM(CASE WHEN ExecutionDate IS NULL THEN 1 ELSE 0 END) AS "невыполненных", SUM(CASE WHEN NOT Executed THEN 1 ELSE 0 END) AS "непогашенных" FROM Request;, возвращающий общее число ремонтных заявок, число невыполненных и непогашенных заявок. Функция COALESCE используется для замены неопределенного значения на любое другое и имеет следующий синтаксис: COALESCE (<выражение1> , <выражение2> [, <выражение3> ]...) . Эта функция имеет два или более параметров и возвращает значение первого из параметров, отличного от NULL. Если значения всех выражений 3 определены как NULL, то функция COALESCE вернет NULL. Функция COALESCE фактически представляет собой сокращение операции CASE и в зависимости от числа аргументов может быть заменена следующими эквивалентными ей конструкциями:  конструкция COALESCE (<выражение1>, <выражение2>) эквивалентна конструкции CASE WHEN <выражение1> IS NOT NULL THEN <выражение1> ELSE <выражение2> END;  конструкция COALESCE (<выражение1>, <выражение2>, ..., <выражениеN>) для N >= 3 эквивалентна конструкции CASE WHEN <выражение1> IS NOT NULL THEN <выражение1> ELSE COALESCE (<выражение2>,..., <выражениеN>) END. Таким образом, эквивалентная конструкция CASE всегда содержит в качестве условия поиска проверку соответствующего выражения из списка функции COALESCE на неравенство NULL. Например, требуется вывести информацию о датах выполнения ремонтных заявок, поступивших от абонентов с номерами лицевых счетов '005488', '115705' и '080270'. Если заявка не выполнена, то вывести дату ее поступления (рис. 3.55). Если ни дата поступления, ни дата выполнения неизвестны, то вывести 'Дата неизвестна'. Соответствующий запрос в Firebird будет выглядеть таким образом: REQUESTCD 1 Date_Info 2011-12-20 2 2011-08-12 5 2011-12-31 13 2010-12-05 14 2013-04-13 15 2010-09-23 16 2011-12-28 17 2011-09-06 18 2013-01-04 19 2011-12-27 Рис. 3.55. Результат выполнения запроса при использовании COALESCE 4 SELECT RequestCD, COALESCE(ExecutionDate,IncomingDate, 'Дата неизвестна') AS "Date_Info" FROM Request WHERE AccountCD IN ('005488', '115705', '080270'); В результате для ремонтных заявок с номерами 5 и 16, которые не выполнены, выведена дата поступления заявки, а для остальных заявок – дата выполнения. Представим данный запрос с помощью операции CASE: SELECT RequestCD, (CASE WHEN ExecutionDate IS NOT NULL THEN ExecutionDate ELSE CASE WHEN IncomingDate IS NOT NULL THEN IncomingDate ELSE 'Дата неизвестна' END END) AS "Date_Info" FROM Request WHERE AccountCD IN ('005488', '115705', '080270'); Результат выполнения данного запроса совпадает с результатом выполнения предыдущего запроса, использующего функцию COALESCE (рис. 3.55), однако форма записи более длинная и сложная для понимания, чем предыдущая. Следует обратить внимание, что приведенные запросы подразумевают, что сервером Firebird будет выполнено неявное преобразование типа даты (значения ExecutionDate, IncomingDate) в строковый тип. Если, например, составлять аналогичный запрос в Oracle, то потребовалось бы использовать функцию явного преобразования TO_CHAR для приведения значений ExecutionDate и IncomingDate к строке. В Oracle, как правило, применяется своя собственная функция NVL. Эта функция имеет два параметра: NVL(<выражение1> , <выражение2>). Если значение <выражение1> определено, то функция возвращает его значение. Если значение <выражение1> является NULL, то вместо него функция возвращает значение <выражение2> SELECT NVL(Phone,'Нет телефона') FROM Abonent; Функция NULLIF производит замену заданного значения на NULL. Синтаксис использования имеет следующий вид: NULLIF (<выражение1>, <выражение2>), где <выражение1> – столбец или вычисляемое выражение; <выражение2> – вычисляемое выражение (может включать константы, имена столбцов, функции, а также арифметические операции и операцию конкатенации), со значением которого сравнивается значение <выражение1>. Функция NULLIF возвращает NULL, если значение <выражение1> совпадает со значением <выражение2>, и значение <выражение1> в противном случае. Использование функции NULLIF аналогично использованию следующей конструкции операции CASE: CASE WHEN <выражение1> = <выражение2> THEN NULL ELSE <выражение1> END. Пусть, например, требуется вывести номера лицевых счетов, ФИО абонентов и номера их телефонов, учитывая, что номер телефона '556893' 5 уже не существует, а новый номер неизвестен (рис. 3.56). Запрос в Firebird будет выглядеть так: SELECT AccountCD, Fio, NULLIF (Phone,'556893') FROM Abonent; ACCOUNTCD FIO CASE 005488 Аксенов С.А. NULL 115705 Мищенко Е.В. 769975 015527 Конюхов В.С. 761699 443690 Тулупова М.И. 214833 136159 Свирина З.А. NULL 443069 Стародубцев Е.В. 683014 136160 Шмаков С.В. NULL 126112 Маркова В.П. 683301 136169 Денисова Е.К. 680305 080613 Лукашина Р.М. 254417 080047 Шубина Т. П. 257842 080270 Тимошкина Н.Г. 321002 Рис. 3.56. Результат выполнения запроса при использовании NULLIF Как следует из результата, у абонента с ФИО Аксенов С.А., имеющего в БД номер телефона 556893, теперь выводится сообщение, что номер его телефона неизвестен. С помощью функции NULLIF можно некоторые или все значения в заданном столбце поменять на NULL (рис. 3.57): SELECT AccountCD, Fio, NULLIF (Phone, Phone) FROM Abonent; ACCOU FIO CASE NTCD 005488 Аксенов С.А. NULL 115705 Мищенко Е.В. NULL 015527 Конюхов В.С. NULL 443690 Тулупова М.И. NULL 136159 Свирина З.А. NULL 443069 Стародубцев Е.В. NULL 136160 Шмаков С.В. NULL 126112 Маркова В.П. NULL 136169 Денисова Е.К. NULL 080613 Лукашина Р.М. NULL 080047 Шубина Т. П. NULL 080270 Тимошкина Н.Г. NULL Рис. 3.57. Результат выполнения запроса на замену всего столбца на NULL Две предыдущие рассмотренные функции соответствовали стандарту SQL. СУБД Firebird и MS SQL Server предоставляют возможность 6 использования также функции IIF, которая является расширением языка и не описана в стандарте SQL. Функция имеет синтаксис: IIF (<условие_поиска>, <выражение1>, <выражение2>) и эквивалентна следующей конструкции операции CASE: CASE WHEN <условие_поиска> THEN <выражение1> ELSE <выражение2> END. Данная конструкция возвращает значение <выражение1>, если <условие_поиска> истинно, и значение <выражение2> в противном случае. Например, необходимо вывести информацию о типах неисправностей, не детализируя неисправности газовой плиты (рис. 3.58): SELECT FailureCD, IIF(FailureNM Like '%плит%', 'Неисправность плиты', FailureNM) AS "Failure_Type" FROM Disrepair; FAILURECD Failure_Type 1 Засорилась водогрейная колонка 2 Не горит АГВ 3 Течет из водогрейной колонки 4 Неисправна печная горелка 5 Неисправен газовый счетчик 6 Неисправность плиты 7 Неисправность плиты 8 Неисправность плиты 12 Неизвестна Рис. 3.58. Результат выполнения запроса при использовании IIF Функция DECODE является сокращенной формой простой операции CASE и имеет формат: DECODE (<выражение>, <значение1>, результат1 [ , <значение2>, результат2 ... ] [,<результат_по_умолчанию>]. Эта функция последовательно сравнивает значение выражения, заданного первым аргументом, с аргументами <значение1>, <значение2> и т. д. Если сравнение оказывается истинным, возвращается соответственно результат1 или результат2 и т. д. Если значение выражения не совпало ни с одним из значений из списка, то возвращается <результат_по_умолчанию> (аналогичен результату, задаваемому после фразы ELSE в операции CASE). Если <результат_по_умолчанию> не задан, то возвращается NULL. Типы значения исходного выражения и его декодированного значения (результат1, результат2 и т. д.) могут не совпадать. С помощью следующего запроса в Firebird можно вывести первые 5 строк из таблицы ремонтных заявок, указав, погашены заявки или нет: 7 SELECT FIRST 5 RequestCD, DECODE (Executed, False, 'Заявка не погашена', True, 'Заявка погашена', 'Неизвестно') FROM Request; Результат выполнения запроса представлен на рис. 3.59. REQUESTCD 1 2 3 5 6 CASE Заявка погашена Заявка погашена Заявка не погашена Заявка не погашена Заявка погашена Рис. 3.59. Результат работы функции DECODE Примерами использования рассмотренных средств выбора вариантов в секции WHERE может быть такой запрос: SELECT * FROM Request WHERE NULLIF(ExecutionDate,Incomingdate) IS NULL;, возвращающий информацию по невыполненным заявкам и заявкам, у которых равны даты регистрации и выполнения, а также следующий запрос: SELECT Accountcd,Servicecd,PaySum FROM Paysumma WHERE Servicecd = CASE WHEN Accountcd=136169 THEN 1 WHEN Accountcd=136160 THEN 3 WHEN Accountcd=080270 THEN 4 ELSE 2 END;, выбирающий для абонентов значения их платежей за заданные услуги. Функция CHOOSE в MS SQL Server по указанному индексу возвращает элемент из списка значений [37]. Функция имеет формат: CHOOSE ( <индекс>, <значение1>, <значение2> [,<значениеN> ] ), где <индекс> - целочисленное выражение, которое представляет отсчитываемый от 1 индекс в списке элементов, следующих за ним. Если указанное значение индекса имеет числовой тип, отличный от типа int, то значение неявно преобразуется в целое. Если значение индекса выходит за границы массива значений, то функция CHOOSE возвращает значение NULL. Функция CHOOSE действует подобно индексу массива, где массив состоит из следующих за аргументом индекса аргументов. Аргумент индекса определяет, какие из следующих за ним значений будут возвращены. Следующий запрос возвращает время года, в котором поступила ремонтная заявка. Функция MONTH используется, чтобы вернуть значение месяца из столбца Incomingdate. SELECT RequestCD, CHOOSE(MONTH(Incomingdate),'Winter','Winter', 8 'Spring','Spring','Spring','Summer','Summer','Summer', 'Autumn','Autumn','Autumn','Winter') AS Season FROM Request; 3.2.4. Секция GROUP BY Запрос, включающий в себя секцию GROUP BY, называется запросом с группировкой, поскольку он объединяет строки исходной таблицы в группы и для каждой группы строк генерирует одну строку НД. Элементы, указанные в секции GROUP BY, называются элементами группировки, и именно они определяют, по какому признаку строки делятся на группы. При этом группой называется набор строк, имеющих одинаковое значение в элементе (элементах) группировки. Синтаксис секции GROUP BY имеет вид: GROUP BY <элемент_группировки1> [, <элемент_группировки2>]…, где <элемент_группировки> := {[<таблица>.] столбец | порядковый_номер_столбца | псевдоним_столбца | <выражение>}. Фактически в качестве элемента группировки может выступать любой возвращаемый элемент, указанный в секции SELECT, кроме значений агрегатных функций. В выражение, представляющее собой <элемент_группировки>, могут входить скалярные функции из различных контекстов или это может быть любая CASE-операция. Следует учесть, что в Oracle и MS SQL Server порядковый_номер_столбца и псевдоним_столбца не могут выступать в качестве элементов группировки. Использование секции GROUP BY имеет смысл только при наличии в списке возвращаемых элементов секции SELECT хотя бы одного вычисляемого столбца или агрегатной функции. Агрегатная функция берет столбец значений и возвращает одно значение. Секция GROUP BY указывает, что результаты запроса следует разделить на группы, применить агрегатную функцию по отдельности к каждой группе и получить для каждой группы одну строку результатов. Например, если NACHISLYEAR AVG необходимо вычислить 2010 76,40 средние значения начислений 2011 125,24 за каждый год (рис. 3.60), то в 2012 213,09 Firebird можно воспользоваться 2013 196,91 запросом: Рис. 3.60. Результат выполнения запроса с группировкой SELECT NachislYear, AVG(NachislSum) FROM NachislSumma GROUP BY NachislYear; 9 В Firebird возможна группировка результатов запроса на основании порядкового номера возвращаемого элемента в секции SELECT. Таким образом, запрос SELECT NachislYear, AVG(NachislSum) FROM NachislSumma GROUP BY 1; выдаст такой же результат, как и предыдущий запрос. Summa_20 COUNT Также в Firebird в секции GROUP BY можно 8,30 2 указывать псевдонимы 10,60 1 столбцов. Например, 12,60 2 следующий запрос 15,30 1 позволит для каждого 17,80 1 значения, меньшего 20, 18,30 2 подсчитать количество 18,32 1 фактов его начислений 19,56 1 (рис. 3.61): Рис. 3.61. Результат SELECT NachislSum AS выполнения запроса "Summa_20", COUNT(*) с группировкой по FROM NachislSumma псевдониму WHERE NachislSum < 20 GROUP BY "Summa_20"; Если для каждого абонента требуется вывести общее количество оплат с указанным в этой же строке максимальным для него значением оплаты, то можно сгруппировать результат по номеру лицевого счета и использовать в качестве второго возвращаемого элемента выражение с агрегатными функциями COUNT и MAX. Соответствующий запрос будет выглядеть так: SELECT AccountCD, (COUNT(*) || ' – с максимальной суммой ' || MAX(PaySum)) AS "Pay_Count" FROM PaySumma GROUP BY AccountCD; Результат выполнения запроса представлен на рис. 3.62. ACCOUNTCD Pay_Count 005488 6 – с максимальной суммой 280.00 015527 6 – с максимальной суммой 611.30 080047 7 – с максимальной суммой 271.60 080270 8 – с максимальной суммой 460.00 080613 8 – с максимальной суммой 258.80 115705 9 – с максимальной суммой 553.85 126112 4 – с максимальной суммой 10 136159 136160 136169 443069 443690 435.50 5 – с максимальной суммой 420.00 6 – с максимальной суммой 240.00 8 – с максимальной суммой 528.44 6 – с максимальной суммой 466.69 4 – с максимальной суммой 485.00 Рис. 3.62. Результат выполнения запроса с группировкой В SQL можно группировать результаты запроса на основании двух или более элементов. Например, необходимо для каждого абонента вывести наименьшее значение оплаты за 2011 и 2012 годы (результирующий НД должен включать первые 10 строк). Для этого нужно сгруппировать таблицу PaySumma по номерам лицевых счетов абонентов и по годам (рис. 3.63), применяя к значениям оплат агрегатную функцию MIN: SELECT FIRST 10 AccountCD, PayYear, MIN(Paysum) FROM PaySumma WHERE PayYear IN (2011, 2012) GROUP BY AccountCD, PayYear; ACCOUNTCD 005488 005488 015527 080047 080047 080270 080270 080613 080613 115705 PAYYEAR 2011 2012 2012 2011 2012 2011 2012 2011 2012 2011 MIN 58,70 280,00 20,00 32,56 19,56 30,00 60,00 58,50 11,00 37,80 Рис. 3.63. Результат выполнения запроса с группировкой по двум столбцам В качестве элемента группировки допускается определение выражений, в которых разрешено использовать различные скалярные SQL-функции, а также средства выбора вариантов. Например, требуется для каждой группы номеров лицевых счетов, начинающихся с одинаковых трех символов, вы вести количество абонентов, имеющих такие счета (рис. 3.64): 11 SELECT ('Начало счета ' || SUBSTRING(AccountCD FROM 1 FOR 3)) AS "Acc_3", COUNT(*) FROM Abonent GROUP BY "Acc_3"; Acc_3 COUNT(*) Начало 1 счета 005 Начало 1 счета 015 Начало 3 счета 080 Начало 1 счета 115 Начало 1 счета 126 Начало 3 счета 136 Начало 2 счета 443 Рис. 3.64. Результат выполнения запроса с группировкой по выражению В Oracle запрос, аналогичный предыдущему, будет выглядеть так: SELECT ('Начало счета '|| RPAD(AccountCD,3)) AS Acc_3, COUNT(*) FROM Abonent GROUP BY ('Начало счета '|| RPAD(AccountCD,3)); Следует учесть, что нельзя выводить по отдельности столбцы, участвующие в выражении группировки. Например, в результате попытки выполнить следующий запрос: SELECT AccountCD, COUNT(*) FROM Abonent GROUP BY SUBSTRING (AccountCD FROM 1 FOR 3); будет выдано сообщение об ошибке, так как столбец AccountCD, участвующий в выражении группировки, присутствует в списке возвращаемых элементов секции SELECT. Таблица разделяется на группы по первым трем совпадающим символам номера лицевого счета, и каждый элемент из списка выбора должен иметь по одному значению для группы. В одну группу может входить несколько номеров лицевых счетов, но если попытаться вывести каждый номер лицевого счета AccountCD, будет выдано сообщение об ошибке. Рассмотрим пример использования операции CASE в качестве элемента группировки. Например, требуется вывести средние значения начислений 12 за годы до 2012 г. и за годы после 2011 г. (рис. 3.65). Соответствующий запрос в Firebird будет выглядеть так: SELECT 'В среднем начислено ' || (CASE WHEN NachislYear < 2012 THEN 'до 2012 года' ELSE 'после 2011 года' END) AS "God", AVG (NachislSum) AS "Average_Sum" FROM NachislSumma GROUP BY God; God В среднем начислено до 2012 г. В среднем начислено после 2011 г. Average_Sum 109,98 205,00 Рис. 3.65. Результат выполнения запроса с группировкой по CASE СУБД MS SQL Server и Oracle предоставляют расширенный синтаксис секции GROUP BY засчет использования специальных функций: GROUP BY { <элемент_группировки1> | { ROLLUP | CUBE | GROUPING SETS } ( <элемент> [, <элемент>]... ) } [, { <элемент_группировки2> | { ROLLUP | CUBE | GROUPING SETS } ( <элемент> [, <элемент>]... ) } ] … Функция ROLLUP формирует статистические строки секции GROUP BY и строки подытогов или строки со статистическими вычислениями высокого уровня, а также строки общего итога. Количество возвращаемых группирований равно количеству выражений в списке элементов функции плюс один. Например, если имеется запрос вида SELECT a, b, c, SUM (<выражение>) FROM t GROUP BY ROLLUP (a,b,c); то для каждого уникального сочетания значений (a, b, c), (a, b) и (a) формируется одна строка с подытогом. Вычисляется также строка общего итога. Столбцы свертываются справа налево. Последовательность расположения столбцов влияет на выходное группирование ROLLUP и может отразиться на количестве строк в результирующем наборе. Результирующий набор, созданный функцией ROLLUP, подобен набору, возвращаемому оператором COMPUTE BY. Однако функция ROLLUP имеет следующие преимущества: - ROLLUP возвращает единственный результирующий набор, а COMPUTE BY – несколько результирующих наборов, что приводит к усложнению кода приложения; 13 - ROLLUP, в отличие от COMPUTE BY, может быть использована в серверном курсоре; - в некоторых случаях оптимизатор запросов при работе с функцией ROLLUP дает лучшие результаты, чем при работе с оператором COMPUTE BY. Функция CUBE формирует статистические строки секции GROUP BY, строки со статистическими вычислениями высокого уровня функции ROLLUP и строки с результатами перекрестных вычислений. Выходные данные CUBE являются группированием для всех перестановок выражений в списке элементов оператора. Количество формируемых группирований равно (2n), где n – количество выражений в списке элементов оператора. Например, имеется запрос следующего вида: SELECT a, b, c, SUM (<выражение>) FROM t GROUP BY CUBE (a,b,c); Формируется одна строка для каждого уникального сочетания значений (a, b, c), (a, b), (a, c), (b, c), (a), (b) и (c) с подытогом для каждой строки и строкой общего итога. Выходные данные CUBE не зависят от порядка столбцов. Функция GROUPING SETS указывает несколько группирований данных в одном запросе. Выполняется статистическая обработка только указанных групп, а не полного набора статистических данных, формируемых с помощью функций CUBE или ROLLUP. Результаты эквивалентны тем, что формируются с применением оператора UNION ALL к указанным группам. Функция GROUPING SETS может содержать единственный элемент или список элементов, могут быть указаны группирования, эквивалентные тем, которые возвращают функции ROLLUP или CUBE. Список элементов функции GROUPING SETS может содержать функции ROLLUP или CUBE. Пустая группа ( ) формирует итог. Для того, чтобы улучшить обработку значений NULL в строках, созданных функциями ROLLUP или CUBE, в секции SELECT запросов с группировкой доступно использование специальной функции GROUPING. Функция GROUPING возвращает значение 1, если строка - это подытог, созданный функцией ROLLUP или CUBE, и 0 в противном случае. Следует отметить, что функции ROLLUP, CUBE и GROUPING SETS существенно упрощают программирование на SQL тем, что облегчают анализ данных по нескольким измерениям, устраняют необходимость проектировать запросы с объединением UNION (см. далее п. 3.3.3). Как следует из всех предыдущих примеров, на запросы с группировкой накладываются некоторые ограничения. 14 1. В секции GROUP BY должны быть указаны столбцы или выражения, которые используются в качестве возвращаемых элементов секции SELECT (за исключением агрегатных функций). 2. Все элементы списка возвращаемых столбцов должны иметь одно значение для каждой группы строк. Это означает, что возвращаемым элементом в секции SELECT может быть: константа; агрегатная функция, возвращающая одно значение для всех строк, входящих в группу; элемент группировки, который по определению имеет одно и то же значение во всех строках группы; функция, которая используется в качестве элемента группировки; выражение, включающее в себя перечисленные выше элементы. На практике в список возвращаемых элементов запроса с группировкой всегда входят столбец или функция группировки и агрегатная функция. Если агрегатная функция не указана, значит, запрос можно более просто выразить с помощью ключевого слова DISTINCT без использования секции GROUP BY. Например, запрос с группировкой вида SELECT StreetCD FROM Abonent GROUP BY StreetCD; может быть более просто выражен с помощью DISTINCT (такой запрос был приведен ранее в п. 3.2.1, рис. 3.9). И наоборот, если не включить в результаты запроса столбец или функцию, по которым производится группировка, то станет невозможно определить, к какой группе относится каждая строка результатов. Наконец следует отметить, что по умолчанию строки сортируются в порядке возрастания значений столбцов, заданных в секции GROUP BY. Изменить этот порядок можно с помощью секции ORDER BY. 3.2.5. Секция HAVING Секция HAVING запроса SELECT применяется для наложения условий на строки, возвращаемые при использовании секции GROUP BY. Секция состоит из ключевого слова HAVING, за которым следует <условие_поиска>: <условие_поиска> ::= [NOT] <условие_поиска1> [[AND|OR][NOT] <условие_поиска2>]…, где <условие_поиска> позволяет исключить из результата группы, не удовлетворяющие заданным условиям. Условие поиска совпадает с условием поиска, рассмотренным выше для секции WHERE, однако в качестве значения часто используется значение, возвращаемое агрегатными функциями. Результат совместной работы HAVING с GROUP BY аналогичен результату работы запроса SELECT с секцией WHERE с той разницей, что HAVING выполняет те же функции над строками (группами) возвращаемого 15 набора данных, а не над строками исходной таблицы. Из этого следует, что секция HAVING начинает свою работу после того, как секция GROUP BY разделит базовую таблицу на группы. В противоположность этому использование секции WHERE приводит к тому, что сначала отбираются строки из базовой таблицы и только после этого отобранные строки начинают использоваться. Например, чтобы для каждого из абонентов, которые подавали более одной ремонтной заявки, вывести количество заявок и дату самой ранней из них (рис. 3.66), нужно выполнить запрос: SELECT AccountCD, COUNT(*), MIN(IncomingDate) FROM Request GROUP BY AccountCD HAVING COUNT(*) > 1; ACCOUNTCD 005488 080047 080270 115705 136160 136169 443069 MIN COUNT 3 2 2 5 2 2 2 04.09.2010 11.10.2011 17.12.2011 20.09.2010 18.05.2011 07.05.2011 08.08.2011 Рис. 3.66. Результат выполнения запроса с HAVING Работа этого запроса заключается в следующем. Сначала GROUP BY из таблицы Request формирует группы, состоящие из одинаковых значений столбца AccountCD. После этого в секции HAVING происходит подсчет числа строк, входящих в каждую группу, и в НД включаются все группы, которые содержат более одной строки. Следует отметить, что если задать условие COUNT(*)>1 в секции WHERE, то такой запрос потерпит неудачу, так как секция WHERE производит оценку в терминах одиночной строки, а агрегатные функции оцениваются в терминах групп строк. В то же время из этого не следует, что секция WHERE не используется с секцией HAVING. Следует учесть, что секция HAVING должна ссылаться только на агрегатные функции и элементы, выбранные GROUP BY, т. е. должны использоваться аргументы, которые имеют одно значение на группу вывода. Например, такой запрос потерпит неудачу: SELECT AccountCD, MAX(IncomingDate) FROM Request GROUP BY AccountCD HAVING FailureCD=1; 16 Столбец FailureCD не может быть использован в секции HAVING, потому что он может иметь (и действительно имеет) больше чем одно значение на группу вывода. Таким образом, следующий запрос является корректным: SELECT AccountCD, MAX(IncomingDate) FROM Request WHERE FailureCD=1 GROUP BY AccountCD; Результат запроса представлен на рис. 3.67. ACCOUNTCD 005488 080270 115705 136169 MAX 17.12.2011 31.12.2011 07.08.2011 06.11.2011 Рис. 3.67. Результат выполнения корректного запроса Если необходимо узнать максимальные значения начислений для абонентов с лицевыми счетами '005488' и '080047' (рис. 3.68), то можно выполнить запрос, в котором секция HAVING ссылается на элемент, указанный в GROUP BY. Данный запрос является корректным: SELECT AccountCD, MAX (NachislSum) FROM NachislSumma GROUP BY AccountCD HAVING AccountCD IN ('005488', '080047'); ACCOUNTCD MAX 005488 279,80 080047 271,60 Рис. 3.68. Результат выполнения запроса Предположим, необходимо для каждой неисправности, с которой последняя заявка поступила позднее 31.08.2011, вывести дату поступления последней заявки, общее количество заявок и сколько из них было выполнено (рис. 3.69). Для решения этой задачи запрос будет выглядеть таким образом: SELECT FailureCD, MAX(IncomingDate),COUNT(*), ' Из них выполнено ' || Count(ExecutionDate) FROM Request GROUP BY FailureCD HAVING MAX(IncomingDate) > '31.08.2011'; FAILURECD MAX 1 31.12.201 1 2 20.10.201 2 3 28.12.201 2 COUNT CONCATENATION 4 Из них выполнено 3 4 Из них выполнено 4 2 Из них выполнено 1 17 6 8 12 04.04.201 3 17.12.201 1 28.02.201 2 3 Из них выполнено 3 2 Из них выполнено 2 2 Из них выполнено 2 Рис. 3.69. Результат выполнения запроса к таблице Request В следующем примере в секции HAVING используется оператор CASE, чтобы ограничить строки, возвращаемые запросом SELECT. Запрос возвращает для каждого абонента максимальное значение оплаты по каждой услуге (рис. 3.70). Секция HAVING ограничивает услуги, оставляя только услугу с кодом 2 и максимальной оплатой более 100 или услугу с кодом 4 и максимальной оплатой более 500. SELECT AccountCD, ServiceCD, MAX(Paysum) FROM PaySumma P GROUP BY P.AccountCD, P.ServiceCD HAVING (MAX(CASE WHEN ServiceCD =2 THEN P.Paysum ELSE NULL END) > 100 OR MAX(CASE WHEN ServiceCD =4 THEN P.Paysum ELSE NULL END) >500);. ACCOUNTCD SERVICECD MAX 015527 4 611,30 115705 2 250,00 115705 4 553,85 136169 4 528,44 Рис. 3.70. Результат выполнения запроса с оператором CASE Как и условие поиска в секции WHERE, условие поиска в секции HAVING может дать 1 из 3 перечисленных результатов:  если условие поиска имеет значение TRUE, то группа строк остается и для нее генерируется одна строка в результате запроса;  если условие поиска имеет значение FALSE, то группа строк исключается и строка в результате запроса для нее не генерируется;  если условие поиска NULL, то группа строк исключается и строка в результате запроса для нее не генерируется. Правила обработки NULL в условиях поиска для секции HAVING точно такие же, как и для секции WHERE. Секцию HAVING имеет смысл использовать в сочетании с секцией GROUP BY, хотя синтаксис запроса SELECT не требует этого. Если базовая 18 таблица интерпретируется одной группой, как, например в следующем запросе SELECT MAX(IncomingDate) FROM Request HAVING MAX(IncomingDate) > '31.08.2011';, то использование HAVING возможно без GROUP BY. В заключение следует рассмотреть типичные ошибки, возникающие при построении запросов с группировкой данных. Пусть требуется вывести список номеров лицевых счетов абонентов с общей суммой оплат больше 1000. Если попытаться эту задачу решить так: SELECT AccountCD, SUM (PaySum) AS Summa FROM PaySumma WHERE PaySum > 1000 GROUP BY AccountCD;, то запрос возвратит пустой набор. Это происходит потому, что фильтр секции WHERE оценивается построчно, а строк со значением отдельных оплат больше 1000 нет. Поэтому нужно использовать фильтр по группе оплат, а не по каждой оплате: SELECT AccountCD, SUM (PaySum) AS Summa FROM PaySumma GROUP BY AccountCD HAVING Summa > 1000; Однако этот запрос не выполнится из-за ошибки – псевдоним Summa невидим для секции HAVING, так как она оценивается перед секцией SELECT. Для устранения ошибки в секции HAVING следует обратиться к самому столбцу: SELECT AccountCD, SUM (PaySum) AS Summa FROM PaySumma GROUP BY AccountCD HAVING SUM (PaySum) > 1000; 3.2.6. Секция ORDER BY Строки НД, как и строки таблиц БД, не имеют определенного порядка. Включив в запрос SELECT секцию ORDER BY, можно отсортировать результаты запроса. Секция ORDER BY состоит из ключевого слова ORDER BY, за которым следует список элементов сортировки, каждый из которых имеет такой синтаксис: <элемент_сортировки> :: ={{[<таблица>.] столбец | порядковый_номер_столбца | псевдоним_столбца | <выражение>} [ASC[ENDING] | DESC[ENDING]] [NULLS FIRST | NULLS LAST]}… . Использование ASCENDING и DESCENDING вместо ASC и DESC допустимо только в Firebird, в других рассматриваемых СУБД – нет. Опция 19 DESC означает сортировку по убыванию. Если указать необязательную и используемую по умолчанию опцию ASC, то сортировка будет произведена по возрастанию. Например, для вывода начислений абонентам за декабрь 2010 г., упорядоченных по убыванию значений (рис. 3.71), следует использовать запрос: SELECT NachislFactCD, AccountCD, NachislSum FROM NachislSumma WHERE NachislMonth=12 AND NachislYear=2010 ORDER BY NachislSum DESC; NACHISLFACTCD 38 2 50 ACCOUNTt NACHISLSUM CD 080270 58,10 005488 46,00 136160 18,30 Рис. 3.71. Результат выполнения запроса с сортировкой по убыванию Можно производить сортировку по нескольким столбцам. Старшинство столбцов, по которым осуществляется сортировка, в секции ORDER BY определяется слева направо. Например, требуется вывести значения оплат абонентов с лицевыми счетами '136169', '005488', '443690'. Результаты запроса упорядочить по возрастанию номеров лицевых счетов, а затем по уменьшению значения оплаты. Соответствующий запрос имеет такой вид: SELECT AccountCD, PaySum ACCOUNTCD PAYSUM FROM PaySumma 005488 280,00 WHERE AccountCD IN 005488 260,00 ('136169','005488','443690') 005488 65,00 ORDER BY AccountCD, 005488 58,70 PaySum DESC; 005488 56,00 Результат выполнения 40,00 запроса представлен на 005488 136169 528,44 рис. 3.72. 349,19 SQL позволяет 136169 346,18 упорядочивать строки НД 136169 58,70 не только по тем столбцам, 136169 которые в нем 136169 28,32 присутсвуют, но и по 136169 28,32 столбцам исходной 136169 25,32 таблицы и выражениям над 136169 20,00 ними. Например, запрос 443690 485,00 SELECT PaySum FROM 443690 295,00 PaySumma 443690 20,00 WHERE AccountCD IN 20 ('136169','005488','443690') ORDER BY AccountCD, PaySum DESC; выведет строки в том же порядке, что и в предыдущем запросе, хотя столбец AccountCD выражения сортировки отсутствует в НД. 443690 19,47 Рис. 3.72. Результат выполнения запроса с сортировкой по нескольким столбцам При работе с MS SQL Server и Oracle следует учесть, что если в запросе SELECT используются и секция ORDER BY, и опция DISTINCT, то секция ORDER BY не может ссылаться на столбцы, которые отсутствуют в НД. Например, в MS SQL Server и Oracle недопустим запрос вида SELECT DISTINCT PaySum FROM PaySumma WHERE AccountCD IN ('136169','005488','443690') ORDER BY AccountCD, PaySum DESC; В списке элементов сортировки для столбцов в НД с именами и без имен (например, вычисляемых столбцов) можно указывать их порядковый номер в списке возвращаемых элементов секции SELECT. Нумерация столбцов в списке начинается с 1 и осуществляется в направлении слева направо. Примером этому может служить запрос: SELECT DISTINCT AccountCD, (NachislSum +100) AS "NachislSum_100" FROM NachislSumma WHERE ServiceCD =2 AND AccountCD LIKE '1%' ORDER BY 2; Здесь сортировка выполняется по значениям вычисляемого столбца (NachislSum+100), который имеет порядковый номер 2 в списке возвращаемых элементов секции SELECT. Результат выполнения запроса представлен на рис. 3.73. ACCOUNTCD 136160 136169 115705 136160 115705 136169 115705 NACHISLSUM_100 120,00 120,00 140,00 156,00 158,70 158,70 350,00 Рис. 3.73. Результат с сортировкой по номеру столбца выполнения запроса В секции ORDER BY можно указать псевдоним столбца, например NachislSum_100 в предыдущем примере. Результат выполнения будет совпадать с результатом выполнения предыдущего запроса. 21 Сортировку по номеру элемента в списке можно использовать при выборке всех столбцов из таблицы. Пусть, например, требуется вывести все данные об улицах, отсортированные по названию улиц (рис. 3.74): SELECT * FROM Street ORDER BY 2; STRE ETCD 3 5 7 6 8 2 4 1 STREETNM ВОЙКОВ ПЕРЕУЛОК ГАГАРИНА УЛИЦА КУТУЗОВА УЛИЦА МОСКОВСКАЯ УЛИЦА МОСКОВСКОЕ ШОССЕ НОВАЯ УЛИЦА ТАТАРСКАЯ УЛИЦА ЦИОЛКОВСКОГО УЛИЦА Рис. 3.74. Результат выполнения с сортировкой при выборке всех данных запроса Примечание. Не рекомендуется в приложениях использовать запросы с сортировкой по номерам столбцов. Это связано с тем, что со временем структура таблицы может измениться, например, в результате добавления или удаления столбцов. Как следствие, запрос с сортировкой по порядковому номеру может дать совсем другую последовательность или вообще вызвать ошибку, ссылаясь на отсутствующий столбец [50]. В качестве выражения сортировки можно использовать функцию. В этом случае сортировке подвергается результат, возвращаемый функцией для каждой строки. Например, для вывода информации о первых четырех абонентах, имеющих наиболее длинные ФИО, в Firebird можно использовать запрос SELECT FIRST 4 * FROM Abonent ORDER BY CHAR_LENGTH(Fio) DESC; В данном случае строки НД сортируются в порядке уменьшения числа символов в ФИО абонентов. Результат выполнения запроса представлен на рис. 3.75. ACCOUNTC D 443069 STREETC D 4 HOUSEN O 51 FLATN O 55 080270 6 35 6 136169 4 7 13 080613 8 35 11 FIO Стародубце в Е.В. Тимошкина Н.Г. Денисова Е.К. Лукашина PHON E 683014 321002 680305 254417 22 Р.М. Рис. 3.75. Результат выполнения запроса с сортировкой по значению функции В MS SQL Server аналогичный запрос будет выглядеть так: SELECT TOP(4) * FROM Abonent ORDER BY LEN(Fio) DESC;, а в Oracle так: SELECT * FROM Abonent WHERE ROWNUM <= 4 ORDER BY LENGTH(Fio) DESC; Для возврата трех строк таблицы Abonent, выбранных случайным образом, в Firebird, например, можно использовать запрос SELECT FIRST 3 * FROM Abonent ORDER BY RAND(); Иногда при сортировке может оказаться важным расположение в выходном НД NULL-маркеров. По умолчанию в Firebird и MS SQL Server при сортировке по возрастанию значения NULL располагаются в конце списка, а при сортировке по убыванию – в начале. В Oracle – наоборот. В соответствии с SQL-стандартом существует возможность определить положение NULL в выходном НД. Такую возможность поддерживают Firebird и Oracle. Результаты могут быть отсортированы таким образом, что NULL будут располагаться выше (NULLS FIRST) или ниже (NULLS LAST) остальных результатов запроса, отличных от NULL. Примечание. Если задано NULLS FIRST, то для сортировки не может быть использован индекс (использование индексов будет описано далее). Например, для вывода в Firebird номеров и дат выполнения ремонтных заявок, поданных абонентами, номера лицевых счетов которых начинаются с '08' или с '11', отсортированных по убыванию дат выполнения и с неопределенными значениями в начале списка (рис. 3.76), можно использовать такой запрос: SELECT RequestCD, ExecutionDate, AccountCD FROM REQUEST WHERE (AccountCD LIKE '08%') OR (AccountCD LIKE '11%') ORDER BY ExecutionDate DESC NULLS FIRST; REQUESTCD 16 5 18 7 19 20 17 2 EXECUTIONDAT E NULL NULL 04.01.2013 24.10.2012 27.12.2011 11.10.2011 06.09.2011 12.08.2011 ACCOUNTCD 115705 080270 115705 080047 080270 080047 115705 115705 23 6 15 24.06.2011 23.09.2010 Рис. 3.76. Результат запроса неопределенных значений 080613 115705 с указанием положения Возможна сортировка НД по частям строки. Например, следующий запрос в Firebird возвратит абонентов из таблицы Abonent, упорядоченных по инициалам: SELECT * FROM Abonent ORDER BY RIGHT(Fio, 4); Для сортировки по ключу, зависящему от данных, можно использовать операцию CASE. Например, если код улицы равен 4, следующий запрос возвратит строки таблицы Abonent упорядоченными по Fio, а в противном случае – упорядоченными по Phone: SELECT * FROM Abonent ORDER BY CASE WHEN StreetCD=4 THEN Fio ELSE Phone END; Секция ORDER BY является последней в запросе SELECT. Следовательно, НД можно упорядочивать по группам, например так в Firebird: SELECT AccountCD, SUM(NachislSum) FROM NachislSumma GROUP BY 1 ORDER BY 1; или так: SELECT AccountCD, SUM(NachislSum) FROM NachislSumma GROUP BY 1 ORDER BY 2; 3.2.7. Статистический анализ данных в SQL Оконные (аналитические) функции были введены в стандарте SQL:2003 [40, 43]. Такие функции предназначены для статистического анализа данных в готовой выборке и часто используются в ряде аналитических приложений. Спектр применения аналитических функций достаточно широк – это различного рода распределения, процентирование, линейная регрессия, стандартные отклонения, дисперсии, нарастающие итоги и т. д. [51]. В принципе практически все, что можно получить с помощью аналитических функций, можно получить и без них, с помощью обычных SQL-запросов, но это будет более громоздко и зачастую не так эффективно. Оконные функции не только обеспечивают простые выразительные средства для формулировки аналитических запросов, но также могут способствовать эффективной оптимизации и выполнению запросов, позволяя избежать многочисленных соединений и наличия нескольких блоков запроса. Синтаксис оконных функций выглядит следующим образом [42]: <имя_оконной_функции>( [<выражение> [, <выражение> ...]]) OVER ( [PARTITION BY <выражение> [, <выражение> ...]] [ORDER BY <выражение> [{ASC | DESC}] [NULLS {FIRST | LAST}] [, <выражение> [{ASC | DESC}] [NULLS {FIRST | LAST}] ...] ). Любые оконные функции всегда используются с секцией OVER(). 24 Примечание. Если необходимо использовать две аналитические функции в одном запросе, то для каждой функции конструкция OVER() указывается отдельно. Также может быть указан список группировки (секция PARTITION BY) или сортировки (ORDER BY) обрабатываемых аналитической функцией данных. Одно из главных отличий аналитических функций от обычных, скалярных, заключается в том, что они работают с готовой выборкой. Аналитические функции представляют собой особый вид агрегации исходных данных, при этом исходный набор данных не фильтруется. Сначала формируется выборка, выполняются все объединения, условия WHERE, GROUP BY, HAVING – все, кроме сортировки, и только затем к практически готовому набору данных применяется аналитическая функция. В связи с этим аналитические функции можно указывать лишь в списке выборки SELECT или в секции ORDER BY. В общем случае принцип работы аналитических функций можно представить следующим образом. Допустим, что у нас есть результирующий набор данных, полученный вышеописанным способом, – выполнено все, кроме сортировки. На каждую запись в этом наборе накладывается так называемое «окно», размеры и положение которого определяются в соответствии с некоторым аналитическим условием (отсюда и название этого класса функций – «оконные функции», windows functions). В это виртуальное «окно» попадают несколько других записей из того же набора, то есть целая группа записей. При этом «окно» может быть сформировано таким образом, что в него попадут практически произвольные записи из набора, и даже сама текущая запись, на основании которой формируется «окно», может в это «окно» не попасть (в дальнейшем термин «окно» будет использоваться для обозначения именно такого набора записей). Когда «окно» сформировано, аналитическая функция вычисляет агрегированное значение по записям, входящим в «окно», и переходит к следующей записи. Для этой записи формируется новое «окно», снова вычисляется агрегированное значение – и так для всех записей в выборке. При этом размер и положение «окна» от записи к записи может меняться, в таких случаях используют термин «скользящее окно» (sliding window). Агрегатные функции. Любые агрегатные функции могут быть использованы как оконные путем добавления секции OVER(). Отличие состоит в том, что обычные агрегатные функции уменьшают степень детализации результирующего набора, а в аналитическом варианте степень детализации не уменьшается. Это относится и к другим типам аналитических функций. Рассмотрим пример использования агрегатной функции в качестве аналитической. Допустим, нужно вывести по каждому абоненту соответствующую ему сумму начислений и процент его начислений от общей суммы начислений (рис. 3.77). С помощью обычного запроса без использования аналитической функции требуемая информация может быть получена таким образом: 25 SELECT N.AccountCD, SUM(N.NachislSum), (SUM(N.NachislSum) / (SELECT SUM(N1.NachislSum) FROM NachislSumma N1)) AS "Percent_Summa" FROM NachislSumma N GROUP BY N.AccountCD ORDER BY N.AccountCD; ACCOUNTCD 005488 015527 SUM PERCENT_SUMMA 769,33 0,0594 1619,7 0,125 2 080047 528,48 0,0408 080270 1851,9 0,143 2 080613 867,19 0,0669 115705 1301,8 0,1005 126112 656,00 0,0506 136159 964,99 0,0745 136160 588,78 0,0454 136169 1384,4 0,1069 7 443069 1106,2 0,0854 4 443690 1310,8 0,1012 1 Рис. 3.77. Результат работы аналитической функции в Firebird Результаты выполнения запроса в MS SQL Server и Oracle будут несколько отличаться от результата на рис. 3.77 засчет различий СУБД в области вычисления арифметических выражений (см. п. 2.9). Точность значений в столбце Percent_Summa в MS SQL Server и Oracle будет выше, чем в Firebird. Данные, возвращенные предыдущим запросом, можно получить проще с помощью запроса, использующего вместо подзапроса аналитическую функцию: SELECT N.AccountCD, SUM(N.NachislSum), (SUM(N.NachislSum) / SUM(SUM(N.NachislSum)) OVER()) AS "Percent_Summa" FROM NachislSumma N GROUP BY N.AccountCD ORDER BY N.AccountCD; В приведенном запросе функция SUM(SUM(N.NachislSum)) OVER() вычисляется путем суммирования всех SUM(N.NachislSum) из запроса. Подобно тому, как обычные агрегатные функции могут применяться по отношению к сгруппированным данным, аналитические функции могут оперировать с данными определенной группы (порции, "partition"), которая определяется секцией PARTITION BY. В PARTITION BY указывается список столбцов, по которым производится группировка, и таким образом формируется «окно» для работы аналитической функции. В «окно» попадают все строки, сгруппированные по указанным столбцам, и в общем случае «окно» содержит более одной строки. Набор данных «окна» соединяется с набором данных основного запроса по столбцам, указанным в секции PARTITION BY. 26 Группировка PARTITION BY делает практически то же самое, что и GROUP BY, но с несколькими отличиями: производится по уже сформированной выборке и распространяется только на тот агрегат, после которого идет опция OVER (), а не на все столбцы. Для демонстрации работы аналитических функций с группировкой модифицируем предыдущий пример. Для каждого абонента в столбце Percent_Summa вместо процента от общей суммы всех начислений выведем процент от общей суммы начислений абонентов, проживающих на той же улице (рис. 3.78). Запрос будет выглядеть следующим образом: SELECT A.AccountCD, A.StreetCD, SUM(N.NachislSum), (SUM(N.NachislSum) / SUM(SUM(N.NachislSum)) OVER(PARTITION BY A.StreetCD)) AS "Percent_Summa" FROM Abonent A INNER JOIN NachislSumma N ON A.AccountCD = N.AccountCD GROUP BY A.AccountCD, A.StreetCD ORDER BY A.AccountCD; ACCOUNTCD STREETCD 005488 3 015527 3 080047 080270 8 6 080613 115705 8 3 126112 136159 136160 136169 4 7 4 4 443069 4 443690 7 Рис. 3.78. Результат группировкой в Firebird SUM 769,33 1619,7 2 528,48 1851,9 2 867,19 1301,8 656,00 964,99 588,78 1384,4 7 1106,2 4 1310,8 1 работы PERCENT_SUMMA 0,2084 0,4388 0,3786 1,0000 0,6213 0,3527 0,1756 0,4240 0,1576 0,3706 0,2961 0,5759 аналитической функции с Аналитические функции могут оперировать с данными, отсортированными определенным образом. В этом случае подсчитывается значение, начиная от первой строки «окна» и заканчивая текущей обрабатываемой строкой, т. е. подсчет производится в границах определенного диапазона данных. Сортировка определяется списком столбцов, указанных в секции ORDER BY функции, и может использоваться совместно с секцией PARTITION BY. В одном запросе можно использовать несколько оконных функций с разными сортировками. Допустим, необходимо вывести по каждому абоненту соответствующую ему сумму начислений, а также общую сумму всех начислений, не 27 превышающих величины начисления абонента. Данные необходимо отсортировать по суммам начислений абонентам (рис. 3.79). Запрос, реализующий данную задачу: SELECT N.AccountCD, SUM(N.NachislSum), SUM(SUM(N.NachislSum)) OVER(ORDER BY SUM(N.NachislSum)) AS "Cumul_Sum" FROM NachislSumma N GROUP BY N.AccountCD ORDER BY SUM(N.NachislSum); В данном примере аналитическая функция для каждой строки суммирует все начисления, не превышающие значения столбца Sum. Следовательно, для абонента (номер лицевого счета '080047') с наименьшим начислением значение столбца Cumul_Sum равно значению столбца Sum, для абонента с номером лицевого счета '136160' значение столбца Cumul_Sum равно сумме значений столбца Sum первых двух строк и т. д. ACCOUNTCD 080047 136160 126112 005488 080613 136159 443069 115705 443690 136169 015527 080270 SUM 528,48 588,78 656,00 769,33 867,19 964,99 1106,2 4 1301,8 1310,8 1 1384,4 7 1619,7 2 1851,9 2 CUMUL_SUM 528,48 1117,26 1773,26 2542,59 3409,78 4374,77 5481,01 6782,81 8093,62 9478,09 11097,81 12949,73 Рис. 3.79. Результат работы аналитической функции с сортировкой в Firebird В примере нет повторяющихся значений суммы (столбец Sum). Если бы присутствовали одинаковые значения, то в столбце Cumul_Sum были бы тоже одинаковые значения, так как в секции ORDER BY указан только столбец Sum. Данные для каждого набора значений столбцов секции ORDER BY обрабатываются аналитической функцией только раз. В следующем примере рассматривается применение функций MIN, MAX, AVG и COUNT с секцией OVER для получения статистических значений оплат для каждой из услуг в таблице PaySumma: SELECT DISTINCT ServiceCD, 28 MIN(Paysum) OVER (PARTITION BY ServiceCD) AS MinPay, MAX(Paysum) OVER (PARTITION BY ServiceCD) AS MaxPay, AVG(Paysum) OVER (PARTITION BY ServiceCD) AS AvgPay, COUNT(Paysum) OVER (PARTITION BY ServiceCD) AS CountPay FROM PaySumma ORDER BY ServiceCD; Статистические функции Oracle. В СУБД Oracle поддерживаются различные виды статистических функций, большая часть из которых может использоваться как в качестве агрегирующих функций, так и в качестве аналитических. Ниже перечислены статистические функции Oracle с указанием их форматов и кратким описанием [49].  CORR(число1, число2) [OVER (выражение)] – коэффициент корреляции множества пар чисел. В статистическом смысле, корреляция – это степень связи между переменными. Связь между переменными означает, что значение одной переменной можно в определенной степени предсказать по значению другой. Коэффициент корреляции представляет степень корреляции в виде числа в диапазоне от -1 (высокая обратная корреляция) до 1 (высокая корреляция). Значение 0 соответствует отсутствию корреляции;  COVAR_POP (числовое_выражение1, числовое_выражение2) ковариация совокупности множества пар чисел;  COVAR_SAMP (числовое_выражение1, числовое_выражение2) – выборочная ковариация набора пар чисел;  CUME_DIST (выражение1, … выражениеN) WITHIN GROUP ( ORDER BY выражение1, … выражениеN) – кумулятивное распределение значений в группе значений. Функция всегда возвращает число, которое > 0 и <= 1. Это число представляет относительную "позицию" строки в группе из N строк. В группе из трех строк, например, возвращаются следующие значения кумулятивного распределения: 1/3, 2/3 и 3/3;  STDDEV ([DISTINCT|ALL] выражение) – стандартное отклонение списка чисел;  VARIANCE(выражение) – дисперсия набора чисел;  VAR_POP (выражение) – дисперсия совокупности множества чисел;  VAR_SAMP (выражение) – выборочная дисперсия набора чисел. Функции ранжирования. Помимо агрегатных аналитических функций, выделяют аналитические функции ранжирования. Такие функции возвращают ранг каждой строки внутри «окна». В общем случае рангом является некое число, отражающее положение или «вес» записи относительно других записей в том же наборе. Формируется «окно» точно так же, как и в случае агрегатных функций – с помощью группировки. Однако, поскольку результат работы функций ранжирования зависит от порядка обработки записей, то обязательно должен быть указан порядок записей внутри «окна» посредством секции ORDER BY. В зависимости от используемой функции некоторые записи могут получать один и тот же ранг. Функции ранжирования могут быть использованы для создания различных типов счетчиков по возрастанию. Все ранжирующие функции являются 29 недетерминированными, то есть при одних и тех же входных значениях они могут возвращать разный результат. К функциям ранжирования относятся функции ROW_NUMBER(), RANK() и DENSE_RANK(). Функция ROW_NUMBER() служит для нумерации записей в указанном порядке внутри «окна». При этом, если в опции OVER опустить секцию PARTITION BY, то за «окно» будет принята вся выборка, что дает возможность пронумеровать все записи в нужном порядке, причем порядок нумерации может не совпадать с порядком записей в результирующей выборке, то есть секция ORDER BY внутри OVER(), определяющая порядок сортировки записей внутри «окна», и соответственно порядок нумерации записей могут не совпадать с секцией ORDER BY основного запроса SELECT. Нумерация всегда начинается с единицы, порядковые номера меняются, даже когда есть повторяющиеся значения. Функция RANK() предназначена для ранжирования записей внутри «окна», при этом, если колонка для группировки не задана явным образом, то за «окно» принимается вся выборка. Рангом каждой записи является количество уже ранжированных записей с более высоким рангом, чем текущая, плюс единица. Если встретятся несколько записей с одинаковым значением, по которому производится ранжирование, то этим записям будет присвоен одинаковый ранг. Однако при этом следующая запись с новым значением получит такой ранг, как будто бы предыдущие записи получили свой уникальный номер, то есть образуется дырка. Фактически это та же нумерация, что и в ROW_NUMBER(), которая начинается с той же единицы. Различие в том, что одинаковые записи получают одинаковый номер, а следующая отличающаяся от них запись получает такой номер, как если бы использовалась ROW_NUMBER(), и все предыдущие записи получили свои уникальные номера. Таким образом, образуется дырка в нумерации, равная количеству одинаковых записей минус единица. Функция DENSE_RANK() выполняет «плотное» ранжирование, то есть делает то же самое, что и функция RANK(), но без «дырок» в нумерации. Рассмотрим пример использования ранжирующих аналитических функций. Допустим, необходимо вывести по каждому абоненту соответствующую ему сумму начислений, при этом пронумеровав строки результата (рис. 3.80). Модифицируем предыдущий запрос путем замены аналитической функции на функцию ROW_NUMBER(), в результате чего получим запрос: SELECT ROW_NUMBER() OVER(ORDER BY SUM(N.NachislSum)), N.AccountCD, SUM(N.NachislSum) FROM NachislSumma N GROUP BY N.AccountCD ORDER BY SUM(N.NachislSum); ROW_NUMBE R 1 2 ACCOUNTC D 080047 136160 SUM 528,48 588,78 30 3 126112 656,00 4 005488 769,33 5 080613 867,19 6 136159 964,99 7 443069 1106,24 8 115705 1301,80 9 443690 1310,81 10 136169 1384,47 11 015527 1619,72 12 080270 1851,92 Рис. 3.80. Результат работы функции ROW_NUMBER() Навигационные функции. Навигационные функции предназначены для получения простого (неагрегированного) значения выражения из определенной строки «окна». Навигационные функции в Firebird и Oracle представлены статистическими функциями для интервалов агрегирования FIRST_VALUE, LAST_VALUE и NTH_VALUE и функциями сдвига наблюдений LAG и LEAD с запаздывающим и опережающим аргументами. В MS SQL Server функция NTH_VALUE не используется. Функции FIRST_VALUE и LAST_VALUE позволяют для каждой строки выдать первое и последнее значения выражения из упорядоченного «окна». Функции имеют синтаксис FIRST_VALUE(<выражение>), LAST_VALUE(<выражение>). Функция NTH_VALUE получает N-е значение (N задается как <смещение>), начиная с первой (по умолчанию) или последней записи из упорядоченного «окна», т. е. позволяет выделить N-й элемент из аналитического «окна». Если <смещение> равно 1 от начала, то функция эквивалентна функции FIRST_VALUE, если <смещение> равно 1 с конца, то функция эквивалентна функции LAST_VALUE. Функция имеет синтаксис NTH_VALUE(<выражение>, <смещение>) [FROM FIRST | FROM LAST]. Следует учесть, что при использовании функций для интервалов агрегирования FIRST_VALUE и LAST_VALUE и NTH_VALUE «окно» формируется от первой до текущей (а не до последней) строки порции данных. Функции сдвига наблюдений LAG и LEAD являются статистическими функциями соответственно с запаздывающим и опережающим аргументом. Такие функции могут быть полезными в приложениях эконометрики и других задачах, связанных с анализом данных, представляющих собой временные ряды, где часто возникает потребность сдвигать эти ряды относительно друг друга. Это может быть необходимо для расчёта доходностей ценных бумаг, вычисления автокорреляций и т. д. Иногда сдвиг рядов применяется и вне временного контекста, в ходе преобразования данных. Функции имеют синтаксис: 31 LAG(<выражение> [ [, <смещение> [, <значение_по_умолчанию> ] ] ), LEAD(<выражение> [ [, <смещение> [,<значение_по_умолчанию> ] ] ). Функция LAG внутри «окна» ищет значение <выражения> для строки, которая находится выше текущей на <смещение> строчек (по умолчанию <смещение> равно 1). Функция LEAD внутри «окна» ищет значение <выражения> для строки, которая находится ниже текущей на <смещение> строчек. В случае, когда <смещение> выходит за пределы «окна», возвращается <значение_по_умолчанию> (по умолчанию NULL). На практике обычно требуется сдвигать данные с запаздыванием – так, чтобы в текущем наблюдении присутствовали данные за предыдущее время. Модифицируем пример, выводящий суммы начислений для каждого абонента, дополнив его вычислением различных навигационных функций: SELECT N.AccountCD, SUM(N.NachislSum), FIRST_VALUE (SUM(N.NachislSum)) OVER(ORDER BY SUM(N.NachislSum)), LAST_VALUE (SUM(N.NachislSum)) OVER(ORDER BY SUM(N.NachislSum)), NTH_VALUE (SUM(N.NachislSum), 2) OVER(ORDER BY SUM(N.NachislSum)), LAG (SUM(N.NachislSum)) OVER(ORDER BY SUM(N.NachislSum)), LEAD (SUM(N.NachislSum)) OVER(ORDER BY SUM(N.NachislSum)) FROM NachislSumma N GROUP BY N.AccountCD ORDER BY SUM(N.NachislSum); Результат выполнения запроса представлен на рис. 3.81. ACCOUNTCD SUM 080047 136160 126112 005488 080613 136159 443069 115705 443690 136169 015527 080270 528,48 588,78 656,00 769,33 867,19 964,99 1106,2 4 1301,8 1310,8 1 1384,4 7 1619,7 2 1851,9 2 FIRST_VAL UE 528,48 528,48 528,48 528,48 528,48 528,48 528,48 LAST_VAL UE 528,48 588,78 656,00 769,33 867,19 964,99 1106,24 NTH_VAL UE NULL 588,78 588,78 588,78 588,78 588,78 588,78 528,48 1301,80 588,78 528,48 1310,81 588,78 528,48 1384,47 588,78 528,48 1619,72 588,78 528,48 1851,92 588,78 LAG LEAD NULL 528,48 588,78 656,00 769,33 867,19 964,99 588,78 656,00 769,33 867,19 964,99 1106,24 1301,80 1106,2 4 1301,8 1310,8 1 1384,4 7 1619,7 2 1310,81 1384,47 1619,72 1851,92 NULL Рис. 3.81. Результат работы навигационных функций 32      В приведенном примере «окно» для каждой строки содержит значения сумм от самой минимальной до суммы, не превышающей сумму в текущей строке. Из рис. 3.81 понятно: что функция FIRST_VALUE возвращает значение первой суммы из «окна», т. е. наименьшей суммы, которая для всех строк одинаковая; функция LAST_VALUE возвращает значение последней суммы из «окна», т. е. суммы, равной сумме в текущей строке; функция NTH_VALUE возвращает значение второй суммы из «окна», которое одинаково для всех строк, кроме первой (для первой строки «окно» содержит только одну запись, поэтому второе значение возвращает NULL); функция LAG возвращает значение предыдущей суммы из «окна» для текущей строки, т. е. предыдущую меньшую по величине сумму отсортированного ряда (для первой строки «окно» содержит только одну запись, поэтому предыдущее значение возвращает NULL); функция LEAD возвращает значение следующей суммы из «окна» для текущей строки, т. е. следующую по величине сумму отсортированного ряда (для последней строки «окно» не содержит следующего по величине значения, поэтому возвращается NULL). 33
«Средства выбора вариантов» 👇
Готовые курсовые работы и рефераты
Купить от 250 ₽
Решение задач от ИИ за 2 минуты
Решить задачу
Помощь с рефератом от нейросети
Написать ИИ
Получи помощь с рефератом от ИИ-шки
ИИ ответит за 2 минуты

Тебе могут подойти лекции

Смотреть все 588 лекций
Все самое важное и интересное в Telegram

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

Перейти в Telegram Bot