Новые возможности T-SQL в SQL Server 2005 - Часть 1/3Источник: SQL Exercises
В статье прошлого месяца, я рассказал о нескольких новых возможностях XML, которые разработчики могут использовать в SQL Server 2005. В этом месяце я начинаю серию из трех статей о новых возможностях языка Transact-SQL в SQL Server 2005. Microsoft добавила много нового в SQL Server 2005 в ответ на пожелания разработчиков, а также в дальнейшем следовании стандарту SQL-99. В этом месяце мы рассмотрим три новые языковые конструкции: 1 - PIVOTОдной из самых общих задач, с которой сталкиваются разработчики приложений, - это преобразование строк "сырых" строк базы данных к некоторому типу аналитического представления. Пользователи часто хотят видеть данные, просуммированные по кварталам, месяцам или еще по какой-нибудь другой бизнес-категории. Для этого требуется преобразовать строки данных в столбцы. В SQL Server 2000 разработчики в основном имели два варианта: - Извлечь запросом сырые SQL данные в слой приложения и написать код на C#, VB или на некотором другом языке, и далее агрегировать данные в необходимый формат. - Использовать выражение CASE T-SQL, чтобы привести сырые данные к необходимому формату столбца на основании соответствующего условия. Например, рассмотрим таблицу OpenBalances со столбцами DueDate и BalanceOwed; и получим временной отчет, распределяющий открытые балансы по стандартным интервалам времени (1-30 дней, 31-60, 61-90, и т.д.) на основании срока переменной даты. SELECT SUM(CASE WHEN DueDate BETWEEN @dAgingDate-30 AND @dAgingDate При том, что оба подхода работают, SQL Server 2005 предоставляет новое ключевое слово PIVOT, чтобы более ясно и просто решить эту задачу. Просто формулируемый, PIVOT позволяет разработчикам превращать строки данных в столбцы. Давайте взглянем на два примера кода, использующих PIVOT. Для первого примера возьмем заказы из базы данных Northwind Orders и получим результирующий набор, который суммирует для каждого клиента величины заказов по кварталам 1997 года. sp_dbcmptlevel Northwind, 90 - необходимо выполнить для баз данных ранних версий USE northwind - Создаем табличную переменную для хранения результатов DECLARE @tQtrPivotedTable TABLE (CustomerID char(25), M_Q1 Money, M_Q2 Money, M_Q3 Money, M_Q4 Money) INSERT INTO @tQtrPivotedTable SELECT * FROM @tQtrPivotedTable - Выборка результатов Вышеприведенный код определяет соответствующий квартал для каждой даты заказа (OrderDate ), используя функцию SQL DatePart. Затем код поворачивает (то есть преобразует строки в столбцы) сумму объемов заказов (Amount) в зависимости от квартала, представленного одним из четырех значений. Если Вы захотите просуммировать заказы клиента по месяцам, то следует сделать три вещи: изменить табличную переменную, чтобы она имела по одному столбцу на каждый месяц, использовать DartPart для месяца (m) вместо квартала (q) и изменить ссылки с OrderQtr на OrderMonth, а в PIVOT на OrderMonth IN ([1], [2], [3]...... [12]). Важно отметить, что список значений предиката IN ДОЛЖЕН быть предопределенным, жестко закодированным значением. Значение, для которого применяется PIVOT, (в нашем случае, OrderQtr) должен равняться одному из значений списка. Если список рассматриваемых значений является переменным, Вы должны строить строку запроса вручную и использовать динамический SQL. Вернемся к нашему примеру о сроках заказов, поскольку это один из наиболее общих результирующих наборов, который должны выдавать разработчики бизнес-приложений. Это займет немного больше времени, но продемонстрирует, как мы можем использовать PIVOT в более конкретной ситуации. Сначала предположим, что мы имеем таблицу счетов клиентов, содержащую дату счета, сумму счета, и величину, полученную на дату... DECLARE @tInvoices TABLE (CustomerID char(15), InvoiceNo Char(20), InvoiceDate DateTime,InvoiceAmount decimal(14,2), ReceivedAmount decimal(14,2)) INSERT INTO @tInvoices VALUES ('Customer 1', 'ABC', '09-01-2005', 1000, 0) Затем каждый отчет о сроках должен иметь дату 'НА' (например, отчет на начало месяца ...) DECLARE @dAgingDate DATETIME Кроме того, не каждый отчет имеет один и тот же диапазон дат. Поскольку чаще всего определяют такие диапазоны сроков, как 1-30 дней, 31-60, и т.д, нам следует сделать это посредством таблицы. Поэтому мы создадим табличную переменную, которая будет хранить определения сроков на основании диапазонов дней. - создадим интервалы посредством границ..., которые можно изменять [1-45 дней, и т.д.] DECLARE @tAgingBrackets TABLE ( StartDay int, EndDay int, BracketNumber int, BracketLabel char(20)) Теперь создадим табличную переменную для хранения результирующего набора - каждый клиент, дата/номер счета и пять столбцов для пяти временных интервалов: - создаем наш результирующий набор DECLARE @tAgingDetails TABLE (CustomerID char(15), InvoiceNo char(20), InvoiceDate DateTime, Наконец, мы используем оператор PIVOT, чтобы преобразовать Amount Owed (величину счета, полученную к настоящему времени) к одному из пяти сроков, сопоставляя BracketNumber из определения @tAgingBrackets, основанного на числе дней, прошедших от даты выставления счета, с номером соответствующего интервального срока. INSERT INTO @tAgingDetails SELECT * FROM @tAgingBrackets Если описанный выше пример кажется вам слишком пространным, пробуем так: мы используем функцию DateDiff, чтобы определить, что второй счет (датированный 10/1/05) имеет срок 61 день на дату 12-1-2005. Запрос выполняет поиск в таблице @tAgingBrackets, где разность (61 день) находится в диапазоне между заданными начальной и конечной датами. В нашем случае он падает в третий интервал, и мы определяем в PIVOT для Amount Owed номер интервала (Bracket Number) со значением 3. Одно заключительное примечание относительно PIVOT: я использовал табличные переменные для временного хранения результирующих наборов, отчасти, для того, чтобы самодокументировать эту статью. В последующей статье я расскажу о новой возможности SQL Server 2005 - Общем Табличном Выражении (CTE), - и о том, как мы можем использовать CTE вместо табличных переменных. 2 - OUTPUTКак поступить в SQL Server 2000, если вы захотите сразу запросить таблицу после оператора INSERT или UPDATE, чтобы вернуть значение столбца, подвергнувшегося изменению? Это может быть результат вычисляемого столбца или значение столбца identity, или некоторое другое значение по умолчанию. Обычно вы либо следом пишите оператор SELECT в хранимой процедуре, либо из своего приложения посылаете вызов обратно на сервер, чтобы получить этот результат. А как в SQL Server 2000 сразу вернуть и старое, и новое значения, после обновления данных (UPDATE)? Зачастую вам приходится обращаться к системным таблицам INSERTED и DELETED, которые видны только в триггере. Например, в SQL Server 2000, если мы хотим определить значение столбца IDENTITY после выполнения оператора INSERT, нам часто приходится использовать функцию SQL Server SCOPE_IDENTITY: DECLARE @tTestTable TABLE ( MainPK [int] IDENTITY(1,1) NOT NULL, Name Char(50)) SQL Server 2005 предоставляет новый оператор OUTPUT, который позволяет разработчикам решать эти задачи более удобно и эффективно. Используя OUTPUT в сочетании с оператором INSERT/UPDATE, мы можем легко прочитать добавленную/измененную информацию. Вместо использования SCOPE_IDENTITY, мы можем сразу ВЫВЕСТИ значение столбца IDENTITY: DECLARE @tTestTable TABLE ( MainPK [int] IDENTITY(1,1) NOT NULL, Name Char(50)) Если Вы выполняете несколько операторов INSERT, и вам необходим после этого список вставленных строк, Вы можете перенаправить ВЫВОД в таблицу: DECLARE @tTestTable TABLE ( MainPK [int] IDENTITY(1,1) NOT NULL, Name Char(50)) Далее, если Вы выполняете оператор UPDATE и хотите сразу увидеть новое/старое значения из системных таблиц INSERTED и DELETED, Вы можете сделать следующее: DECLARE @tTest TABLE ( MainPK [int] IDENTITY(1,1) NOT NULL ,Amount decimal(10,2)) UPDATE @tTest SET Amount = Amount * 10 И снова, если Вы хотите перенаправить результаты OUTPUT в табличную переменную, то можно сделать следующее: DECLARE @tTemp TABLE (MainPK int, OldValue Decimal(10,2), NewValue Decimal(10,2)) UPDATE @tTest SET Amount = Amount * 10 Одно замечание: не думайте о новой возможности OUTPUT как о замене аудиторской журнализации с помощью триггеров базы данных. Думайте об этом как об удобном средстве получить мгновенную обратную связь без необходимости обращаться к дополнительным операторам и/или лишним обращениям к серверу. Триггеры базы данных предлагают лучшие средства реализации функциональных возможностей аудита. 3 - TOP NSQL Server 2000 трактует N в операторах SELECT TOP N как литерал. Разработчики, которым требовалось выполнить оператор SELECT TOP N, чтобы вернуть первые N строк, вынуждены были использовать динамический SQL (или использовать оператор ROWCOUNT), если величина N являлась переменной. Примером может служить приложение, позволяющее конечному пользователю задавать значение N в процессе его выполнения (например, показать 10-ку лучших по продажам или 5-ку по доходам и т.д.). Теперь SQL Server 2005 трактует N как числовую переменную, позволяя разработчикам указывать ее в круглых скобках. Так, если мы хотим вернуть 5 (10 и т.д) первых заказов при сортировке по убыванию для величины заказа (Order Amount), то можем поступить следующим образом: use northwind Вы можете задать TOP (N) PERCENT, а также использовать необязательное предложение WITH TIES, чтобы вернуть дополнительные строки с тем же самым значением в выражении ORDER BY. TOP (N) может даже являться результатом вычисления некоторой функции или выражением, которое возвращает целое число. Например, следующий запрос вернет первые 3 строки из таблицы Orders при сортировке по столбцу Freight в порядке убывания, поскольку в таблице Shippers имеется только три строки: SELECT TOP( SELECT COUNT(*) FROM SHIPPERS) * Наконец, Вы можете применить функциональность TOP (N) к операторам INSERT и UPDATE. Это особенно полезно при выполнении специальной обработки промежуточных результирующих наборов. Следующий код создает табличную переменную с тремя строками, и применяет оператор UPDATE для обновления первых двух строк. Затем код создает вторую табличную переменную и вставляет туда первые две строки из первой табличной переменной. DECLARE @nTop int DECLARE @tTest1 TABLE ( Amount decimal(10,2)) UPDATE TOP(@nTop) @tTest1 SET Amount = Amount * 10 DECLARE @tTest2 TABLE ( Amount decimal(10,2)) SELECT * FROM @ttest2 Kevin S. Goff, Microsoft MVP (оригинал: Take Five with SQL Server 2005: New T-SQL Capabilities in SQL Server 2005 - Part 1 of 3) |