Sql циклы: Урок 18. Хранимые процедуры. Циклы.

Содержание

WHILE (Transact-SQL) — SQL Server

  • Чтение занимает 2 мин

В этой статье

Применимо к:Applies to: SQL ServerSQL Server (все поддерживаемые версии) SQL ServerSQL Server (all supported versions) База данных SQL AzureAzure SQL DatabaseБаза данных SQL AzureAzure SQL Database Управляемый экземпляр SQL AzureAzure SQL Managed InstanceУправляемый экземпляр SQL AzureAzure SQL Managed Instance Azure Synapse AnalyticsAzure Synapse AnalyticsAzure Synapse AnalyticsAzure Synapse Analytics Параллельное хранилище данныхParallel Data WarehouseПараллельное хранилище данныхParallel Data WarehouseПрименимо к:Applies to: SQL ServerSQL Server (все поддерживаемые версии) SQL ServerSQL Server (all supported versions) База данных SQL AzureAzure SQL DatabaseБаза данных SQL AzureAzure SQL Database Управляемый экземпляр SQL AzureAzure SQL Managed InstanceУправляемый экземпляр SQL AzureAzure SQL Managed Instance Azure Synapse AnalyticsAzure Synapse AnalyticsAzure Synapse AnalyticsAzure Synapse Analytics Параллельное хранилище данныхParallel Data WarehouseПараллельное хранилище данныхParallel Data Warehouse

Ставит условие повторного выполнения SQL-инструкции или блока инструкций. Sets a condition for the repeated execution of an SQL statement or statement block. Эти инструкции вызываются в цикле, пока указанное условие истинно.The statements are executed repeatedly as long as the specified condition is true. Вызовами инструкций в цикле WHILE можно контролировать из цикла с помощью ключевых слов BREAK и CONTINUE.The execution of statements in the WHILE loop can be controlled from inside the loop with the BREAK and CONTINUE keywords.

Синтаксические обозначения в Transact-SQLTransact-SQL Syntax Conventions

СинтаксисSyntax

-- Syntax for SQL Server and Azure SQL Database  
  
WHILE Boolean_expression   
     { sql_statement | statement_block | BREAK | CONTINUE }  
  
-- Syntax for Azure Azure Synapse Analytics and Parallel Data Warehouse  
  
WHILE Boolean_expression   
     { sql_statement | statement_block | BREAK }  
  

АргументыArguments

Boolean_expressionBoolean_expression
Выражение, возвращающее значение TRUE или FALSE. Is an expression that returns TRUE or FALSE. Если логическое выражение содержит инструкцию SELECT, инструкция SELECT должна быть заключена в скобки.If the Boolean expression contains a SELECT statement, the SELECT statement must be enclosed in parentheses.

{sql_statement | statement_block}{sql_statement | statement_block}
Любая инструкция или группа инструкций Transact-SQLTransact-SQL, определенная в виде блока инструкций.Is any Transact-SQLTransact-SQL statement or statement grouping as defined with a statement block. Для определения блока инструкций используйте ключевые слова потока управления BEGIN и END.To define a statement block, use the control-of-flow keywords BEGIN and END.

BREAKBREAK
Приводит к выходу из ближайшего цикла WHILE.Causes an exit from the innermost WHILE loop. Вызываются инструкции, следующие за ключевым словом END, обозначающим конец цикла.Any statements that appear after the END keyword, marking the end of the loop, are executed.

CONTINUECONTINUE
Выполняет цикл WHILE для перезагрузки, не учитывая все инструкции, следующие после ключевого слова CONTINUE.Causes the WHILE loop to restart, ignoring any statements after the CONTINUE keyword.

КомментарииRemarks

Если вложенными являются два цикла WHILE или более, внутренний оператор BREAK существует до следующего внешнего цикла.If two or more WHILE loops are nested, the inner BREAK exits to the next outermost loop. Все инструкции после окончания внутреннего цикла выполняются в первую очередь, а затем перезапускается следующий внешний цикл.All the statements after the end of the inner loop run first, and then the next outermost loop restarts.

ПримерыExamples

A.A. Использование ключевых слов BREAK и CONTINUE внутри вложенных конструкций IF…ELSE и WHILEUsing BREAK and CONTINUE with nested IF…ELSE and WHILE

В следующем примере в случае, если средняя цена продуктов из списка меньше чем $300, цикл WHILE удваивает цены, а затем выбирает максимальную. In the following example, if the average list price of a product is less than $300, the WHILE loop doubles the prices and then selects the maximum price. В том случае, если максимальная цена меньше или равна $500, цикл WHILE повторяется и снова удваивает цены.If the maximum price is less than or equal to $500, the WHILE loop restarts and doubles the prices again. Этот цикл продолжает удваивать цены до тех пор, пока максимальная цена не будет больше чем

$500, затем выполнение цикла WHILE прекращается, о чем выводится соответствующее сообщение.This loop continues doubling the prices until the maximum price is greater than $500, and then exits the WHILE loop and prints a message.

USE AdventureWorks2012;  
GO  
WHILE (SELECT AVG(ListPrice) FROM Production.Product) < $300  
BEGIN  
   UPDATE Production.Product  
      SET ListPrice = ListPrice * 2  
   SELECT MAX(ListPrice) FROM Production. Product  
   IF (SELECT MAX(ListPrice) FROM Production.Product) > $500  
      BREAK  
   ELSE  
      CONTINUE  
END  
PRINT 'Too much for the market to bear';  

Б.B. Применение инструкции WHILE в курсореUsing WHILE in a cursor

В следующем примере используется переменная @@FETCH_STATUS для управления действиями курсора в цикле WHILE.The following example uses @@FETCH_STATUS to control cursor activities in a WHILE loop.

DECLARE @EmployeeID as NVARCHAR(256)
DECLARE @Title as NVARCHAR(50)

DECLARE Employee_Cursor CURSOR FOR  
SELECT LoginID, JobTitle   
FROM AdventureWorks2012.HumanResources.Employee  
WHERE JobTitle = 'Marketing Specialist';  
OPEN Employee_Cursor;  
FETCH NEXT FROM Employee_Cursor INTO @EmployeeID, @Title;  
WHILE @@FETCH_STATUS = 0  
   BEGIN  
      Print '   ' + @EmployeeID + '      '+  @Title 
      FETCH NEXT FROM Employee_Cursor INTO @EmployeeID, @Title;  
   END;  
CLOSE Employee_Cursor;  
DEALLOCATE Employee_Cursor;  
GO 

Примеры: Azure Synapse AnalyticsAzure Synapse Analytics и Параллельное хранилище данныхParallel Data WarehouseExamples: Azure Synapse AnalyticsAzure Synapse Analytics and Параллельное хранилище данныхParallel Data Warehouse

В.

Простой цикл WhileC: Simple While Loop

В следующем примере в случае, если средняя цена продуктов из списка меньше чем $300, цикл WHILE удваивает цены, а затем выбирает максимальную.In the following example, if the average list price of a product is less than $300, the WHILE loop doubles the prices and then selects the maximum price. В том случае, если максимальная цена меньше или равна $500, цикл WHILE повторяется и снова удваивает цены.If the maximum price is less than or equal to $500, the WHILE loop restarts and doubles the prices again. Этот цикл продолжает удваивать цены до тех пор, пока максимальная цена не будет больше, чем

$500, после чего выполнение цикла WHILE прекращается.This loop continues doubling the prices until the maximum price is greater than $500, and then exits the WHILE loop.

-- Uses AdventureWorks  
  
WHILE ( SELECT AVG(ListPrice) FROM dbo. DimProduct) < $300  
BEGIN  
    UPDATE dbo.DimProduct  
        SET ListPrice = ListPrice * 2;  
    SELECT MAX ( ListPrice) FROM dbo.DimProduct  
    IF ( SELECT MAX (ListPrice) FROM dbo.DimProduct) > $500  
        BREAK;  
END  

См. такжеSee Also

ALTER TRIGGER (Transact-SQL) ALTER TRIGGER (Transact-SQL)
Язык управления потоком (Transact-SQL) Control-of-Flow Language (Transact-SQL)

CREATE TRIGGER (Transact-SQL) CREATE TRIGGER (Transact-SQL)
Курсоры (Transact-SQL) Cursors (Transact-SQL)
SELECT (Transact-SQL)SELECT (Transact-SQL)

Циклы в PL / SQL

Введение в циклы в PL / SQL

Процедурный язык / язык структурированных запросов или PL / SQL — это процедурное расширение Oracle Corporation для СУБД Oracle. PL / SQL расширил SQL, добавив конструкции, используемые на процедурных языках, для обеспечения более сложного программирования, чем SQL. Примерами этих структур являются IF… THEN… ELSE, базовые циклы, циклы FOR и циклы WHILE.

Объясните различные типы циклов в PL / SQL

Эта статья объяснит вам итеративную структуру управления средствами циклов PL / SQL; это позволит вам запускать один и тот же код несколько раз. PL / SQL предоставляет три разных типа циклов:

  • Простой или бесконечный цикл
  • Цикл FOR
  • WHILE петля

Здесь каждый цикл предназначен для определенной цели, правил использования и руководящих принципов для высококачественного создания.

Примеры разных циклов

Рассмотрим следующие три процедуры, чтобы по-разному понимать различные циклы и их способность решать проблемы.

1. Простая петля

Этот цикл так же прост, как и его имя. Он начинается с ключевого слова LOOP и заканчивается оператором «END LOOP».

Синтаксис

LOOP
The sequence of statements;
END LOOP;

Здесь, согласно приведенному выше синтаксическому ключевому слову, «LOOP» отмечает начало цикла, а «END LOOP» — конец цикла.

Последовательность операторной части может содержать любой оператор для исполнения.

Пример простого цикла

Давайте напишем программу для вывода таблицы умножения на 18.

Здесь, в вышеприведенном цикле, у нас нет оператора «EXIT»; означает, что выполнение вывода будет продолжаться до бесконечности, пока мы не закроем эту программу вручную.

См. Ниже программу с оператором Exit:

Объяснение вышеуказанной программы

В разделе объявлений мы объявили две переменные; переменная v_counter будет служить счетчиком, а v_result будет содержать результат умножения.

Вниз по разделу выполнения у нас есть простой цикл, здесь у нас есть три утверждения.

  • Первое утверждение будет работать как наше обновление; это обновит наш счетчик и увеличит его на 1.
  • Второе утверждение является арифметическим выражением, которое выполнит умножение нашей таблицы и сохранит результат в переменной v_result.
  • Третий оператор является оператором вывода, который будет печатать результат умножения в отформатированном виде.

Использование заявления о выходе

В соответствии с оператором выхода, если v_counter> = 10, цикл с выходом означает, что цикл будет выполнен 10 раз.

Выход:

2. Цикл FOR

Цикл FOR позволяет выполнять блок операторов многократно в течение фиксированного числа раз.

Синтаксис

FOR loop_counter IN (REVERSE) lower_limit .. upper_limit LOOP
Statement1;
Statement2;
....Statement3;
END LOOP;

  • Первая строка синтаксиса — это оператор цикла, где ключевые слова FOR отмечают начало цикла, за которым следует счетчик цикла, который является неявной целочисленной переменной индекса.
  • Это означает, что вам не нужно определять эту переменную в разделе объявления, а также она будет увеличиваться на 1 неявно на каждой итерации вашего цикла, в отличие от других циклов, где мы должны определить счетчик цикла.
  • Ключевое слово IN обязательно должно быть в программе FOR Loop.
  • Ключевое слово REVERSE не является обязательным, но всегда используется вместе с Keyword IN.
  • Если используется ключевое слово REVERSE, цикл будет повторяться в обратном порядке.
  • lower_limit и upper_limit — это два целых числа. Эти две переменные определяют количество итераций цикла.
  • Две точки между этими двумя переменными служат оператором диапазона.
  • Тогда у нас есть тело цикла, которое может быть оператором или группой операторов.
  • В конце концов, у нас есть фраза END LOOP, которая обозначает окончание цикла.

Пример № 1

Здесь, как в приведенной выше программе, у нас есть цикл FOR, который будет печатать значение переменной v_counter от 11 до 20.

Выход:

Пример №2: Теперь давайте напечатаем то же самое в обратном порядке, используя цикл FOR.

Просто добавьте ключевое слово REVERSE после IN и до 11, это выполнит то же самое o / p, но в обратном порядке.

3. ЦИКЛ

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

Синтаксис

WHILE condition LOOP
Statement 1;
Statement 2;
...
Statement N;
END LOOP;

  • В отличие от другого синтаксического цикла WHILE, синтаксис очень прост для понимания. Здесь, согласно приведенному выше синтаксису, «WHILE» отмечает начало цикла вместе с условием, а «END LOOP» определяет конец цикла.
  • Операторы с 1 по N являются исполняемыми операторами, определенными в теле цикла. Кроме того, в конце мы упомянули END LOOP, который указывает на конец цикла while.
  • Чтобы выполнить операторы внутри тела цикла While, условие должно быть истинным.

Пример: вывести таблицу умножения 17 с использованием цикла while.

  • В этом примере у нас есть первая переменная «v_counter», которая будет служить в качестве счетчика, а вторая переменная «v_result», которая будет содержать результат умножения.
  • Здесь первый оператор является арифметическим выражением внутри цикла WHILE, который будет выполнять задачу умножения таблицы, и результат будет сохранен в v_result.
  • Вторым оператором является оператор print, который будет печатать результаты умножения. Третье утверждение — счетчик обновлений, который обновляет счетчик при каждой итерации.
  • Этот цикл while будет работать до тех пор, пока значение счетчика не будет больше или равно 10, а цикл WHILE получит значение счетчика после 10.

Выход:

Преимущества циклов в PL / SQL

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

Вывод — циклы в PL / SQL

SQL является единственным интерфейсом к реляционной базе данных, а PL / SQL является процедурным расширением для SQL. Важно понимать, как работает SQL, и правильно проектировать базы данных и бизнес-логику, чтобы получить правильный набор результатов. PL / SQL может использоваться внутри базы данных, и он имеет много мощных функций. Есть много улучшений в PL / SQL в Oracle Database 12.1. Используйте SQL всякий раз, когда это возможно, но если ваш запрос становится слишком сложным или требуются процедурные функции, лучше вместо этого использовать PL / SQL.

Рекомендуемые статьи

Это было руководство по циклам в PL / SQL. Здесь мы также обсудим преимущества и различные типы циклов с примерами. Вы также можете взглянуть на следующие статьи, чтобы узнать больше:

  1. Что такое PL / SQL?
  2. Тестирование масштабируемости
  3. Что такое язык программирования R?
  4. Что такое PHP?
  5. 36 ключевых слов в SQL с примерами
  6. Циклы в PowerShell | Типы | Преимущества

Выполняем код для каждой строки в выборке Transact SQL с использованием курсора

В SQL мы обычно оперируем выборками, но иногда надо выполнить некие действия для каждой строки в выборке. В обычных языках такая задача решается обходом последовательности в цикле, но в Transact SQL клссические циклы плохо работают, вместо них лучше использовать специальный обьект — курсор. Его синтаксис весьма непривычен для новичков, так что в данной статье я приведу простой и работающий пример, который можно взять за основу. Общая схема действий такова:

  1. Создаем курсор, описывая выборку, со строками которой мы будем работать. Запрос может быть сколь угодно сложным, с кучей JOIN
  2. Открываем курсор
  3. Объявляем переменные, в которые будем выбирать поля каждой строки, количество переменных должно совпадать с количеством столбцов в запросе
  4. Делаем выборку первой строки
  5. Прокручиваем в цикле наш код, завершая его выборкой следующей строки
  6. Закрываем курсор


-- обьявляем курсор 
declare some_cursor cursor
--- sql запрос любой сложности, формирующий набор данных для курсора
for 
  select SOME_INT_FIELD, SOME_VARCHAR_FIELD from SOME_TABLE
-- открываем курсор
open some_cursor
-- курсор создан, обьявляем переменные и обходим набор строк в цикле
declare  @counter int
declare  @int_var int, @string_var varchar(100) 
set @counter = 0
-- выборка первой  строки
fetch next from some_cursor INTO  @int_var, @string_var
-- цикл с логикой и выборкой всех последующих строк после первой
while @@FETCH_STATUS = 0
begin
--- логика внутри цикла
set @counter = @counter + 1
 if @counter >= 5 break  -- возможный код для проверки работы, прерываем после пятой итерации

-- отладочный select, на большом количестве строк выборка данных  в  sql server management studio может привести к ошибке переполнения памяти
SELECT @int_var, @string_var
INSERT INTO OTHER_TABLE (SOME_FIELD1, SOME_FIELD2) VALUES (@string_var, 'Мегастрока')
DELETE FROM OTHER_TABLE2 WHERE ID_FIELD =  @int_var
exec some_stored_procedure
-- выборка следующей строки
fetch next from some_cursor INTO  @int_var, @string_var
-- завершение логики внутри цикла
end
select @counter as final_count
-- закрываем курсор
close some_cursor
deallocate some_cursor

Курсоры (Transact-SQL)
FETCH (Transact-SQL)
@@FETCH_STATUS (Transact-SQL)

План запросов, простой взгляд MSSQL Server

План запросов, простой взгляд:

План выполнения запроса — последовательность операций, необходимых для получения результата SQL-запроса в реляционной СУБД.

План в целом разделяется на две стадии:

  • Выборка результатов;
  • Сортировка и группировка, выполнение агрегаций.

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

Выборка результатов выполняется следующими способами:

  • Вложенные циклы;
  • Слияние.

Вложенные циклы — это вложенные итеративные процессы поиска данных в каждой из соединяемых таблиц.

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

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

Циклы могут вкладываться произвольное число раз. В этом случае внутренний цикл становится внешним для следующего цикла и т. д.

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

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

Слияние

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

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

Оптимизатор запросов (компонент СУБД) использует хранящуюся в базе данных вместе с таблицами и индексами статистическую информацию, на основе которой он оценивает альтернативные способы формирования результатов запроса. Например, команду ORDER BY в инструкции SELECT можно выполнить с использованием имеющегося в базе индекса, либо же путём физической сортировки строк. Оптимизатор старается выбрать самый эффективный план выполнения запроса.

Изучение планов выполнения запросов, созданных оптимизатором, позволяет решить, как ускорить выполнение запроса — изменить сам запрос или создать в базе данных дополнительный индекс.

Планы выполнения отображаются следующими способами.

  • Среда SQL Server Management Studio

    Отображает либо ориентировочный графический план выполнения (инструкции не выполнены), либо реальный графический план выполнения (при выполненных инструкциях), который можно просмотреть в Management Studio и сохранить.

  • Параметры инструкции SET Transact-SQL

    При использовании параметров инструкции SET Transact-SQL можно вывести ожидаемый или реальный план выполнения в формате XML или в текстовом формате.

  • Классы событий Приложение SQL Server Profiler

    Можно включить классы событий Приложение SQL Server Profiler в трассировки для получения ожидаемых или реальных планов выполнения в формате XML или в текстовом формате в результатах трассировки.

Ниже перечислены некоторые наиболее распространенные причины медленного выполнения запросов и обновлений.

  • Медленная передача данных в сети.

  • Недостаточно памяти на серверном компьютере или недостаточно памяти для SQL Server.

  • Не хватает полезной статистики.

  • Не хватает полезных индексов.

  • Не хватает полезных индексированных представлений.

  • Не хватает полезного расслоения данных.

  • Не хватает полезного секционирования.

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

    Можно использовать системный монитор Windows для контроля производительности компонентов SQL Server и компонентов, не относящихся к SQL Server.

  2. Если проблема производительности связана с запросами, то какой запрос или набор запросов вызывает проблему?

    С помощью Приложение SQL Server Profiler определите медленный запрос или запросы.

  3. Инструкции по анализу производительности медленного запроса.

    После определения медленно выполняющихся запросов можно далее проанализировать производительность запросов, создав параметр showplan, который может быть текстом, XML или графическим представлением плана выполнения запросов, формируемого оптимизатором запросов. Параметр showplan можно создать при помощи параметров SET языка Transact-SQL, среды Среда SQL Server Management Studio или приложения Приложение SQL Server Profiler.

  4. Был ли оптимизирован запрос с помощью статистики?

    Оптимизатор запросов использует статистику для создания планов запросов, которые повышают производительность запросов. Для большинства запросов оптимизатор уже создает достаточно статистики для формирования высококачественного плана запроса, но в некоторых случаях для достижения оптимальных результатов нужно создать дополнительную статистику или изменить структуру запроса.

     Предлагаются следующие рекомендации.

    • Использование параметров статистики на уровне базы данных. Например, следует убедиться, что включены параметры автоматического создания статистики (AUTO_CREATE_STATISTICS) и автоматического обновления статистики (AUTO_UPDATE_STATISTICS), действующие на уровне базы данных. Если они отключены, то планы запросов могут быть неоптимальными и производительность запросов может понизиться.

    • Определение условий создания статистики. В некоторых случаях можно усовершенствовать планы запросов, создав дополнительную статистику с помощью инструкции CREATE STATISTICS (Transact-SQL). Эта дополнительная статистика может фиксировать статистическую корреляцию, которую не учитывает оптимизатор запросов при создании статистики для индексов или отдельных столбцов.

    • Определение условий обновления статистики. В некоторых случаях можно улучшить план запроса и тем самым повысить производительность запроса, обновляя статистику чаще, чем она обновляется при включенном параметре AUTO_UPDATE_STATISTICS. Статистику можно обновлять инструкцией UPDATE STATISTICS или хранимой процедурой sp_updatestats.

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

  5. Доступны ли подходящие индексы? Улучшит ли производительность запроса добавление одного или более индексов?

  6. Имеются ли какие-нибудь данные или наиболее активные участки индексов? Возможно, будет полезно расслоение дисков. Расслоение дисков можно выполнить с использованием RAID (резервного массива независимых дисков) уровня 0, когда данные распределяются на несколько дисковых носителей.

  7. Поддерживается ли в оптимизаторе запросов самая лучшая возможность оптимизации сложного запроса?

  8. Если объем данных большой, необходимо ли секционирование данных? Управляемость данных является главным преимуществом секционирования, но секционирование может также повысить производительность запроса, если таблицы и индексы таблиц секционируются подобным образом.  

Анализ плана запроса ;

Актуальный план выполнения вы можете посмотреть в Management Studio, нажав “Ctrl+M”

 

1. Оценка  Estimated Subtree Cost – суммарная стоимость всех операций внутри дерева (или поддерева).

Если же стоимость больше 2.0, то, как правило, этот запрос нужно либо оптимизировать, либо хорошо представлять то, как подобные запросы могут отразиться на состоянии системы (особенно в моменты пиковой нагрузки).Показатель в условных единицах, который говорит , насколько
тяжело исполнять этот запрос(стоимость всего запроса целиком). Чем меньше, тем лучше.
Этот показатель всегда надо сравнивать. Например месяц назад.
Стоимость и скорость запроса- не одно и тоже. Например блокировки не сказываются на стоимости.

Clustered Index Scan – сканирование кластерного индекса часто служит тревожным сигналом (гораздо больше радует глаз Index Seek)

2. Смотрим состав и последовательность операций.

3.Раскладка в стоимости по операциям и выделяем те опрерации , которые имеют большую стоимость в % соотношении.

4. Смотрим подробности по дорогим операциям.

5. Толщина стрелок, которая  отражает количество строк. и говрит , где идёт обработка большого количества данных.

6. Подрообнее исследуем план — xml-представление и свойства плана(F4)(настройки,уровень оптимизации,причина отказа от дальнейшей оптимизации)

7.Представить логику запроса;

-JOIN — логическая  операция  на плане (может выполнить 3  способами физическими nested loops,hash match,merge join), хинты — INNER LOOP JOIN(самый  медленный),INNER HASH JOIN,INNER MERGE JOIN (самый  быстрый), если в плане перед MERGE будет сортировка- то не хватет индексов по этому  полю.

_WHERE

-GROUP BY

-HAVING

-оконные функции — partition by, order by

-distinct,select,top

Nested Loops Joins

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

select p.ProductID, p.Name, c.Name
from SalesLT.Product p join SalesLT.ProductCategory c
    on c.ProductCategoryID = p.ProductCategoryID
where c.Name = ''Bike Stands''

Приводит к такому плану выполнения:

Давайте посмотрим, что происходит по этому плану:

  1. Получаем набор записей для категорий по фильтру (в нашем случае одна категория). Поскольку категории индексированы по названию, происходит Index Seek, то есть быстрый поиск по индексу.
  2. Проходим все записи таблицы товаров. При этом происходит сканирование всей таблицы, потому что нет индекса по категории. В случае, когда мы редко добавляем новые товары и редко меняем категорию товара, зато часто фильтруем по категориям, скорее всего будет целесообразно проиндексировать товары по категории (или даже сделать кластерный индекс, начинающийся с категории). Однако, учитывая небольшую стоимость запроса на данном этапе (я не стал ее показывать на рисунке, но у меня она была меньше 0.1), такую оптимизацию можно оставить “на потом”.
  3. Во время прохождения таблицы товаров проверяем, совпадает ли идентификатор категории с результатами шага (1). И, если совпадает, добавляем к результатам запроса. Это и отображено узлом “Nested Loops”.

Прежде чем перейти к обсуждению других вариантов, хочу рассказать историю, которая может быть вам интересна. Несколько лет назад довелось общаться с сотрудником Oracle (плотно занимающимся оптимизацией этой СУБД в американском офисе). Из разговора я, в частности, узнал, что (по крайней мере на тот момент) в Microsoft SQL Server именно Nested Loops были реализованы лучше, чем в других топовых СУБД. А уж подобным заявлениям прямого конкурента обычно всегда имеет смысл верить. Другой вопрос, что про остальные типы соединения я умолчу, чтобы никого не обидеть, да и достоверной информации на сегодняшний момент у меня нет.

Hash joins

Данный вариант более сложен и используется для работы с несортированными данными средних и больших объемов.

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

Пример запроса, который приводит к hash join такой:

select p.ProductID, p.Name, c.Name
from SalesLT.Product p join SalesLT.ProductCategory c
    on c.ProductCategoryID = p.ProductCategoryID
where c.Name like ''%Bike%''

А план выполнения выглядит следующим образом:

Давайте посмотрим, что происходит:

  1. Сначала ищем категории по шаблону, который начинается с “%”, что автоматически не дает использовать индекс, однако, мы хотя бы не сканируем всю таблицу, а считываем только индекс (потому что фильтруем по названию, а значение кластерного ключа содержит любой не-кластерный индекс). Все значения заносим в хэш-таблицу (в данном случае, практически наверняка в оперативной памяти).
  2. Затем сканируем таблицу товаров.
  3. При этом получаем соответствующую категорию из хэш-таблицы.

Хотя операции получаются более сложными да и количество данных больше, чем в примере с Nested Loops, стоимость запроса все равно невысока (у меня она была чуть больше 0.1). Это объясняется небольшим количеством исходных данных – тысяча строк для промышленной СУБД – это смешно (если, конечно, железо нормальное).

Merge joins

Этот вариант применяется, когда данных слишком много для применения Nested Loops, и элементы обоих множеств отсортированы по столбцу соединения. Когда сортировка отсутствует, иногда бывает целесообразно предварительно отсортировать множество и свести ситуацию к предыдущей. В последней же ситуации, оптимизатору приходится выбирать между Merge и Hash joins (метод hash joins часто приводит к меньшей стоимости запроса, потому что сортировка довольно затратная операция).

Данный метод основан на том, что в случае сортировки обоих множеств по столбцу соединения, для “inner join”, например, мы просто можем параллельно сканировать оба множества и отбрасывать “кусок”, значение в котором меньше, чем значение из другого множества (и не встретилось в другом множестве ранее).

Пример использования Merje joins можно получить, убрав фильтр из предыдущего запроса:

select p.ProductID, p.Name, c.Name
from SalesLT.Product p join SalesLT.ProductCategory c 
    on c.ProductCategoryID = p.ProductCategoryID

При этом получаем такой план выполнения:

Последовательность действий здесь следующая:

  1. Сканируем все товары и получаем список товаров, отсортированный по категории (операции Scan и Sort).
  2. Сканируем таблицу категорий по кластерному индексу (здесь дополнительная сортировка не нужна, потому что строки уже хранятся в нужном порядке).
  3. Соединяем множества по алгоритму, описанному до примера.

8. Представить логику WHERE

Можно ли ускорить этот фильтр индексом?

— Можно ли ствинуть фильтр перед JOIN

9. Оконные функции

Инлекс по PARTITION BY и затем по ORDER BY

-Включить в инлекс (INCLUDE) поля для  всех последующих операций  в запросе

Например ранжирование с секционированием Select  Name,listPrice,Size, Rank() OVER (PARTITION BY Size ORDER BY ListPrice) from Product

требуется  создание  индекса по полю Size и ListPrice , затем include индекс   Name  — CREATE INDEX ind2 on Product (Size,ListPrice) INCLUDE (Name)

10.Распаралеливанием

11 Обращать внимание  на warning в плане запроса

SQL лайфхаки в BigQuery, о которых вы точно не знали | by Aleksandr Osiyuk

Список полезных возможностей в BigQuery, неочевидных даже для опытных пользователей:

(добавляйте в закладки — список будет обновляться)

В BigQuery можно запрашивать содержание таблиц по состоянию на конкретное время. Если перезаписали или удалили часть нужных данных — не беда, все можно восстановить. Пример запроса количества записей в таблице сутками ранее:

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

Оператор SAFE_DIVIDE позволяет игнорировать деление на 0. К примеру SAFE_DIVIDE(X,Y)=x/y и если y=0, то оператор вернет null вместо ошибки. Эту же функцию выполняет префикс SAFE вместе с другими операторами, а именно SAFE.function_name() возвращает null вместо ошибки.

Есть оператор COUNTIF. Пример использования: COUNTIF(event='purchase') — подсчет количества определенных действий пользователя. Часто есть соблазн по аналогии использовать SUMIF, но такого оператора нет. Его функциональность можно заменить существующими операторами, например: SUM(IF(value > 2, value, 0).

User Defined Functions — кастомные функции JavaScript, добавляющие функциональности SQL. С недавних пор можно не декларировать функции при выполнении запроса, так как их можно сделать постоянно доступными или даже публичными. Ниже SQL-запрос с общедоступной функцией перевода текста в числа fhoffa.x.parse_number('text') :

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

BigQuery Scripting позволяет программировать на языке SQL прямо в BigQuery. Можно декларировать переменные, использовать циклы, условные операторы, программные модули, функции и процедуры. Есть отличная статья с полезными аналитику примерами.

Analytical functions или Оконные функции значительно расширяют возможности SQL для аналитики. Они позволяют группировать сырые данные в сессии, помогают строить продуктовые воронки, формировать цепочки мультиканальных последовательностей и пр.

Использование алгоритма HyperLogLog++ позволяет заменить некоторые стандартные функции агрегирования на аналогичные более быстрые, работающие с определенной погрешностью. Еще алгоритм позволяет довольно точно агрегировать уже агрегированные данные. Интерфейс Google Analytics использует эти алгоритмы для подсчета уникального количества пользователей, из-за чего данные в Google Analytics и BigQuery могут отличаться. Пример использования:

В BigQuery есть полезные таблицы в публичном доступе, которые могут помочь дополнить ваши данные. Например, есть множество способов подтянуть geo-данные по IP, но мало кто знает, что это можно сделать прямо в BigQuery, пример запроса:

Еще один простой пример добавления флага страны emoji к их названию для более удобной визуализации:

Оператор ORDINAL возвращает элемент массива по его позиции, начиная с единицы. К примеру, рассмотрим SQL-запрос для визуализации распределения метрик по дням недели и времени суток:

Его можно переписать с использованием ORDINAL таким образом:

Иногда данные записываются в таблицу в BigQuery в json-образной структуре. Их можно распарсить и транспонировать из строчек в колонки. Пример преобразования данных:

Ниже SQL-запрос, помогающий это сделать:

Оператор RANGE_BUCKET сканирует указанный отсортированный массив и возвращает позицию минимального элемента массива, который больше указанного значения. Если значение больше или равно последнему элементу в массиве, возвращает длину массива. Это может быть полезно, если вам нужно сгруппировать данные для построения гистограмм, бизнес-правил и пр.

_____________

Если понравилась подборка, подписывайтесь на мой Телеграм-канал BigQuery Insights, где я делюсь интересными решениями аналитики в Google BigQuery.

_____________

Может быть интересно:

Строим продуктовую воронку при помощи SQL в BigQuery

Проектируем сессионную таблицу при помощи SQL в BigQuery

Автоматизация результатов A/B тестирования

Как стать Аналитиком: лучшие телеграм каналы специалистов

Неужели циклы в T-SQL так тупо сделаны

 
Kolan ©   (2006-11-14 22:18) [0]

Здравствуйте,
 Сижу, учусь, делаю лабораторную. Вот пример из нё:

ALTER PROCEDURE Accordance
AS
 DECLARE X CURSOR FOR
   SELECT COUNT(Number) AS AllEmp, Department
   FROM Employee
   GROUP BY Department;
OPEN X
DECLARE @Qty int, @Dept tinyint;
 FETCH NEXT FROM X INTO @Qty, @Dept
 UPDATE Department
   SET EmployeeCount = @Qty
   WHERE Number = @Dept;

WHILE @@FETCH_STATUS = 0
BEGIN
 FETCH NEXT FROM X INTO @Qty, @Dept
 UPDATE Department
   SET EmployeeCount = @Qty
   WHERE Number = @Dept;

END  
CLOSE X
DEALLOCATE X

Моя рука к этому не притрагивалась — только набрал. Работает. Но неужели нельзя организовать цикл так чтобы два раза не писать одно и тоже? Если можно, то как?


 
Johnmen ©   (2006-11-14 22:27) [1]

Для while цикла всегда, не зависимо от языка, нужны разгонные значения. Т.к. он с предусловием.
Поэтому так:

FETCH NEXT FROM X INTO @Qty, @Dept
WHILE @@FETCH_STATUS = 0
BEGIN
UPDATE Department
  SET EmployeeCount = @Qty
  WHERE Number = @Dept;
FETCH NEXT FROM X INTO @Qty, @Dept
END  


 
Anatoly Podgoretsky ©   (2006-11-14 22:28) [2]

> Kolan  (14.11.2006 22:18:00)  [0]

Ты форум перепутал, у нас тут по Дельфи.
Сходи лучше на sql.ru там есть специализированый форум по MS SQL
Только не надо говорить, мол я в Дельфи программирую, программируй в чем хочешь, но это дело сервервное.


 
Kolan ©   (2006-11-14 22:46) [3]

Ну вот.. а то препод сбил, блин с толку что тут так надо делать(2 раза) и по другому неполучится.
Благодарю..


 
Kolan ©   (2006-11-14 22:46) [4]

> Только не надо говорить, мол я в Дельфи программирую, программируй
> в чем хочешь, но это дело сервервное.

Дык я же там никого не знаю 🙂


 
Kolan ©   (2006-11-14 22:47) [5]

Вопросы по базам данных (вопросы по использованию и программированию БД )
Вороде не нарушил правил .

ЗЫ
 А программирую я на Delphi! 🙂


 
Anatoly Podgoretsky ©   (2006-11-14 22:51) [6]

> Kolan  (14.11.2006 22:46:04)  [4]

Не волнуйся, там люди культурные, не в пример нам.


 
Anatoly Podgoretsky ©   (2006-11-14 22:53) [7]

> Kolan  (14.11.2006 22:47:05)  [5]

Вроде нарушил, это конференция по Дельфи и программирование в ней связаное с базами.
Твой вопрос ничего общего с Дельфи не имеет.
Обычно такие вопросы мы не закрываем, а говорим куда лучше сходить.
И ответ квалифицированый легче получить в специализированой конференции.


 
Kolan ©   (2006-11-14 23:00) [8]

> [7] Anatoly Podgoretsky ©   (14.11.06 22:53)


Ок. Учту 🙂


 
sniknik ©   (2006-11-14 23:06) [9]

Johnmen ©   (14.11.06 22:27) [1]
ктото тут трюк с GOTO показывал, не будем про то хорошо это или плохо, делает из while аналог repeat until (в одном месте выборку писать, при больших удобноЮ не надо туда сюда копировать)

GOTO Start
WHILE @@FETCH_STATUS = 0
BEGIN
UPDATE Department
 SET EmployeeCount = @Qty
 WHERE Number = @Dept;
Start:
FETCH NEXT FROM X INTO @Qty, @Dept
END


 
Johnmen ©   (2006-11-14 23:12) [10]


> sniknik ©   (14. 11.06 23:06) [9]

А-а-а… Там и метки ещё есть…:)))


 
Kolan ©   (2006-11-14 23:28) [11]

> sniknik ©   (14.11.06 23:06)

Понятно. Благодарю. 🙂


Python + Django: с нуля до коммерческих приложений

Материалы в курсе постоянно обновляются.

Планы выхода новых видео ниже в описании.

Курс по Python и Django для начинающих с подробным изучением фундаментальных основ и применением их на реальных проектах.

Почему стоит учить Python и Django?

Python — это стремительно развивающийся язык с большим прогрессирующим сообществом.

Python — это второй по популярности язык программирования по итогам 2020 года.

Django — самый популярный веб фреймворк языка Python.

Цель Джанго —возможность разрабатывать веб проекты быстро, сохраняя безопасность данных и стабильность веб приложений.

Что вас ждет на курсе?

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

— В первой части вас ждет знакомство с фундаментальными основами языка Python, синтаксис, типы данных, операторы сравнения и bool.

— Во второй части вы изучите коллекции — итерабельные типы дынных, условия, циклы, логические операторы.

— Третья часть посвящена функциям в Python, вы узнаете что такое функция, как работать с атрибутами функций, познакомитесь с функциями map filter lambda выражениями, узнаете функции — обертки, обработку ошибок и многое другое.

— В четвертой части курса разберем Объектно-ориентированное программирование в Python. Вы изучите понятие класс, объект, принципы наследования, полиморфизм и инкапсуляцию в Python.

— В пятой части курса вы познакомитесь с основами самого популярного пайтон веб фреймворка Django. Разберем как работает джанго, затронем тему виртуального окружения, систему контроля версий GIT, джанго формы, POST-GET запросы, разберем структуру проекта джанго и разработаем первую веб страницу.

— Шестая часть несет в себе больше практики. Создадим реальное веб приложение  с помощью полученных знаний.

Коммерческий веб сайт с административной панелью, CRM системой, управлением контентом и Telegtam ботом для отправки оповещений.

— Седьмая часть это теория и практика развертывания своего проекта на сервере под управлением linux. Мы разберем типы серверов, поднимем сервер Nginx и Gunicorm для Django проекта, настроим SSL TLS и поднимем HTTPs соединение.

К большинству видео есть исходный код.

Домашние задания вынесены в отдельные задания и упражнения.

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

Для быстрой навигации в длинных лекциях есть тайм-коды тем.

ПЛАНЫ НА БЛИЖАЙШЕЕ ВРЕМЯ

— Новые домашние задания

— Язык баз данных SQL

— JSON в Python

— PyAutoGUI автоматизация рутинных задач

— Kivy фреймворк для создания мобильных приложений на языке Python

— Работа с CSV и Excel

— Второй проект Django

— Django Rest Framework

Введение в циклы SQL Server

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

Введение

В этой статье мы не будем использовать какие-либо модели данных. Хотя это может показаться странным (ну, мы работаем с базой данных, а данных нет !?), вы уловили суть.Поскольку это вводная статья о циклах SQL Server, мы рассмотрим основные концепции, которые вы можете комбинировать с данными для получения желаемого результата.

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

Почти все языки программирования реализуют их, и мы обычно встречаем эти 3 типа циклов:

  • WHILE — Пока условие цикла истинно, мы выполним код внутри этого цикла.
  • DO… WHILE — работает так же, как цикл WHILE, но условие цикла проверяется в конце цикла.Циклы WHILE и DO… WHILE очень похожи и могут легко имитировать друг друга. REPEAT… UNTIL (Паскаль) похож на цикл DO… WHILE, и цикл будет повторяться, пока мы не «достигнем» этого условия.
  • FOR — По определению, этот цикл должен использоваться для выполнения кода внутри цикла столько раз, сколько вы точно знаете, прежде чем этот цикл начнется. Это верно в большинстве случаев, и такой цикл (если он доступен) следует использовать таким образом (чтобы избежать сложного кода), но, тем не менее, вы можете изменить количество раз, которое он выполняется внутри цикла.

Для нас наиболее важными фактами являются:

  • SQL Server реализует цикл WHILE, позволяющий нам повторять определенный код, пока выполняется условие цикла.
  • Если по какой-либо причине нам нужны другие циклы, мы можем смоделировать их с помощью цикла WHILE.Мы покажем это позже в статье.
  • Циклы используются редко, и большую часть работы выполняют запросы. Тем не менее, иногда петли оказываются очень полезными и могут значительно облегчить нашу жизнь.
  • Не используйте петли ни для чего, что вам нравится. Они могут вызвать серьезные проблемы с производительностью, поэтому убедитесь, что вы знаете, что делаете.

IF… ELSE IF и PRINT

Прежде чем перейти к циклам, мы представим два оператора / команды SQL — IF (IF… ELSE) и PRINT.

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

Вы также можете добавить ELSE к оператору IF, и это приведет к следующему: если исходное условие не было истинным, код в части ELSE должен выполняться.

Если мы хотим проверить несколько условий, мы будем использовать, IF (1 st условие)… ELSE IF (2 nd условие)… ELSE IF (n-е условие)… ELSE.Мы сделаем это в нашем примере — просто чтобы показать, как это работает в SQL Server.

Но перед этим — команда ПЕЧАТЬ. PRINT просто печатает текст, помещенный после этой команды. Это внутри кавычек, но вы также можете объединять строки и использовать переменные.

DECLARE @ num1 INTEGER;

DECLARE @ num2 INTEGER;

НАБОР @ num1 = 20;

НАБОР @ num2 = 30;

IF (@ num1> @ num2)

PRINT ‘1-е число больше 2-го числа.

ELSE IF (@ num2> @ num1)

PRINT’ 2-е число больше 1-го числа. ‘

ELSE

PRINT ‘Числа равны.’;

С помощью приведенного выше набора команд мы:

  • Объявлены две переменные и присвоены им значения
  • Использовал оператор IF… ELSE IF, чтобы проверить, какая переменная больше

Хотя этот пример довольно прост (и очевидно, какое число больше), это хороший и простой способ продемонстрировать, как IF… ELSE IF и PRINT работают в SQL Server.

Теперь мы воспользуемся утверждениями из предыдущего примера, чтобы показать еще одну вещь.

1

2

3

4

5

6

7

8

9

10

11

12

13

140002

14

18

19

DECLARE @ num1 INTEGER;

DECLARE @ num2 INTEGER;

НАБОР @ num1 = 100;

НАБОР @ num2 = 30;

IF (@ num1> @ num2) BEGIN

PRINT ‘1-е число больше 2-го числа.

IF (@ num1> 75)

PRINT’ 1-е число больше 75. ‘

ELSE IF (@ num1> 50)

PRINT ‘1-е число больше 50.’

ELSE

PRINT ‘1-е число меньше или равно 50.’;

END

ELSE IF (@ num2> @ num1)

PRINT ‘2-е число больше 1-го.’

ELSE

PRINT ‘Числа равны.’;

Вы можете заметить, что мы поместили оператор IF внутри другого оператора IF.Это называется вложенным IF. Вы можете избежать этого, используя логические операторы в операторе IF 1 st , но в этом случае код будет более читабельным).

Цель нашего кода — сравнить два числа, а также распечатать, если первое больше 75, больше 50, меньше или равно 50 (и только в том случае, если первое число больше второго).

Как и в предыдущем примере, этот код не очень «умный», но используется для демонстрации концепции вложенного IF.

Циклы SQL Server

Теперь мы готовы перейти к циклам SQL Server. В нашем распоряжении 1 цикл, и это цикл WHILE. Вы можете подумать, почему у нас тоже нет других, и ответ заключается в том, что цикл WHILE выполнит свою работу. Сначала рассмотрим его синтаксис.

ПОКА {условие выполняется}
НАЧАТЬ
{… Сделать что-нибудь…}
КОНЕЦ;

Как вы могли легко заключить — пока выполняются условия цикла, мы выполним все операторы в блоке BEGIN… END.Поскольку мы говорим о циклах SQL Server, в нашем распоряжении есть все операторы SQL, и мы можем использовать их в цикле WHILE по своему усмотрению.

Давайте теперь посмотрим на первый пример цикла WHILE.

DECLARE @i INTEGER;

НАБОР @i = 1;

WHILE @i <= 10

BEGIN

PRINT CONCAT (‘Pass …’, @i);

НАБОР @i = @i + 1;

КОНЕЦ;

Мы объявили переменную @i и установили для нее значение 1.Если текущее значение переменной <= 10, мы введем тело цикла и выполним инструкции. Каждый раз, когда мы входим в тело, мы увеличиваем @i на 1. Таким образом, значение переменной @i в какой-то момент станет равным 10, и это предотвратит повторный запуск цикла.

Здесь важно упомянуть несколько вещей:

  • Переменная @i подсчитывает, сколько раз мы были в цикле, и иногда для такой переменной следует использовать слово «счетчик».Назвать счетчик @i — тоже хорошая практика.
  • Поскольку мы знаем, что этот цикл всегда должен выполняться ровно 10 раз — @i начинается с 1, увеличивается на 1, и мы будем повторять это, пока @i не станет 11 — это также моделирование цикла FOR с использованием цикла WHILE.
  • Всегда важно быть уверенным, что условие цикла не всегда выполняется. Если условие цикла всегда выполняется, цикл будет бесконечным, и в большинстве случаев мы этого не хотим (особенно в базе данных).
  • Совет : Бесконечные циклы редко используются в программировании. Один из таких случаев — это когда мы хотим дождаться сигнала (чего-то). Подождем, используя бесконечный цикл. Хотя это очень полезно в программировании, использование такого цикла в базах данных не было бы разумным шагом (мы повлияем на производительность, и весь смысл баз данных состоит в том, чтобы получать данные из них как можно быстрее).

Два ключевых слова — BREAK и CONTINUE — присутствуют в большинстве языков программирования. То же самое относится к циклам SQL Server. Идея такая:

  • Когда вы сталкиваетесь с ключевым словом BREAK в цикле, вы просто игнорируете все операторы до конца цикла (не выполняете ни одного) и выходите из цикла (не переходя к следующему шагу, даже если условие цикла выполняется)
  • CONTINUE действует аналогично BREAK — игнорирует все операторы до конца цикла, но затем продолжает цикл.

DECLARE @i INTEGER;

НАБОР @i = 1;

WHILE @i <= 10

BEGIN

PRINT CONCAT (‘Pass. .. ‘, @i);

ЕСЛИ @i = 9 ПЕРЕРЫВ;

НАБОР @i = @i + 1;

КОНЕЦ;

Вы можете заметить, что размещение BREAK в цикле привело к тому, что мы вышли из цикла на проходе 9 -го .

DECLARE @i INTEGER;

НАБОР @i = 1;

WHILE @i <= 10

BEGIN

PRINT CONCAT (‘Pass… ‘, @i);

ЕСЛИ @i = 9 ПРОДОЛЖИТЬ;

НАБОР @i = @i + 1;

КОНЕЦ;

Приведенный выше код приводит к бесконечному циклу. Причина этого в том, что когда @i становится 9, мы ПРОДОЛЖАЕМ цикл, а @i никогда не будет иметь значение 10. Поскольку это бесконечный цикл, он просто тратит ресурсы на сервере, ничего не делая. Мы можем прервать запрос, нажав кнопку «Стоп».

После нажатия кнопки остановки вы можете заметить, что цикл что-то сделал, напечатал числа от 1 до 8 и номер 9 столько раз, сколько это было до того, как мы отменили запрос.

Циклы и даты SQL Server

До сих пор мы рассмотрели основы и то, как работают циклы SQL Server, и как мы объединяем такие операторы, как IF и PRINT, с циклами. Теперь мы будем использовать циклы, чтобы сделать что-нибудь полезное. Мы хотим вывести все даты между двумя заданными датами.

DECLARE @date_start DATE;

DECLARE @date_end DATE;

DECLARE @loop_date DATE;

НАБОР @date_start = ‘2020/11/11’;

НАБОР @date_end = ‘2020/12/12’;

НАБОР @loop_date = @date_start;

WHILE @loop_date <= @date_end

BEGIN

PRINT @loop_date;

НАБОР @loop_date = DATEADD (DAY, 1, @loop_date);

КОНЕЦ;

Мы объявили две переменные, присвоили им значения даты.Единственная разница в том, что мы используем переменную @loop_date. Хотя мы могли бы сделать это без этой переменной, рекомендуется оставлять исходные значения (в нашем случае это @date_start и @date_end) неизменными. На каждом этапе цикла мы печатаем дату и увеличиваем «счетчик» на 1 день. Это полезно, но мы не можем использовать эти даты в запросе.

Для этого мы сохраним значения во временной таблице.

1

2

3

4

5

6

7

8

9

10

11

12

13

140002

14

18

19

20

21

22

УДАЛИТЬ ТАБЛИЦУ, ЕСЛИ СУЩЕСТВУЕТ #dates;

CREATE TABLE #dates (

report_date DATE

);

DECLARE @date_start DATE;

DECLARE @date_end DATE;

DECLARE @loop_date DATE;

НАБОР @date_start = ‘2020/11/11’;

НАБОР @date_end = ‘2020/12/12’;

НАБОР @loop_date = @date_start;

WHILE @loop_date <= @date_end

BEGIN

INSERT INTO #dates (report_date) VALUES (@loop_date);

НАБОР @loop_date = DATEADD (DAY, 1, @loop_date);

КОНЕЦ;

ВЫБРАТЬ * ИЗ # дат;

УДАЛИТЬ ТАБЛИЦУ, ЕСЛИ СУЩЕСТВУЕТ #dates;

Мы удалили временные таблицы #dates (если они существуют). После этого мы создали временную таблицу. Используемый код почти такой же, как в предыдущем примере. Разница в том, что вместо использования команды PRINT на каждом этапе цикла мы вставляем 1 строку во временную таблицу.

После цикла мы выбрали из временной таблицы и отбросили ее.

Заключение

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

Содержание

Эмиль — профессионал в области баз данных с более чем 10-летним опытом работы во всем, что касается баз данных. В течение многих лет он работал в сфере информационных технологий и финансов, а сейчас работает фрилансером.

Его прошлые и настоящие занятия варьируются от дизайна и программирования баз данных до обучения, консультирования и написания статей о базах данных. Также не забывайте, BI, создание алгоритмов, шахматы, филателия, 2 собаки, 2 кошки, 1 жена, 1 ребенок…

Вы можете найти его в LinkedIn

Посмотреть все сообщения Emil Drkusic

Последние сообщения Emil Drkusic (посмотреть все)

WHILE (Transact-SQL) — SQL Server

  • 2 минуты на чтение

В этой статье

Применимо к: SQL Server (все поддерживаемые версии) База данных SQL AzureAzure SQL Managed InstanceAzure Synapse Analytics Хранилище параллельных данных

Устанавливает условие для повторного выполнения оператора SQL или блока операторов.Операторы выполняются повторно, пока выполняется указанное условие. Выполнением операторов в цикле WHILE можно управлять изнутри цикла с помощью ключевых слов BREAK и CONTINUE.

Соглашения о синтаксисе Transact-SQL

Синтаксис

  - синтаксис для SQL Server и базы данных SQL Azure
  
WHILE Boolean_expression
     {sql_statement | statement_block | ПЕРЕРЫВ | ПРОДОЛЖИТЬ }
  
  
  - Синтаксис для Azure Azure Synapse Analytics и хранилища параллельных данных
  
WHILE Boolean_expression
     {sql_statement | statement_block | ПЕРЕМЕНА }
  
  

Аргументы

Логическое_выражение
Выражение, которое возвращает ИСТИНА, или , ЛОЖЬ, .Если логическое выражение содержит оператор SELECT, оператор SELECT должен быть заключен в круглые скобки.

{ sql_statement | statement_block }
Любая инструкция Transact-SQL или группа инструкций, определенная в блоке инструкций. Чтобы определить блок операторов, используйте ключевые слова управления потоком BEGIN и END.

BREAK
Вызывает выход из внутреннего цикла WHILE. Выполняются все операторы, которые появляются после ключевого слова END, обозначающего конец цикла.

CONTINUE
Вызывает перезапуск цикла WHILE, игнорируя любые операторы после ключевого слова CONTINUE.

Замечания

Если два или более цикла WHILE вложены, внутренний BREAK переходит к следующему самому внешнему циклу. Сначала выполняются все операторы после завершения внутреннего цикла, а затем перезапускается следующий самый внешний цикл.

Примеры

A. Использование BREAK и CONTINUE с вложенными IF … ELSE и WHILE

В следующем примере, если средняя прейскурантная цена продукта меньше 300 долларов , цикл WHILE удваивает цены, а затем выбирает максимальную цену.Если максимальная цена меньше или равна $ 500 , цикл WHILE перезапускается и снова удваивает цены. Этот цикл продолжает удваивать цены до тех пор, пока максимальная цена не превысит $ 500 , а затем выйдет из цикла WHILE и распечатает сообщение.

  ИСПОЛЬЗОВАТЬ AdventureWorks2012;
ИДТИ
ПОКА (ВЫБЕРИТЕ СРЕДНЕЕ (ListPrice) ИЗ Production.Product) <$ 300
НАЧИНАТЬ
   ОБНОВЛЕНИЕ Production.Product
      УСТАНОВИТЬ ListPrice = ListPrice * 2
   ВЫБЕРИТЕ МАКС (ListPrice) ИЗ Производство.Товар
   IF (SELECT MAX (ListPrice) FROM Production.Product)> 500 долларов США
      ПЕРЕМЕНА
   ЕЩЕ
      ПРОДОЛЖИТЬ
КОНЕЦ
ПЕЧАТЬ «Слишком много для рынка»;
  

B. Использование WHILE в курсоре

В следующем примере @@ FETCH_STATUS используется для управления действиями курсора в цикле WHILE .

  ОБЪЯВИТЬ @EmployeeID как NVARCHAR (256)
ОБЪЯВИТЕ @Title как NVARCHAR (50)

ОБЪЯВИТЬ КУРСОР Employee_Cursor ДЛЯ
ВЫБЕРИТЕ LoginID, JobTitle
ОТ AdventureWorks2012.HumanResources.Employee
ГДЕ JobTitle = «Специалист по маркетингу»;
ОТКРЫТЬ Employee_Cursor;
ВЫБРАТЬ ДАЛЕЕ ИЗ Employee_Cursor В @EmployeeID, @Title;
ПОКА @@ FETCH_STATUS = 0
   НАЧИНАТЬ
      Печать '' + @EmployeeID + '' + @Title
      ВЫБРАТЬ ДАЛЕЕ ИЗ Employee_Cursor В @EmployeeID, @Title;
   КОНЕЦ;
ЗАКРЫТЬ Employee_Cursor;
DEALLOCATE Employee_Cursor;
ИДТИ
  

Примеры: Azure Synapse Analytics и хранилище параллельных данных

C: простой цикл while

В следующем примере, если средняя прейскурантная цена продукта меньше 300 долларов , цикл WHILE удваивает цены, а затем выбирает максимальную цену. Если максимальная цена меньше или равна $ 500 , цикл WHILE перезапускается и снова удваивает цены. Этот цикл продолжает удваивать цены до тех пор, пока максимальная цена не превысит $ 500 , а затем выйдет из цикла WHILE .

  - использует AdventureWorks
  
ПОКА (ВЫБЕРИТЕ СРЕДНЕЕ (ListPrice) ИЗ dbo.DimProduct) <300 долларов США
НАЧИНАТЬ
    ОБНОВЛЕНИЕ dbo.DimProduct
        УСТАНОВИТЬ ListPrice = ListPrice * 2;
    ВЫБЕРИТЕ МАКС (ListPrice) ИЗ dbo.DimProduct
    IF (SELECT MAX (ListPrice) FROM dbo.DimProduct)> 500 долларов США
        ПЕРЕМЕНА;
КОНЕЦ
  

См. Также

ALTER TRIGGER (Transact-SQL)
Язык управления потоком (Transact-SQL)
CREATE TRIGGER (Transact-SQL)
Курсоры (Transact-SQL)
SELECT (Transact-SQL)

SQL Server WHILE Loops


SQL 2005+

В пятьдесят пятой части учебника по основам программирования SQL Server исследуется оператор WHILE в Transact-SQL (T-SQL). Эта мощная команда потока управления позволяет создавать циклические структуры в сценариях и хранимых процедурах.

Предыдущая: Условия IF SQL Server

Петли

Циклические структуры позволяют многократно выполнять одну команду или группу операторов. При использовании цикла T-SQL WHILE логическое условие проверяется каждый раз, когда код внутри цикла собирается запускаться. Если условие истинно, цикл выполняется. Если нет, управление передается оператору, следующему за циклом.

Циклы

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

Использование циклов WHILE

Синтаксис для создания цикла while следующий:

WHILE условие условия

Этот синтаксис такой же, как основной синтаксис оператора IF, который мы видели в предыдущей статье. Элемент условия определяет значение, переменную или выражение, которое оценивается как истинное или ложное. Если значение истинно, выполняется инструкция , часть команды . После завершения условие снова проверяется, и процесс продолжается до тех пор, пока условие не станет ложным. NB: элемент оператора может содержать несколько команд, если они появляются между командами BEGIN и END.

Мы можем продемонстрировать цикл, выполнив сценарий в SQL Server Management Studio или в вашей предпочтительной среде для запуска T-SQL.Следующий сценарий объявляет переменную и инициализирует ее значение нулем. Условие цикла указывает, что содержащиеся в нем операторы будут выполняться, пока значение переменной меньше или равно десяти. Внутри цикла значение переменной увеличивается и печатается. Конечным результатом является то, что выводятся целые числа от одного до десяти.

 ОБЪЯВИТЬ @Iteration INT
НАБОР @Iteration = 1
WHILE @Iteration <= 10
НАЧИНАТЬ
    ПЕЧАТЬ @ Итерация
    НАБОР @Iteration = @Iteration + 1
КОНЕЦ 

Явный выход из цикла

Иногда вам может понадобиться завершить цикл, даже если часть условия истинна. Этого можно достичь с помощью команды BREAK . Эта команда указывает, что управление должно перейти к команде, следующей сразу после текущего цикла.

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

 ОБЪЯВИТЬ @ToSquare INT
ОБЪЯВИТЬ @Square INT
НАБОР @ToSquare = 0
ПОКА @ToSquare <100
НАЧИНАТЬ
    УСТАНОВИТЬ @ToSquare = @ToSquare + 1
    НАБОР @Square = @ToSquare * @ToSquare
    ЕСЛИ @Square> 1000
        ПЕРЕМЕНА
    ПЕЧАТЬ @Square
КОНЕЦ 

Перезапуск цикла

Если вы хотите завершить одну итерацию цикла, вы можете использовать команду CONTINUE . Эта команда немедленно останавливает текущую итерацию и повторно проверяет состояние цикла.Если условие остается верным, цикл перезапускается. В следующем примере операторы CONTINUE и BREAK комбинируются так, что выводятся квадраты от ста до тысячи.

 ОБЪЯВИТЬ @ToSquare INT
ОБЪЯВИТЬ @Square INT
НАБОР @ToSquare = 0
ПОКА @ToSquare <100
НАЧИНАТЬ
    УСТАНОВИТЬ @ToSquare = @ToSquare + 1
    НАБОР @Square = @ToSquare * @ToSquare
    ЕСЛИ @Square <100
        ПРОДОЛЖИТЬ
    ЕСЛИ @Square> 1000
        ПЕРЕМЕНА
    ПЕЧАТЬ @Square
КОНЕЦ 

Вложенность

Циклы могут быть вложенными для создания более сложных циклических структур.При вложении циклов команды BREAK и CONTINUE влияют только на цикл, в котором они появляются. т.е. при выходе из внутреннего цикла управление переходит к следующему оператору после внутреннего цикла, который будет внутри внешнего цикла.

В следующем примере один цикл вложен в другой для вывода таблиц умножения для чисел от одного до десяти.

 ОБЪЯВИТЬ @ Val1 INT
ОБЪЯВИТЬ @ Val2 INT
НАБОР @ Val1 = 1
WHILE @ Val1 <= 10
НАЧИНАТЬ
    НАБОР @ Val2 = 1
    ПОКА @ Val2 <= 10
    НАЧИНАТЬ
        ПЕЧАТЬ CONVERT (VARCHAR, @ Val1) + '*' + CONVERT (VARCHAR, @ Val2)
         + '=' + CONVERT (VARCHAR, @ Val1 * @ Val2)
        НАБОР @ Val2 = @ Val2 + 1
    КОНЕЦ
    НАБОР @ Val1 = @ Val1 + 1
КОНЕЦ 

3 января 2010 г.

Вложенный цикл SQL While

Вложенный цикл «пока» SQL - это не что иное, как помещение цикла «пока» внутри другого цикла «пока».Вложенные циклы SQL Server While играют важную роль при работе с многоуровневыми данными. Потому что, когда мы хотим выбрать многоуровневые данные, мы должны использовать эти циклы SQL Nested While для извлечения многоуровневых данных. Но будьте осторожны при его использовании.

Вложенный синтаксис цикла while

SQL

Синтаксис вложенного цикла while в SQL Server:

 WHILE Expression
НАЧИНАТЬ
ПОКА @ Val2 <= 10
НАЧИНАТЬ
- Вторые операторы цикла while
Положение 1
Положение 2
...........
Заявление N
КОНЕЦ
- Заявления ниже находятся вне второго цикла while
- Первые операторы цикла while
Положение 1
Положение 2
...........
Заявление N
КОНЕЦ
- Это заявление выходит за рамки первого цикла while 

Если вы заметили вышеупомянутый синтаксис вложенного SQL цикла While, мы поместили цикл While внутри другого цикла While. Мы уже объясняли синтаксис цикла While в нашей предыдущей статье. Итак, обратитесь к статье SQL While Loop.

Шаг 1: Сначала он проверяет условие внутри первого цикла While.

  • Если результатом выражения является True, начальный и конечный блоки кода внутри цикла While будут выполнены. Затем он запустит второй цикл While. Перейти к Шаг 2
  • Если результат False, он выйдет из цикла While

Шаг 2: Он проверит условие во вложенном SQL-цикле While (второй цикл While).

  • Если результат True, будет выполнен код внутри второго цикла while begin… end. Это означает, что SQL Server выполняет операторы от Заявление 1 до N .
  • Если задано значение False, происходит выход из второго цикла While

Шаг 3: После выхода из второго цикла While выполняется проверка условия внутри первого цикла While (повторение шага 1 )

Пример вложенного SQL-цикла while

Эта программа SQL вложенного цикла while распечатает таблицу умножения от 1 и 2 до 10.

Для этого мы вложим один цикл While в другой цикл While, также называемый вложенным циклом SQL While.

 - Пример вложенного SQL-цикла while

ОБЪЯВИТЬ @ Val1 INT,
@ Val2 INT
НАБОР @ Val1 = 1

ПОКА @ Val1 <= 2
НАЧИНАТЬ
НАБОР @ Val2 = 1
ПОКА @ Val2 <= 10
НАЧИНАТЬ
PRINT CONVERT (VARCHAR, @ Val1) + '*' + CONVERT (VARCHAR, @ Val2) +
'=' + CONVERT (VARCHAR, @ Val1 * @ Val2)
НАБОР @ Val2 = @ Val2 + 1
КОНЕЦ
НАБОР @ Val1 = @ Val1 + 1
КОНЕЦ
 

В этом примере SQL Nested While Loop сначала мы создали две переменные с именами Val1, Val2, а затем инициализировали @ Val1 значением 1, используя следующий оператор

 ОБЪЯВИТЬ @ Val1 INT,
@ Val2 INT
НАБОР @ Val1 = 1 

В следующей строке мы использовали цикл while SQL Server с выражением.Если результат выражения истинен, тогда он перейдет во вложенный цикл while.

 ПОКА @ Val1 <= 2
НАЧИНАТЬ
НАБОР @ Val2 = 1
ПОКА @ Val2 <= 10
НАЧИНАТЬ
PRINT CONVERT (VARCHAR, @ Val1) + '*' + CONVERT (VARCHAR, @ Val2) +
'=' + CONVERT (VARCHAR, @ Val1 * @ Val2)
НАБОР @ Val2 = @ Val2 + 1
КОНЕЦ
НАБОР @ Val1 = @ Val1 + 1
КОНЕЦ 

Из приведенного выше снимка экрана вы можете заметить, что этот запрос вложенного SQL-цикла While Loop печатает таблицу умножения для 1 и 2.

Первый цикл while Первая итерация

В первом цикле While @ Val1 инициализируется значением 1, а затем проверяется, меньше ли @ Val1 2 или равно 2.Это условие (1 <= 2) истинно, поэтому оно войдет во второй цикл While

.

Вложенный цикл while или второй цикл while Первая итерация

Во втором цикле while @ Val2 инициализируется значением 1 и проверяется, меньше ли @ Val2 или равно 10. Это условие истинно. Таким образом, операторы внутри второго цикла while будут выполнять

 PRINT CONVERT (VARCHAR, @ Val1) + '*' + CONVERT (VARCHAR, @ Val2) +
      '=' + CONVERT (VARCHAR, @ Val1 * @ Val2) 

@ Val1 * @ Val2 ==> 1 * 1 = 1

Затем значение @ Val2 будет увеличено на 1 (SET @ Val2 = @ Val2 + 1).Пожалуйста, обратитесь к статье «Арифметические операторы», чтобы понять нотацию +.

Секунда цикла while Вторая итерация

Здесь @ Val2 увеличивается на единицу, поэтому @ Val2 = 2. Он проверит, меньше ли @ Val2 или равно 10. Это условие (2 <= 10) истинно, поэтому он выполнит инструкции внутри второго цикла While

.

@ Val1 * @ Val2 ==> 1 * 2 = 2
Затем значение @ Val2 будет увеличено на 1

Этот процесс будет повторяться до тех пор, пока @ Val2 не дойдет до 11 .Как только условие внутри SQL-вложенного цикла While не выполняется, компилятор выйдет из второго цикла While, и значение @ Val1 будет увеличено на 1 (SET @ Val1 = @ Val1 + 1).

Первый цикл while Вторая итерация

@ Val1 = 2. Проверьте, меньше ли @ Val1 или равно 2. Это условие (2 <= 2) истинно, поэтому оно входит во второй цикл While

Второй цикл while Первая итерация

Во втором цикле while @ Val2 инициализируется значением 1 и проверяет, меньше ли @ Val2 10.Это условие истинно, поэтому оно выполняет операторы внутри второго цикла While

.

@ Val1 * @ Val2 ==> 2 * 1 = 2

Затем значение @ Val2 будет увеличено на 1 (SET @ Val2 = @ Val2 + 1).

Секунда цикла while Вторая итерация

Здесь @ Val2 увеличивается на 1, поэтому @ Val2 = 2. Затем он проверяет, меньше ли @ Val2 или равно 10. Условие (2 <= 10) истинно, поэтому он начнет выполнение операторов. внутри второго цикла While
@ Val1 * @ Val2 ==> 2 * 2 = 4
Затем значение @ Val2 будет увеличено на 1 (SET @ Val2 = @ Val2 + 1)

Этот процесс будет повторяться, пока @ Val2 не достигнет 11 .Как только он достигает 11, условие (11 <= 10) во втором цикле While не выполняется. Таким образом, он выйдет из вложенного цикла While, и значение @ Val1 будет увеличено на 1 (SET @ Val1 = @ Val1 + 1).

Первый цикл while Третья итерация:

Здесь i = 3 означает, что выражение (@ Val1 <= 2) будет ложным. Итак, цикл while завершен. Помните, второй цикл проверять не нужно.

SQL СЕРВЕР | Условные операторы

SQL SERVER | Условные утверждения

Цикл while: В SQL SERVER цикл while можно использовать так же, как и любой другой язык программирования. Цикл while сначала проверяет условие, а затем выполняет в нем блок SQL-операторов, пока условие оценивается как истинное.

Синтаксис:

ПОКА условие
НАЧИНАТЬ
   {...заявления...}
КОНЕЦ;

 

Параметры:
1. Условие: Условие проверяется при каждом прохождении цикла. Если условие оценивается как ИСТИНА, тело цикла выполняется, в противном случае цикл завершается.
2.Операторы: Операторы, которые необходимо выполнять при каждом проходе цикла.

Пример:

Вывод:


Оператор Break: Оператор BREAK, как следует из названия, используется для прерывания потока управления. Его можно использовать в SQL так же, как и в любом другом языке программирования.

Пример: цикл while с оператором Break

Выход:

Примечание: В примере, когда значение переменной становится равным пяти, выполняется инструкция BREAK, и управление выходит из цикла.

Цикл Do-while: SQL-сервер не имеет функции цикла do-while, но путем внесения небольших изменений в цикл while можно добиться того же поведения.

Пример 1:

Выход:

Пример 2:

Выход:

Оператор CASE: В SQL Server оператор CASE имеет те же функции, что и оператор IF-THEN-ELSE.

Синтаксис:

CASE выражение
   КОГДА Con_1 THEN Output1
   КОГДА Con_2 THEN Output2
   КОГДА Con_3 THEN Output3
   КОГДА Con_4 THEN Output4
   ...
   КОГДА Con_n THEN Outputn
   ELSE выход
КОНЕЦ
 

Параметры:
1. Выражение: Значение, которое нужно сравнить со списком условий (необязательно).
2. Con_1, Con_2,… Con_n: Условия являются обязательными и оцениваются в порядке их перечисления. Когда условие истинно, функция CASE вернет результат и больше не будет оценивать условия.
3. Выход1, Выход2,… Выходn: Выход, который будет напечатан после того, как условие оценивается как истинное.

Пример:

Выход:

циклов обработки в SQL Server

Введение

Нет ничего сложнее повторения. Если делать плохо - это неуклюже, глупо. Когда все сделано хорошо, это как небольшое эхо, как волны, сама поэзия.

Лоуренс Коссе «Книжный магазин романов»

Содержание

Почему эта статья?

Любой, кто хоть раз участвует в форуме «Быстрые ответы» здесь, в Code Project (а также в аналогичном форуме на «другом» сайте), снова и снова увидит некоторые общие темы, возникающие в вопросах.

В рамках темы SQL особенно часто встречается одна тема, которая обычно формулируется как «Как я могу написать цикл в SQL, чтобы получить…», за которым следуют любые результаты, которых пытается достичь плакат.

Эта статья представляет собой попытку собрать воедино несколько возможных решений этого типа вопросов, основанную на принципе : петля вам не нужна!

Таким образом, я нацелен в первую очередь на «новичков» SQL, показывая, как многие функции SQL Server означают, что циклы необходимы очень редко. Однако есть некоторые приемы, которые могут оказаться полезными для опытных пользователей. Я использую отработанные примеры, чтобы объяснить каждую альтернативу.

Что такое петля?

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

В компьютерном программировании цикл - это последовательность инструкций, которая непрерывно повторяется, пока не будет достигнуто определенное условие. Обычно выполняется определенный процесс, например получение элемента данных и его изменение, а затем проверяется какое-то условие, например, достиг ли счетчик заданного числа.]

Еще кое-что, о чем я время от времени упоминаю в «Попался!» разделов, представляет собой «бесконечный цикл». Вот определение:

Бесконечный цикл - это цикл, в котором отсутствует действующая процедура выхода. В результате цикл повторяется непрерывно, пока операционная система не обнаружит его и не завершит программу с ошибкой или пока не произойдет какое-либо другое событие (например, автоматическое завершение программы по истечении определенного времени). ]

Типичный код VB6 / VBA мог выглядеть как

 Размеры как ADODB.Набор записей

‘. . . код, заполняющий набор записей

С rs
Если нет .BOF и не .EOF, то
.MoveLast
.MoveFirst

Делай, пока нет (rs.EOF)



rs.MoveNext
Петля
Конец, если
.Закрывать
Конец с 

Excel может способствовать аналогичному мышлению благодаря тому, что вычисления автоматически меняются для каждой строки, просто перетаскивая ячейку вниз…

Сценарий, который может укрепить это мышление, заключается в том, что требования к проекту определяют несколько действий, которые необходимо выполнить с одним набором данных - что-то вроде:

Представьте отчет на основе таблицы myTable, которая содержит следующую информацию

  1. Отобразить каждую строку с ее рангом на основе значения
  2. Подсчитать количество строк
  3. Определить, какая строка имеет наибольшее значение
  4. Вычислить общее и среднее всех значений для каждого продукта
  5. Захватить даты первая и последняя записи
  6. Если строка имеет определенный атрибут по сравнению с другой таблицей, обновите поле состояния

Некоторые из этих инструкций должны повторяться для каждой строки, поэтому мне нужен цикл, верно?

Пока я просматриваю эти строки, у меня будут переменные для сбора промежуточной суммы, счетчика, максимальной даты и минимальной даты… это хорошая идея, верно?

Затем мне нужно перебрать мою таблицу и обновлять эту другую таблицу по ходу. ]

Они предназначены для одновременного воздействия на группы из связанных объектов (данных). Реляционная база данных будет обрабатывать тот факт, что есть много строк, с которыми нужно выполнить действие. По сути, вы пишете что-то, что описывает то, что вы хотите, и позволяете системе управления реляционными базами данных (СУБД) позаботиться о том, как это сделает.

Например: представьте, что требования следующие: «Для любого продукта, если количество единиц на складе меньше 20 единиц и нет единиц в заказе, прекратите выпуск продукта, установив флаг« Прекращено »на 1».

Псевдокод для процедурного выполнения будет

.
 Старт в начале набора рекордов
Пока есть записи для обработки
    Если единиц на складе <20 И единиц в заказе = 0, то
        Установить Снято с производства = 1
    Конец, если
    Перейти к следующей записи
Конец Пока 

Но с логикой на основе наборов инструкция становится очень простой

 НАБОР ПРОДУКТОВ ОБНОВЛЕНИЯ Снято с производства = 1 ГДЕ UnitsInStock <20 И UnitsOnOrder = 0 
Попался!

Помните определение бесконечного цикла из введения? Один из эквивалентов бесконечного цикла в операторе обновления SQL - это забыть включить предложение WHERE. ]

Попался снова!

Если вы посмотрите на исходные данные в таблице «Продукты», то увидите, что некоторые продукты уже сняты с производства. Для многих СУБД наша текущая инструкция снова установит флаг «Прекращено» для этих записей. Мы без необходимости обновляем записи, которые нам не нужны. Это не сильно повлияет на нашу небольшую базу данных, но если бы тысячи записей уже были прекращены, мы могли бы тратить время и ресурсы. Итак, хотя это явно не упоминается в требованиях, которые нам дали, мы действительно должны добавить еще один фильтр…

 НАБОР ПРОДУКТОВ ОБНОВЛЕНИЯ Прекращено = 1 ГДЕ UnitsInStock <20 AND UnitsOnOrder = 0  AND Discontinued = 0  

Пересмотренный запрос повлияет на меньшее количество строк - наш новый набор содержит только те строки, которые действительно требуют обновления.

Альтернативы циклам - объединения и подзапросы

Для меня достаточно легко сказать: «Не используйте циклы в SQL», но было бы немного несправедливо не предлагать альтернативы с немного большей сложностью, чем я предлагал до сих пор. В следующем разделе будут приведены некоторые отработанные примеры, чтобы дать вам некоторые идеи о других методах, которые можно использовать вместо этого. Я также буду ссылаться на некоторые другие статьи, в основном здесь, в CodeProject, где авторы полностью объяснили тему лучше, чем у меня есть место здесь.

Данные фильтрации

Представьте себе требование: «Получить список всех заказов для клиентов из Лондона».

Когда я смотрю на таблицу заказов, я вижу столбец [ShipCity], и когда я запрашиваю его с помощью

 ВЫБЕРИТЕ ИД заказа, ИД клиента ИЗ заказов, ГДЕ ShipCity = 'London' 

Я получаю список из 33 заказов. Отлично, поэтому я передаю эту информацию пользователю, который возвращается с жалобой: «А как насчет заказов для моего клиента« Вокруг рога »?»

Возвращаясь к просмотру данных, я понимаю, что этот клиент «Around the Horn» находится в Лондоне, но его заказы отправлены в Колчестер.Мой первый запрос не соответствует требованиям.

Может возникнуть соблазн создать петлю по строкам:

 Создайте таблицу для хранения результатов
Создайте набор записей, содержащий только клиентов, у которых City = ’London’
Начать с начала набора записей
Пока есть записи для обработки
    Скопируйте все заказы для этого клиента в выходные результаты
    Перейти к следующей записи клиента
Конец пока
 

Но я знаю, что SQL установлен на основе, поэтому я делаю это вместо этого - получаю все заказы, где идентификатор клиента находится в наборе клиентов, где город = «Лондон» или:

 ВЫБРАТЬ * ИЗ заказов, ГДЕ ИД клиента (ВЫБРАТЬ ИД клиента ИЗ клиентов, ГДЕ Город = 'Лондон') 

Так лучше.] .

Подзапросы - отличный способ ограничить элементы в наборе при использовании с предложением WHERE, но они также могут использоваться в операторе SELECT. Это простой способ включения сводной информации.

Представьте, что требования на самом деле гласят: «Получите список всех заказов для клиентов из Лондона и покажите сравнение фрахта каждой строки со средним фрахтом для всех заказов».

Процедурный подход, вероятно, будет выглядеть так

 ОБЪЯВИТЬ @O TotalAvg NUMERIC (15,2) SET @O TotalAvg = (ВЫБРАТЬ СРЕДНЕЕ (Фрахт) ИЗ заказов)
SELECT *, Freight - @O GeneralAvg как AvgFreight FROM заказов
    ГДЕ CustomerID IN (ВЫБЕРИТЕ CustomerID из списка клиентов, ГДЕ City = 'London') 

Но с помощью другого подзапроса мы можем сделать все, что нам нужно, в одном операторе

 SELECT *, Freight - (SELECT AVG (Freight) FROM Orders) как AvgFreight
ИЗ заказов
ГДЕ CustomerID IN (ВЫБЕРИТЕ CustomerID из списка клиентов, ГДЕ City = 'London')
 

Теперь, когда моя таблица заказов растет и растет, мой администратор баз данных стал беспокоиться о производительности и просит меня еще раз взглянуть на мой запрос, чтобы увидеть, могу ли я его улучшить.

Вернемся к тому факту, что SQL основан на наборах. Я могу определить набор данных, с которыми я хочу работать, обратившись к более чем одной таблице с помощью объединений. ]]

Вот первый пример выше, на этот раз с использованием внутреннего соединения вместо подзапроса

 ВЫБРАТЬ O.*, Фрахт - (ВЫБЕРИТЕ СРЕДНЕЕ (Фрахт) ИЗ заказов) как Ср. Фрахт
ИЗ заказов O
ВНУТРЕННЕЕ ПРИСОЕДИНЕНИЕ КЛИЕНТОВ C ON O.CustomerID = C.CustomerID
ГДЕ Город = 'Лондон' 

Или даже

 SELECT O. *, Freight - (SELECT AVG (Freight) FROM Orders) как AvgFreight
ИЗ заказов O
ВНУТРЕННИЕ ПРИСОЕДИНЯЙТЕСЬ к клиентам C ON O.CustomerID = C.CustomerID AND City = 'London'
 

Оба этих запроса возвращают все данные, которые я ожидал, но гораздо более эффективно (в данном случае), чем использование предложения IN. А как насчет этого подзапроса в SELECT?

Ну, мы также можем использовать подзапросы в JOIN, например, этот

 ВЫБРАТЬ O.*, Грузовые перевозки - SQ.O TotalAvg как AvgFreight
ИЗ заказов O
ВНУТРЕННИЕ ПРИСОЕДИНЯЙТЕСЬ к клиентам C ON O. CustomerID = C.CustomerID AND City = 'London'
ВНУТРЕННЕЕ СОЕДИНЕНИЕ (ВЫБЕРИТЕ СРЕДНЕЕ (Фрахт) КАК ОбщееСредн. ИЗ заказов) SQ ON 1 = 1
 

Поскольку из нашей временной таблицы, созданной из SQ, возвращается только одно значение, я применил трюк с использованием предложения ON, которое всегда истинно ... ON 1 = 1 .

К сожалению, это не обязательно лучший способ с точки зрения производительности, но я действительно думаю, что он позволяет немного упорядочить предложение SELECT.

[Примечание. Я сказал, что JOIN работает лучше, чем подзапросы для подобных запросов, однако обсуждение производительности выходит за рамки этой статьи. Я включил некоторые рекомендуемые материалы для дальнейшего чтения в список литературы в конце статьи]

Фильтрация данных с НЕ ВХОДИТ и НЕ СУЩЕСТВУЕТ

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

Представьте, что нашим пользователям теперь нужно, чтобы я вел список телефонных номеров, по которым Заказчик заявил, что они не хотят, чтобы с ними связались по телефону , а не . Я решаю сохранить список этих клиентов в очень простой новой таблице…

 СОЗДАТЬ ТАБЛИЦУ DoNotTelephone
(
[CustomerID] [nchar] (5) НЕ NULL
) НА [ОСНОВНОЙ] 

И я заполняю эту таблицу идентификаторами клиентов, с которыми не следует связываться по телефону.

 ВСТАВИТЬ ЗНАЧЕНИЯ DoNotTelephone
('LAZYK'), ('LETSS'), ('NORTS') и т.д… 

Теперь пользователи спрашивают у меня список телефонных номеров для кампании по продажам, им нужен список всех клиентов из США. Они не упоминают об этом, но, конечно, мне не следует включать какие-либо числа в список DoNotTelephone.

Хорошо, теперь мы знаем, что нам определенно не нужно использовать процедурный цикл. Вы можете подумать, что присоединение, вероятно, хороший вариант, но как присоединиться к чему-то, чего там нет?

Для этого я могу использовать функцию LEFT OUTER Joins. Соединение INNER вернет только записи, которые находятся в обеих таблицах. Соединение LEFT OUTER будет возвращать записи для каждой строки в «левой» таблице, и, если строка не существует в «правой» таблице, SQL предоставит NULL значений для столбцов, взятых из этой таблицы. .]

Итак, если я отфильтрую результаты до , только получит записи со значениями NULL из таблицы DoNotTelephone, это будет эквивалентно заявлению «не в таблице DoNotTelephone»…

 ВЫБЕРИТЕ ContactName, ContactTitle, CompanyName, C.Phone
ОТ клиентов C
ЛЕВЫЙ ВНЕШНИЙ СОЕДИНЕНИЕ DoNotTelephone DNT НА C.CustomerID = DNT.CustomerID
ГДЕ Страна = 'США'
  И dnt.CustomerID НУЛЬ  

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

К счастью, предложение NOT IN кажется именно тем, что мне нужно:

 ВЫБРАТЬ * ИЗ КЛИЕНТОВ
ГДЕ CustomerID  НЕ В 
   (ВЫБЕРИТЕ CustomerID из DoNotTelephone)
И Страна = "США" 

Но я все еще запрашиваю данные, которые мне не нужны - любой из клиентов, которые не в США, но и в списке DoNotTelephone, все еще запрашиваются, несмотря на то, что они нам не нужны.

(вероятно) лучшее решение - использовать NOT EXISTS .Почему? Что ж, подзапрос не вернет никаких фактических данных - он возвращает только эквивалент True или False . В измененном запросе ниже я использовал SELECT 1 , а не SELECT Phone , чтобы подчеркнуть этот факт.

 ВЫБРАТЬ * ИЗ клиентов C
ГДЕ НЕ СУЩЕСТВУЕТ (ВЫБЕРИТЕ 1 ИЗ DoNotTelephone, ГДЕ CustomerID = C.CustomerID) И Country = 'USA' 

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

Обновление таблиц с помощью объединений

Очень распространенный сценарий включает обновление одной таблицы на основе содержимого другой.

Примите во внимание следующие требования: «Для любого продукта из категорий« Морепродукты »или« Мясо / птица »установите флаг« Прекращено »на 1».

Посмотрите на таблицу «Продукты», и вы заметите, что Категория для каждого продукта представлена ​​ CategoryID , который представляет собой число, а не текст.«Морепродукты» относятся к категории 8, а «Мясо / птица» - к категории 6.

Нам нужно определить, какие записи в наборе данных необходимо обновить. Я мог бы запросить таблицу категорий, чтобы найти строку для «Морепродукты», принять к сведению CategoryID и затем использовать этот номер для обновления таблицы «Продукты» ... но вам придется повторить этот процесс для «Мясо / птица» категория.

 - Попытка №1
ОБЪЯВЛЕНИЕ @id INT = (ВЫБЕРИТЕ ИДЕНТИФИКАТОР категории ИЗ категорий
    ГДЕ CategoryName = 'Морепродукты')
ОБНОВЛЕНИЕ НАБОР ПРОДУКТОВ Discontinued = 1 WHERE CategoryID = @id AND Discontinued = 0

SET @id = (ВЫБРАТЬ ИДЕНТИФИКАЦИЮ КАТЕГОРИИ ИЗ категорий
    ГДЕ CategoryName = 'Мясо / Птица')
ОБНОВЛЕНИЕ НАБОР ПРОДУКТОВ Discontinued = 1 WHERE CategoryID = @id AND Discontinued = 0 

Это просто ужасно! Я повторяю весь этот код.

Это опасная точка, когда наш процедурный мозг решает: «Хорошо, что цикл там будет хорошо работать». Возникает искушение перебрать Категории - псевдокод выглядит так:

 Создайте набор записей, содержащий только категории, соответствующие требованиям.
Начать с начала этого набора записей
Пока есть записи для обработки
Обновите таблицу продуктов для этого CategoryID
Перейти к следующей категории
Конец Пока 

Но сейчас мы начинаем понимать, что так делать не будем!

I мог бы использовать предложение IN - с жестко заданными значениями 6 и 8, которые я нашел вручную.

 - Попытка №2
ОБНОВЛЕНИЕ Набор продуктов прекращен = 1
ГДЕ CategoryID IN (6,8)
И прекращено = 0 

Но через пару месяцев я могу забыть, откуда взялись эти «магические числа», или, что еще хуже, коллега должен забрать мою работу и просто не знает, откуда эти числа вообще взялись!

Использование безымянных магических чисел в коде скрывает намерение разработчиков при выборе этого числа, увеличивает возможности для скрытых ошибок . .. и затрудняет адаптацию и расширение программы в будущем.]

К счастью, я могу выполнить обновление за один раз и при этом сделать очевидным, чего я пытаюсь достичь, используя объединение…

 - Попытка №3
ОБНОВЛЕНИЕ Набор продуктов прекращен = 1
ИЗ продуктов P
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Категории C НА P.CategoryID = C.CategoryID
ГДЕ CategoryName IN («Морепродукты», «Мясо / птица»)
И прекращено = 0 

Стоит уделить время тому, чтобы взглянуть на различия между этими двумя последними операторами SQL. («Попытка №2» и «Попытка №3»)

  • Бит, выполняющий ОБНОВЛЕНИЕ, одинаков в обоих - это логично, что - это , чего мы пытаемся достичь.
  • Предложение WHERE очень похоже… в первой версии есть «магические числа», тогда как во второй версии явно используются слова из требований, «Морепродукты» и «Мясо / птица», но положения по сути остаются теми же.
  • Строка INNER JOIN в # 3 выглядит нормально - ничего особенного, хотя, возможно, мы не ожидали этого в операторе UPDATE.
  • Этот лишний FROM в Попытке №3 выглядит странно. Похоже, что мы определяем SELECT, а не обновляем таблицу.В каком-то смысле это именно то, что мы делаем…

Мы определяем набор данных, для которых мы хотим выполнить обновление. Думайте об этом как о «обновлении строк, полученных ОТ этого запроса». Это внутреннее объединение теперь имеет смысл, поскольку мы уже видели, как объединения могут помочь фильтровать набор данных.

В приведенном выше примере мы устанавливали значение столбца на одно значение для всех строк в наборе. Что, если мы хотим установить значения на основе фактических значений в другой таблице?

Представьте, что наш отдел поддержки переместил несколько серверов и переместил все фотографии наших сотрудников.Они любезно предоставили нам список идентификаторов сотрудников и новое расположение их фотографий. Таблица довольно простая:

 СОЗДАТЬ ТАБЛИЦУ [dbo]. [NewPhotos] (
[EmployeeID] [int] IDENTITY (1,1) NOT NULL,
[PhotoPath] [nvarchar] (255) NULL
) НА [ОСНОВНОЙ] 

Мы используем ту же технику, что и для категории, то есть присоединение, но на этот раз присоединение не просто предоставляет фильтр (набор сотрудников, фотографии которых переместились), оно также предоставляет новое значение, которое нам нужно использовать. на каждого Сотрудника:

 ОБНОВЛЕНИЕ Сотрудники SET PhotoPath = New.PhotoPath
ОТ сотрудников E
INNER JOIN newPhotos New ON E.EmployeeID = New.EmployeeID 

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

Для экономии ввода можно использовать псевдоним таблицы вместо полного имени

 ОБНОВЛЕНИЕ  E  НАБОР PhotoPath = New.PhotoPath
ОТ сотрудников E
INNER JOIN newФотографии Новинки ON E.EmployeeID = New.EmployeeID 

Промежуточные итоги с использованием самосоединений

Чтобы получить «текущие» итоги или подсчеты из набора данных, вы можете использовать таблицу с «самосоединением», то есть соединением с самим собой.

Самосоединение - это запрос, в котором таблица соединяется (сравнивается) сама с собой. Самосоединения используются для сравнения значений в столбце с другими значениями в том же столбце в той же таблице. Одно из практических применений самосоединений: получение текущих счетчиков и текущих итогов в запросе SQL.]

Представьте, что мы хотим получить промежуточную сумму и текущее количество всех заказов по продуктам. Мы можем использовать:

 ВЫБЕРИТЕ P.ProductName, t1.OrderID, t1.Quantity,
  RunningTotal = SUM (t2.Quantity),
  RunningCount = COUNT (t2.Quantity)
FROM [Детали заказа] AS t1
ВНУТРЕННЕЕ СОЕДИНЕНИЕ [Детали заказа] КАК t2 НА t1.ProductID = t2.ProductID
И t1.OrderID> = t2.OrderID
INNER JOIN Products P ON t1.ProductID = P.ProductID
ГРУППА ПО P.ProductName, t1.OrderID, t1.Quantity
ЗАКАЗ П.ProductName, t1.OrderID 

Обратите внимание на AND t.OrderID> t2.OrderID… - это та часть предложения ON , которая обеспечивает механизм для «бегущей» суммы, когда мы выполняем SUM и COUNT . ]

Попался! - Присоединяйтесь к подсказкам

Я собираюсь упомянуть здесь подсказки соединения очень кратко и только потому, что есть такая вещь, как подсказка LOOP JOIN.] . Однако я крайне опасаюсь предполагать, что смогу определить, какими должны быть эти улучшения!

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

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

Решите множество проблем с помощью общих табличных выражений

Общее табличное выражение (CTE) можно рассматривать как временный набор результатов, который определяется в пределах области выполнения одного оператора SELECT, INSERT, UPDATE, DELETE или CREATE VIEW. CTE похож на производную таблицу в том, что он не хранится как объект и действует только на время выполнения запроса. В отличие от производной таблицы CTE может ссылаться на себя и на него можно ссылаться несколько раз в одном запросе.] - отличный способ избежать использования процедурных циклов. Вот несколько примеров…

CTE, пример 1 - обход иерархии

База данных Northwind содержит таблицу «Сотрудники», в которой перечислены все сотрудники компании, а также указаны сотрудники, которым подчиняются (кто их руководитель).

Если мы запросим таблицу

 ВЫБЕРИТЕ EmployeeID, ReportsTo, LastName, FirstName, Title
ОТ сотрудников
ORDER BY ReportsTo, EmployeeID
 

Получаем

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

Из диаграммы легко увидеть, что Энн Додсворт, Роберт Кинг и Майкл Суяма находятся на «низшем» уровне, Эндрю Фуллер - «начальник», а все остальные находятся на том же уровне отчетности на среднем уровне (уровень 2). Не так просто увидеть эту структуру в результатах запроса, поскольку нам приходится возвращаться к результатам, чтобы увидеть, кто кому подчиняется.

Мы можем обойти эту проблему, используя рекурсивное общее табличное выражение (rCTE) для обхода этой иерархии и получения дополнительной информации по каждой строке, т.е.е. «Уровень» на диаграмме для каждого сотрудника и путь отчетности. Вот полный запрос

; С Emp_CTE AS
(
    ВЫБЕРИТЕ EmployeeID, ReportsTo, LastName, FirstName, Title
        , 1 как RLevel
        , MtoE = CAST (isnull (ReportsTo, 0) AS VARCHAR (MAX)) + '/' +
            CAST (EmployeeID КАК VARCHAR (MAX))
    ОТ сотрудников
    ГДЕ ReportsTo IS NULL
    СОЮЗ ВСЕ
    ВЫБЕРИТЕ e.EmployeeID, e.ReportsTo, e.LastName, e.FirstName, e.Title
        , RLevel + 1
        , MtoE = MtoE + '/' + CAST (например,EmployeeID КАК VARCHAR (MAX))
    ОТ сотрудников e
    ВНУТРЕННЕЕ ПРИСОЕДИНЕНИЕ Emp_CTE ecte ON ecte.EmployeeID = e.ReportsTo
)
ВЫБЕРИТЕ EmployeeID, EC.ReportsTo, LastName, FirstName, Title, RLevel, MtoE
ОТ Emp_CTE EC
 

Что на самом деле происходит в этом запросе?

Первая часть запроса

 ВЫБЕРИТЕ EmployeeID, ReportsTo, LastName, FirstName, Title, 1 как RLevel
    , MtoE = CAST (isnull (ReportsTo, 0) AS VARCHAR (MAX)) + '/' + CAST (EmployeeID AS VARCHAR (MAX))
ОТ сотрудников
ГДЕ ReportsTo IS NULL
 

является «якорным элементом» для CTE, также именуемым «вызов» - i.е. наша отправная точка. В этом случае я ищу любого Сотрудника, который никому не подчиняется - «босса».

Следующая часть запроса - это «рекурсивный элемент», который вызывается повторно, пока не будут обработаны все записи.

 ВЫБЕРИТЕ e.EmployeeID, e.ReportsTo, e.LastName, e.FirstName,
    e.Title, RLevel + 1
    , MtoE = MtoE + '/' + CAST (e.EmployeeID AS VARCHAR (MAX))
ОТ сотрудников e
ВНУТРЕННЕЕ ПРИСОЕДИНЕНИЕ Emp_CTE ecte ON ecte.EmployeeID = e.ReportsTo
 

UNION ALL объединяет все наборы результатов вместе во временный набор результатов под названием Emp_CTE .Наконец, я запрашиваю этот временный набор результатов, как если бы это была просто еще одна таблица в базе данных.

Итак, наш элемент привязки возвращает этот набор данных

 2 NULL Фуллер Эндрю Вице-президент по продажам 1 0/2 

Первая итерация рекурсивного члена возвращает список сотрудников, которые отчитываются перед Эндрю, то есть первая итерация использует набор результатов из элемента Anchor в качестве входных данных.

 1 2 Davolio Nancy Торговый представитель 2 0/2/1
3 2 Leverling Джанет, торговый представитель 2 0/2/3
4 2 Павлин Маргарет Торговый представитель 2 0/2/4
5 2 Бьюкенен Стивен Менеджер по продажам 2 0/2/5
8 2 Каллахан Лаура Координатор внутренних продаж 2 0/2/8 

Следующая итерация возвращает следующие результаты - список всех людей, которые подчиняются Нэнси, Джанет, Маргарет, Стивену или Лоре.Итерация рекурсивного элемента использует выходные данные предыдущей итерации (рекурсивного элемента) в качестве входных данных и перемещается на один уровень вниз по иерархии.

 6 5 Суяма Майкл Торговый представитель 3 0/2/5/6
7 5 Торговый представитель King Robert 3 0/2/5/7
9 5 Додсворт Энн, торговый представитель 3 0/2/5/9 

Следующая итерация возвращает пустой набор данных, поскольку у Майкла, Роберта и Энн нет никого, кто отчитывается перед ними, и поэтому рекурсия заканчивается.Все результаты объединяются в виде выходных данных rCTE в качестве выходных данных и могут быть запрошены, как если бы вы запрашивали любую другую таблицу.

Весь rCTE дает эти результаты

Давайте более подробно рассмотрим эти производные столбцы RLevel и MtoE

.

RLevel прост - мы устанавливаем его на 1 в элементе Anchor, а затем увеличиваем его на 1 каждый раз, когда вызывается рекурсивный элемент. Таким образом, он показывает «уровень» в иерархии для каждого сотрудника.

MtoE (от менеджера к сотруднику) отслеживает «путь» вниз по дереву иерархии к каждому сотруднику, используя EmployeeID на каждом уровне отчетности - «Уровень 0» / «Уровень 1» / «Уровень 2» / «Уровень 3» /… и т. Д. .

Например, Для сотрудника Энн Додсворт столбец MtoE выглядит как «0/2/5/9»…

  1. Начиная с правой стороны этой строки найдите Идентификатор сотрудника 9 на самом низком уровне (например, Энн Додсворт)
  2. На следующем уровне вверх (Уровень 2) найдите Идентификатор сотрудника 5 (Стивен Бьюкенен)
  3. На следующем уровне вверх (Уровень 1) найдите идентификатор сотрудника 2 (Эндрю Фуллер)
  4. На следующем уровне выше (уровень 0) найдите идентификатор сотрудника 0 (никто)

Итак, Энн Додсворт отчитывается Стивену Бьюкенену, который отчитывается перед Эндрю Фуллером, который отчитывается не- один.

Возможность отслеживать такие записи может быть полезна для вашего уровня представления - например, так что вы можете сразу перечислить каждого менеджера, за которым следуют их непосредственные подчиненные.

Попался! - MAXRECURSION

При использовании рекурсии довольно легко попасть в ситуацию бесконечного цикла с плохо сформированным CTE.

Давайте посмотрим на CTE для структуры отчетности менеджеров с небольшими изменениями - я использовал псевдоним CTE в SELECT рекурсивного элемента вместо таблицы Employees i.е. Я использовал псевдоним «ecte» вместо «e». В приведенном ниже коде он выделен жирным шрифтом.

; С Emp_CTE AS
(
    ВЫБЕРИТЕ EmployeeID, ReportsTo, LastName, FirstName, Title
    ОТ сотрудников
    ГДЕ ReportsTo IS NULL
    СОЮЗ ВСЕ
    ВЫБЕРИТЕ ecte.EmployeeID, ecte.ReportsTo,
        ecte.LastName, ecte.FirstName, ecte.Title
    ОТ сотрудников e
    ВНУТРЕННЕЕ ПРИСОЕДИНЕНИЕ Emp_CTE ecte ON ecte.EmployeeID = e.ReportsTo
)
ВЫБЕРИТЕ EC.EmployeeID КАК ID, ISNULL (E.LastName + ',' + E.FirstName, 'No-one!') Как Manager, EC.Фамилия, EC.FirstName, EC.Title
ОТ Emp_CTE EC
LEFT JOIN Employees E ON EC.ReportsTo = E.EmployeeID
ЗАКАЗ ОТ MtoE, EC.ReportsTo, EC.EmployeeID
 

Когда я запускаю этот запрос, я получаю сообщение об ошибке

Msg 530, уровень 16, состояние 1, строка 1 Оператор завершен. Максимальное количество рекурсии 100 было исчерпано до завершения оператора.

Странно! В таблице «Сотрудники» всего 9 записей, поэтому я ожидаю, что в результате получится только 9 строк - ну, конечно, не более 100! Такие ошибки бывает сложно остановить, но в данном случае я сделал это намеренно.Что происходит с использованием псевдонима ecte вместо e? Я , а не , пытаюсь передать результат предыдущей итерации в следующий проход, я на самом деле постоянно ссылаюсь на предыдущий набор результатов (ecte), который всегда будет содержать данные (в этом примере) - т.е. бесконечный цикл.

К счастью, в SQL Server установлено количество рекурсий по умолчанию (как ни странно, 100), так что мне не приходится сидеть и ждать, пока этот запрос (никогда) не завершится.Но это отличный способ убедиться, что мой код не вызывает бесконечного цикла, и если есть один SQL Server, он убьет его за меня!

Итак, я получил сообщение об ошибке, посмотрел на свой код, обнаружил ошибку в рекурсивном элементе и исправил эти псевдонимы с ecte на e. Теперь, когда я повторно запускаю запрос, я получаю ожидаемые результаты, которые мы обсуждали выше. Работа выполнена? Не совсем.

Что, если бы на столе было более 100 сотрудников? Остановит ли наш запрос количество рекурсий по умолчанию, прежде чем мы доберемся до того места, где нам нужно быть? Короче говоря, да, но мы можем что-то с этим поделать.] по запросу.

Допустим, мы ожидали возврата около 150 строк, мы могли бы добавить

 ОПЦИЯ (MAXRECURSION 150)
 

до конца запроса (после предложения ORDER BY)

Фантастика! Тестирую. Оно работает. Я могу подписать его на продакшн и отправиться отмечать успешное внедрение.

Переместите время вперед, и теперь в нашей компании работает 151 сотрудник. Хлопнуть! Запрос снова начинает терпеть неудачу. Я мог бы изменить подсказку на произвольно большое число, но нет ничего, что говорило бы о том, что наша компания не собирается выходить на мировой рынок и нарушать любое число, которое я выберу.Придется снова изменить код. Не забывайте, что мы говорили о магических числах ранее.

Я могу избежать этой ситуации, сказав SQL Server, что мы не хотим никаких ограничений на количество рекурсий, и я делаю это с помощью

 ОПЦИЯ (МАКСИМАЛЬНАЯ ЗАЩИТА 0)
 

Параметр 0 (ноль) не означает «без рекурсий»; это означает « без ограничений на рекурсии».

Попался снова!

Никогда использовать OPTION (MAXRECURSION 0) в запросе до тех пор, пока запрос не будет тщательно протестирован… или вы можете вернуться к сценарию бесконечного цикла.

CTE, пример 2 - сравнение предыдущих и последующих значений

Поскольку CTE создают временные наборы данных, подобные временным таблицам, у вас есть большая гибкость в том, что вы можете с ними делать. Например, у вас может быть несколько CTE, к которым вы можете присоединиться, или вы также можете выполнить «самостоятельное присоединение». Вот пример, демонстрирующий, как это может быть полезно…

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

Достаточно просто получить OrderDates для этого клиента

 ВЫБЕРИТЕ CustomerID, OrderDate
ОТ Заказы
ГДЕ CustomerID = 'ERNSH'
 

И получаем 30 строк данных…

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

Взгляните на этот CTE:

; С КТР КАК
(
    ВЫБЕРИТЕ CustomerID, OrderDate
    , ROW_NUMBER () OVER (ORDER BY OrderID) AS rn
    ОТ Заказы
    ГДЕ CustomerID = 'ERNSH'
)
ВЫБЕРИТЕ CTE.CustomerID, CTE.OrderDate, ISNULL (DATEDIFF (dd, prev.OrderDate, CTE.OrderDate), 0) AS DIFF
ОТ CTE
LEFT OUTER JOIN CTE prev ON prev.rn = CTE.rn - 1
 

К нашему исходному запросу для получения дат заказа мы добавили вызов SQL-функции ROW_NUMBER (), которая присвоит всем заказам последовательный номер строки от 1 до 30 (этот «последовательный» важен ... ) на основе идентификатора заказа.Идентификатор заказа был определен как

 [OrderID] [int] IDENTITY (1,1) NOT NULL
 

, поэтому заказ в этом столбце означает, что я получу заказы, возвращенные в том порядке, в котором они были введены.

Я поместил этот запрос в простое выражение общей таблицы (в данном случае «простое» означает «нерекурсивный»), а затем выполняю запрос к этому CTE. Этот запрос включает LEFT OUTER JOIN обратно к тому же CTE - он имеет ALIAS prev выше. Это соединение дает нам запись, имеющую номер предыдущей строки для строки, которую мы в данный момент просматриваем.

 LEFT OUTER JOIN CTE prev.rn = CTE.rn - 1
 

Итак, теперь мы можем сравнить OrderDate из CTE с OrderDate в объединении до , чтобы получить разницу в днях между заказами.

 РАЗНДАТ (дд, предыдущая дата заказа, CTE.Дата заказа) КАК РАЗН
 

Наш новый запрос дает результаты:

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

 LEFT OUTER JOIN CTE nxt ON nxt.rn = CTE.rn + 1
 

И вы можете делать с CTE практически все, что вы делали бы со столом - e.грамм. Группировать по, средним и т. Д.

; С КТР КАК
(
    ВЫБЕРИТЕ CustomerID, OrderDate
        , ROW_NUMBER () OVER (ORDER BY OrderID) AS rn
    ОТ Заказы
    ГДЕ CustomerID = 'ERNSH'
)
ВЫБЕРИТЕ CTE.CustomerID, AVG (ISNULL (DATEDIFF (dd, prev.OrderDate, CTE.OrderDate))) AS DIFF
ОТ CTE
LEFT OUTER JOIN CTE prev ON prev.rn = CTE.rn - 1
ГРУППА ПО CTE.CustomerID
 

Чтобы получить

 ЭРНШ 21 

CTE, пример 3 - использование нескольких CTE

Я собираюсь продемонстрировать другой метод получения этого среднего значения, используя второй CTE, связанный с первым.]

; С КТР КАК
(
    ВЫБЕРИТЕ CustomerID, OrderDate
        , ROW_NUMBER () OVER (ORDER BY OrderID) AS rn
    ОТ Заказы
    ГДЕ CustomerID = 'ERNSH'
), CTE2 AS
(
    ВЫБЕРИТЕ CTE.CustomerID,
        ISNULL (DATEDIFF (дд, предыдущийOrderDate, CTE.OrderDate), 0) AS DIFF
    ОТ CTE
    LEFT OUTER JOIN CTE prev ON prev.rn = CTE.rn - 1
)
ВЫБЕРИТЕ CustomerID, «Среднее», СРЕДНЕЕ (РАЗН)
ОТ CTE2
ГРУППА ПО CustomerID
 

Обратите внимание, что второй CTE (гениально названный CTE2 ) использует первый CTE для получения некоторых данных, а последний запрос относится только к CTE2.

Это все хорошо, но в нашей базе данных много клиентов. На самом деле мы не хотим, чтобы этот запрос выполнялся отдельно для каждого клиента. К счастью, есть версия функции ROW_NUMBER, которая РАЗБИРАЕТ данные для нас. Мы можем указать запросу перезапускать нумерацию строк с 1 каждый раз, когда мы сталкиваемся с новым CustomerId. Вот измененный запрос, чтобы получить номера строк для каждого клиента

 ВЫБЕРИТЕ CustomerID, OrderDate, OrderID
    , ROW_NUMBER ()
    НАД (РАЗДЕЛЕНИЕ ПО CustomerID ORDER BY OrderId) AS rn
ОТ Заказы
 

Мы изменили предложение OVER, чтобы упорядочить данные только по OrderId, и передали его PARTITION или проанализировать данные по каждому CustomerId.Этот запрос дает следующие результаты

Обратите внимание, как значение rn перезапускается с 1 для каждого нового набора данных клиента.

Попался! Номер строки больше не уникален

Если я помещу этот запрос в наш CTE и выполню его, я получу… 45844 строки в нашем наборе результатов! Ого! Всего заказов 830, что происходит?

Помните мое присоединение к «предыдущей» строке из запроса…

 LEFT OUTER JOIN CTE prev.rn = CTE.rn - 1
 

Предполагалось, что каждый номер строки уникален - потому что мы запрашивали только один CusomerId.Теперь у нас столько строк с номером 1, сколько у нас CustomerIds. Нам необходимо внести поправки в этот пункт ON…

; С КТР КАК
(
    ВЫБЕРИТЕ CustomerID, OrderDate, OrderID
        , ROW_NUMBER () OVER (РАЗДЕЛЕНИЕ ПО идентификатору клиента ORDER BY OrderId) КАК rn
    ОТ Заказы
)
ВЫБЕРИТЕ CTE.CustomerID, CTE.OrderDate, ISNULL (DATEDIFF (dd, prev.OrderDate, CTE.OrderDate), 0) AS DIFF
ОТ CTE
LEFT OUTER JOIN CTE prev ON prev.rn = CTE.rn - 1
      И CTE.CustomerID = предыдущийCustomerID 
 

Теперь мы получаем разумные результаты - 830 строк, начинающихся с

.

Вам понадобится что-то подобное с нашим примером с несколькими CTE, если вы хотите получить среднее количество дней между заказами также для всех клиентов (помня, что это был надуманный пример для демонстрации нескольких CTE, а не хороший способ получить эти данные. ).

; С КТР КАК
(
    ВЫБЕРИТЕ CustomerID, OrderDate, OrderID
        , ROW_NUMBER ()
       НАД (РАЗДЕЛЕНИЕ ПО CustomerID ORDER BY OrderId) AS rn
    ОТ Заказы
), CTE2 AS
(
    ВЫБЕРИТЕ CTE.CustomerID,
    ISNULL (DATEDIFF (дд, предыдущийOrderDate, CTE.OrderDate), 0) AS DIFF
    ОТ CTE
    LEFT OUTER JOIN CTE prev ON prev.rn = CTE.rn - 1
          И CTE.CustomerID = prev.CustomerID
)
ВЫБЕРИТЕ CustomerID, «Среднее», СРЕДНЕЕ (РАЗН)
ОТ CTE2
ГРУППА ПО CustomerID
 

CTE, пример 4 - последовательности - создание списков

Запросы

CTE могут быть легко использованы для генерации последовательностей.]

Представьте, что мы хотим составить список сотрудников на следующие две недели.

Мы можем использовать rCTE для генерации последовательности дат, начиная с сегодняшнего дня в течение 14 дней. Если мы затем ПЕРЕСЕЧЕМ эту небольшую последовательность с таблицей сотрудников, мы получим каждую строку из rCTE для каждого сотрудника. Как это:

; С КТР КАК
(
    ВЫБЕРИТЕ GETDATE () КАК данные
    СОЮЗ ВСЕ
    ВЫБРАТЬ ДАТУ ДОБАВИТЬ (ДД; 1, данные)
    ОТ CTE
    ГДЕ DATEADD (DD; 1; данные) 

 

Что дает следующие результаты:

CTE, пример 5 - Последовательности - поиск пропавших предметов

Один из приемов с последовательностями дат - определить, какие даты отсутствуют в наборе данных. Как и в примере выше, мы будем использовать rCTE для создания последовательности всех дат между датой начала и датой окончания. Второй запрос к нему будет LEFT OUTER JOIN к таблице Orders.Результатом будут все даты, когда не было размещено ни одного заказа.

 ОБЪЯВИТЬ @minDate DATE
ОБЪЯВИТЬ @maxDate ДАТУ
ВЫБЕРИТЕ @minDate = MIN (OrderDate), @maxDate = MAX (OrderDate) ИЗ заказов

; С CTE AS
(
    ВЫБРАТЬ @minDate как данные
    СОЮЗ ВСЕ
    ВЫБРАТЬ ДАТУ ДОБАВИТЬ (ДД; 1, данные)
    ОТ CTE
    ГДЕ ДОБАВИТЬ ДАТУ (ДД; 1; данные) <= @maxDate
), DateSeq AS
(
    ВЫБРАТЬ данные
    ОТ CTE
)
ВЫБЕРИТЕ D.datum, DATENAME (DW, datum)
ОТ DateSeq D
LEFT OUTER JOIN Заказы O ON O.OrderDate = D.datum
ГДЕ О.OrderID IS NULL
ВАРИАНТ (MAXRECURSION 0)
 
Совет - эта точка с запятой

При создании CTE у вас могут быть другие операторы SQL до него или после его тестирования вы можете захотеть вставить код для CTE в гораздо более крупную процедуру. Затем вы можете встретить это сообщение об ошибке:

Неправильный синтаксис рядом с ключевым словом «с». Если этот оператор является общим табличным выражением, предложением xmlnamespaces или предложением контекста отслеживания изменений, предыдущий оператор должен завершаться точкой с запятой.

Это довольно очевидное сообщение об ошибке, и решение довольно простое ... просто поставьте точку с запятой в конце строки над объявлением CTE. Но если вы вставите еще код перед CTE, вам придется не забыть сделать это снова. Среди разработчиков MSSQL распространена привычка ставить точку с запятой перед WITH, как в приведенных выше примерах.

Решение проблем с функциями окна SQL

Методы, которые мы использовали в предыдущем разделе, будут работать с большинством версий SQL Server, но одна из областей, которые постоянно улучшаются по сравнению с выпусками, - это «Оконные функции».]

Это отличный способ получить подробные сведения об отдельных строках, а также получить совокупную информацию.

В примере выше я использовал оконную функцию ROW_NUMBER (). Включив предложение PARTITION, я смог применить нумерацию строк к каждому набору данных для каждого клиента (без использования процедурного цикла). «Дескриптор окна» был CustomerID. Мы заметили, что если мы опускаем PARTITION BY, тогда все строки в таблице получают номер строки - i.]

Этот запрос работал хорошо и получил данные, которые мы хотели, но было бы неплохо, если бы был более сжатый способ сделать это - вместо того, чтобы генерировать дополнительные данные (ROW_NUMBER ()), создавать CTE, а затем выполнять самостоятельное присоединение. Что ж, для SQL Server 2012 (не в версии Express) и более поздних версий (включая 2014 Express) для этого доступны дополнительные функции окна SQL.

Этот запрос дает точно такие же результаты, как и до

 ВЫБЕРИТЕ CustomerID, OrderDate,
ISNULL (DATEDIFF (dd,  LAG  (OrderDate, 1) OVER (РАЗДЕЛ ПО CustomerID
                                ORDER BY CustomerID, OrderId), OrderDate), 0) AS DIFF
ОТ Заказы
 

Если раньше мы использовали пред.OrderDate в функции DATEDIFF, теперь мы используем

  LAG  (OrderDate, 1) OVER (РАЗДЕЛЕНИЕ ПО CustomerID ORDER BY OrderId)
 

Обратите внимание, что предложение OVER точно такое же, что я использовал ранее для ROW_NUMBER ()? Это имеет смысл, потому что нас по-прежнему интересуют данные для каждого идентификатора клиента, и нам по-прежнему нужны заказы в порядке OrderDate. Однако номер строки исчез, и мы не используем никаких самостоятельных соединений, поэтому псевдоним таблицы prev также исчез.

Функция LAG делает все за меня. В этой части запроса теперь указано

Получить значение OrderDate из предыдущей строки LAG (OrderDate,
Переход назад на одну строку из текущей строки в коллекции , 1)
Коллекция должна быть разделенным на CustomerID I.e. не выбирайте предыдущий OrderDate, если он не был для того же CustomerID, что и текущая строка.] предоставляется в более поздних версиях SQL Server.

Другой пример - помните одно из «требований» с самого начала, которое может побудить вас подумать об использовании цикла?

«Вычислить общее и среднее всех значений для каждого продукта»

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

Это выглядит довольно сложно, но на самом деле довольно просто; Я могу использовать некоторые функции агрегирования SQL и GROUP BY…

 ВЫБРАТЬ ОТЛИЧИТЕЛЬНЫЙ P.ProductID, p.ProductName,
COUNT (OD.OrderID) AS CountOfOrders,
    AVG (количество) AS AvgPerOrder,
    СУММА (количество) КАК ВсегоПродано
ИЗ [Продукты] P
LEFT JOIN [Детали заказа] OD на OD.ProductID = P.ProductID
ГРУППА ПО P.ProductID, p.ProductName
ЗАКАЗАТЬ ПО P.ProductName
 

Но, скажем, я также должен показать количество на единицу, количество единиц на складе, количество единиц заказа и уровень повторного заказа в наших результатах.

Когда мы добавляем эти поля в наш список SELECT, мы получаем ошибки

- Сообщение 8120, уровень 16, состояние 1, строка 1 - столбец «Продукты.QuantityPerUnit ’недопустимо в списке выбора, потому что он не содержится ни в агрегатной функции, ни в предложении GROUP BY. --Msg 145, уровень 15, состояние 1, строка 1 - элементы ORDER BY должны появиться в списке выбора, если указано SELECT DISTINCT.

Одно из решений - расширить наше предложение GROUP BY, включив в него ВСЕ другие поля, которые мы хотим показать…

 ВЫБРАТЬ ОТЛИЧИТЕЛЬНЫЙ P.ProductID, p.ProductName, QuantityPerUnit, UnitsInStock, UnitsOnOrder, ReorderLevel,
    СЧЕТ (OD.OrderID) КАК CountOfOrders,
    AVG (количество) AS AvgPerOrder,
    СУММА (количество) КАК ВсегоПродано
ИЗ [Продукты] P
ВНУТРЕННЕЕ СОЕДИНЕНИЕ [Детали заказа] OD на OD.ProductID = P.ProductID
ГРУППА ПО P.ProductID, p.ProductName,  QuantityPerUnit, UnitsInStock, UnitsOnOrder, ReorderLevel 
ЗАКАЗАТЬ ПО P.ProductName
 

ИЛИ нам нужно выполнить какой-то подзапрос, а затем выполнить соединение…

 ВЫБРАТЬ ОТЛИЧИТЕЛЬНЫЕ SummaryData.ProductID, SummaryData.ProductName,
            Prod.QuantityPerUnit, Prod.UnitsInStock, Prod.UnitsOnOrder, Prod.ReorderLevel,
            SummaryData.CountOfOrders, SummaryData.AvgPerOrder, SummaryData.TotalSold
ОТ (ВЫБРАТЬ ОТЛИЧИТЕЛЬНЫЙ P.ProductID, p.ProductName,
    COUNT (OD.OrderID) AS CountOfOrders,
    AVG (количество) AS AvgPerOrder,
    СУММА (количество) КАК ВсегоПродано
ИЗ [Продукты] P
LEFT JOIN [Детали заказа] OD на OD.ProductID = P.ProductID
ГРУППА ПО P.ProductID, p.ProductName) SummaryData
ВНУТРЕННЕЕ СОЕДИНЕНИЕ [Продукты] Prod ON SummaryData.ProductID = Prod.ProductID
ЗАКАЗАТЬ ПО SummaryData.ProductName
 

Это начинает выглядеть ужасно беспорядочно!

Однако вместо этого мы можем использовать предложения OVER с этими агрегатными функциями (начиная с SQL2008) и РАЗБИРАТЬ данные в нужные нам окна, полностью избавившись от предложения GROUP BY…

 ВЫБРАТЬ ОТЛИЧИТЕЛЬНЫЙ P.ProductID, p.ProductName, QuantityPerUnit, UnitsInStock, UnitsOnOrder, ReorderLevel,
COUNT (OD.OrderID)  OVER (РАЗДЕЛ ПО P.ProductID)  AS CountOfOrders,
    СРЕДНЕЕ (количество)  БОЛЕЕ (РАЗДЕЛ ПО ИДЕНТИФИКАТОРУ продукта)  ВРЕМЯ СРЕДНЕГО ЗАКАЗА
    , SUM (количество)  OVER (РАЗДЕЛЕНИЕ ПО P.ProductID)  AS TotalSold
ИЗ [Продукты] P
LEFT JOIN [Детали заказа] OD на OD.ProductID = P.ProductID
ЗАКАЗАТЬ ПО P.ProductName
 

Лично я считаю, что это выглядит намного аккуратнее - и легче понять, что происходит.].

Табличные результаты (строки в столбцы) - Игра с Pivot

Еще одна концепция, с которой сталкиваются новички, - это когда есть требование свести в таблицу набор результатов.

Представьте, что нас попросили узнать общее количество заказов, размещенных по странам, по годам. Достаточно легко получить информацию

 ВЫБЕРИТЕ DATEPART (Год, Дата заказа) КАК Год заказа,
      ShipCountry, COUNT (идентификатор заказа)
ОТ Заказы
ГРУППА ПО DATEPART (год, дата заказа), страна доставки
 

Но результаты выводятся в виде одной строки за год для каждой страны…, / p>

Это не совсем то, что нам нужно, было бы уместно что-то более похожее.

Я мог бы просмотреть наши результаты, создав новую таблицу в правильном формате ... но вы знаете, что мне не нужно этого делать.] вместо…

 ВЫБРАТЬ *
ИЗ
(
    ВЫБЕРИТЕ DATEPART (год, дата заказа) как год заказа, страна доставки, идентификатор заказа
    ОТ Заказы
) Как источник
ВРАЩАТЬСЯ
(
    COUNT (идентификатор заказа)
    FOR OrderYear IN ([1996], [1997], [1998])
) AS pvt
ЗАКАЗАТЬ ПО СТРАНЕ Судоходства
 

Обратите внимание на этот запрос, который я называю source - он очень похож на наш исходный запрос для извлечения данных, но мы потеряли GROUP BY.

Раздел PIVOT будет подсчитывать заказы для меня на основе каждого года

 FOR OrderYear IN ([1996], [1997], [1998])
 

и.е. список «столбцов» в предложении IN. Обратите внимание, что эти годы не являются строками, они представляют собой столбцы окончательного набора результатов). PIVOT вращает данные на основе этого списка.

Другими словами, SQL ищет значения в OrderYear, которые соответствуют именам в списке столбцов, а затем подсчитывает количество совпадающих идентификаторов OrderID (вместо COUNT можно использовать другие агрегатные функции). Результатом будет секционированных на основе других элементов в операторе SELECT источника .Чтобы продемонстрировать это, если мы выберем ShipCountry из SELECT, он выполнит COUNT по всему набору результатов из источника , дав следующие результаты

Отлично - Теперь мы можем легко создавать таблицы из наших данных.

Подводя итог, для сводки данных вам нужен исходный запрос ... он должен содержать поле, в котором вы хотите повернуть данные, и любые поля, по которым вы хотите разделить данные. Затем вам понадобится ключевое слово PIVOT, за которым следуют инструкции о том, как повернуть данные.Этот раздел должен использовать агрегатную функцию, такую ​​как COUNT, MAX, MIN и т. Д. Предложение FOR будет определять столбцы в таблице результатов ... будут отображаться только эти столбцы, вам не нужно включать все возможные значения, если вас не интересует эта информация, но, что важно, если вы не включите все возможные значения, вы можете случайно исключить информацию, которую действительно хотели.

Попался! - Имена столбцов не могут быть числами
.

Поскольку имя столбца не может быть полностью числовым, нам пришлось заключить имена «столбцов» в квадратные скобки - [], чтобы сделать его допустимым именем столбца.Следовательно,

 FOR OrderYear IN ([1996], [1997], [1998])
 

А не

 FOR Order Год IN (1996,1997,1998)
 

То же самое верно, если значения, которые вы пытаетесь развернуть, являются зарезервированными словами в SQL, например

 FOR SomeData IN ([Имя], [Дата], [Сумма], значение)
 

Также верно, если значения, которые вы пытаетесь развернуть, содержат пробелы, например

 FOR Title IN ([Торговый представитель], [Менеджер по продажам])
 

Это становится очень важным, когда вы генерируете SQL из динамического запроса…

Динамические запросы SQL и списки, разделенные запятыми

Проблема с Pivot заключается в том, что со временем в последующие годы появятся даты заказа.Эти данные не будут включены в наш запрос, потому что мы жестко запрограммировали столбцы, которые хотим видеть… 1996, 1997 и 1998.

Мы действительно не хотим обновлять запрос каждый год. Было бы неплохо, если бы мы могли сгенерировать этот список из самих данных.

Получить информацию достаточно просто

 SELECT DISTINCT DATEPART (Year, OrderDate) AS OrderYear из заказов
 

Мне нужно записать этот список лет в список, разделенный запятыми, в квадратных скобках вокруг чисел.] в довольно простом запросе, как показано ниже

 DECLARE @listStr VARCHAR (MAX) = null
; С лет КАК
(
    ВЫБЕРИТЕ DISTINCT DATEPART (Year, OrderDate) AS OrderYear из заказов
)
ВЫБРАТЬ @listStr = COALESCE (@listStr + '], [', '') + CAST (OrderYear AS Varchar)
С года

ПЕЧАТЬ '[' + @listStr + ']'
 

Что дает нам список таких столбцов.

 [1998], [1996], [1997]
 

Примечание.]

Я могу поместить свой SQL-запрос в строковую переменную, используя переменную @listStr, полученную мной выше, для перечисления столбцов, которые мне нужны.Я немного разбил это здесь, чтобы было немного понятнее.

 ОБЪЯВИТЬ @sql NVARCHAR (МАКС) =
   N'SELECT * FROM (выберите DATEPART (Year, OrderDate) AS OrderYear '
УСТАНОВИТЕ @sql = @sql + N ', ShipCountry, OrderID из заказов) как s'
НАБОР @sql = @sql +
     N'PIVOT (COUNT (OrderID) FOR OrderYear IN (['+ @listStr + N'])) '
SET @sql = N 'AS pvt order by ShipCountry'
 

Если вы распечатаете содержимое @sql, вы увидите, что мы получаем тот же запрос, что и раньше (добавлены пробелы, чтобы сделать его более читаемым)

 ВЫБРАТЬ *
ИЗ
(
    ВЫБЕРИТЕ DATEPART (Год, Дата заказа) КАК Год заказа, Страна доставки,
            OrderID из заказов) AS s
    PIVOT (COUNT (OrderID) FOR OrderYear IN ([1998], [1996], [1997])
) AS pvt
ЗАКАЗАТЬ ПО СТРАНЕ Судоходства
 

Затем мы можем запустить запрос с этим оператором

 EXEC sp_executesql @sql
 

Этот запрос по-прежнему будет работать по мере добавления данных, если данные добавлены для 1999 года, @listStr автоматически подберет требование для нового столбца, а [1998], [1996], [1997], [1999] будут вставлены в переменная @sql.

Подсказка - обратите внимание, я сказал: «Если вы распечатаете содержимое @sql, вы увидите…». Перед включением EXEC всегда рекомендуется ПЕЧАТЬ и исследовать генерируемый SQL-запрос. Вы можете скопировать SQL из области сообщений и запустить его напрямую, чтобы проверить, работает ли он. Так отладить гораздо проще, чем сразу запустить!

Попался! - как ограничить количество отображаемых столбцов

Через некоторое время результаты вышеприведенного запроса станут немного громоздкими.Более вероятно, что пользователи захотят видеть данные только за последние 5 лет (например).

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

Может показаться, что этого запроса будет достаточно, чтобы ограничить столбцы последними 5 годами

; С лет КАК
(
    ВЫБЕРИТЕ DISTINCT TOP 5 DATEPART (Year, OrderDate) AS OrderYear из заказов
)
ВЫБРАТЬ @listStr = COALESCE (@listStr + '], [', '') + CAST (OrderYear AS Varchar)
С года
 

Но если вы внимательно посмотрите на запрос, который был сгенерирован для динамического SQL выше, список столбцов выглядит следующим образом:

 OrderYear IN ([1998], [1996], [1997])
 

Они вышли совсем не по порядку.]

Итак, вы должны не забыть добавить предложение ORDER BY.

Кроме того, TOP 5 и ORDER BY должны появиться в той же части запроса . Если я попытаюсь поместить ORDER BY в тело CTE, но ТОП 5 в SELECT из CTE, например:

; С лет КАК
(
    ВЫБЕРИТЕ DISTINCT DATEPART (Year, OrderDate) AS OrderYear из заказов ORDER BY OrderYear
)
ВЫБЕРИТЕ ТОП 5 @listStr = COALESCE (@listStr + '], [', '') + CAST (OrderYear AS Varchar)
С года
 

Я получаю сообщение об ошибке:

Предложение ORDER BY недопустимо в представлениях, встроенных функциях, производных таблицах, подзапросах и общих табличных выражениях, если также не указаны TOP, OFFSET или FOR XML.

Однако любой из этих методов будет работать:

; С лет КАК
(
    ВЫБЕРИТЕ DISTINCT TOP 5 DATEPART (Год, Дата заказа) AS OrderYear из заказов  ORDER BY OrderYear 
)
ВЫБРАТЬ @listStr = COALESCE (@listStr + '], [', '') + CAST (OrderYear AS Varchar)
С года
 

или

; С лет КАК
(
    ВЫБЕРИТЕ DISTINCT DATEPART (Year, OrderDate) AS OrderYear из заказов
)
ВЫБЕРИТЕ ТОП 5 @listStr = COALESCE (@listStr + '], [', '') + CAST (OrderYear AS Varchar)
ОТ  года ЗАКАЗАТЬ Заказать год 
 

В этой статье CodeProject есть дополнительные материалы для чтения по динамическому sql.].

Списки в группах с разделителями-запятыми

Представьте себе сценарий, в котором вам нужны списки значений, разделенных запятыми, для набора данных - другими словами, вы хотите Сгруппировать по столбцу

В качестве примера давайте получим список продуктов для каждого заказа. Идентификаторы продуктов хранятся в таблице [Детали заказа], и мы можем использовать FOR XML и STUFF для создания списка этих идентификаторов по идентификатору заказа .

Если мы поместим эту информацию в CTE, мы сможем получить остальную информацию прямо из таблицы [Заказы]:

; С КТР КАК
(
ВЫБЕРИТЕ OrderID, продукты =
    STUFF ((SELECT ',' + CAST (ProductID как nvarchar)
           ОТ [Детали заказа] стр. 1
           ГДЕ p1.OrderID = p2.OrderID
          ДЛЯ ПУТИ XML ('')), 1, 2, '')
ОТ [Детали заказа] стр. 2
ГРУППА ПО ID заказа
)
ВЫБЕРИТЕ CustomerID, OrderDate, CTE.products, CTE.OrderID
FROM [Заказы] O
INNER JOIN CTE НА O.OrderID = CTE.OrderID 

Что дает нам следующие результаты

Временные таблицы и табличные функции

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

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

Мне обязательно нужна петля!

Может наступить время, когда вам просто нужно будет использовать петлю.]

Почему это так? Это технология, основанная на наборах, поэтому циклы, вероятно, являются скорее запоздалым размышлением, чем эффективным способом обработки данных.

Многие инструменты программирования, такие как семейство языков .NET, генераторы отчетов и т. Д., Могут делать это достаточно эффективно. Поэтому рассмотрите возможность передачи набора данных обратно в приложение и позвольте этому приложению выполнять обработку цикла. Это особенно верно в отношении любых визуальных улучшений (например, добавление предшествующих нулей к значению), которые следует оставить на уровне представления вашего приложения.] в SQL обычно используются для управления доступом к курсору, но они могут использоваться и сами по себе. Этот подход полезен, если вы хотите избежать блокировки таблицы при просмотре набора записей. Вот надуманный пример

 DECLARE @min int
ОБЪЯВИТЬ @max int
ОБЪЯВИТЬ @curr int

ВЫБЕРИТЕ @min = MIN (ProductID), @max = MAX (ProductID)
ОТ продуктов, ГДЕ CategoryID = 2

УСТАНОВИТЬ @curr = @min
ПОКА @curr <= @max
НАЧИНАТЬ
    ОБЪЯВЛЕНИЕ @prodname NVARCHAR (125) = NULL
    ВЫБЕРИТЕ @prodname = ProductName
    ОТ продуктов, ГДЕ ProductID = @curr AND CategoryID = 2
    ЕСЛИ НЕ @prodname ЕСТЬ NULL
    НАЧИНАТЬ
        
        ПЕЧАТЬ 'Работа с' + CAST (@curr как varchar)
    КОНЕЦ
    УСТАНОВИТЬ @curr = @curr + 1
КОНЕЦ
 

для цикла в SQL

На самом деле я жульничаю.]

 СОЗДАТЬ ФУНКЦИЮ [dbo]. [FnSplitString]
(
    @string NVARCHAR (МАКС),
    @delimiter СИМВОЛ (1)
)
ВОЗВРАТ @output ТАБЛИЦА (разделенные данные NVARCHAR (МАКС))
НАЧИНАТЬ
    ОБЪЯВИТЬ @start INT, @end INT
    ВЫБЕРИТЕ @start = 1, @end = CHARINDEX (@delimiter, @string)
    ПОКА @start 

 

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

Курсоры в SQL

Наконец, конечно, в SQL есть циклы CURSOR.Недостатки курсоров обсуждались сотни раз, и для меня было бы бессмысленно просто повторять здесь эти аргументы. Вместо этого вот несколько статей для дальнейшего чтения.

Надеюсь, вы уже догадались, что я не люблю курсоры!

Попался! Будьте особенно осторожны с бесконечными циклами

Каждый раз, когда вы используете цикл WHILE, будь то специально для конструкции WHILE, симулируете ли вы цикл FOR или контролируете использование CURSOR, будьте очень осторожны, чтобы включить переменную цикла или условие во все пути через код .Чтобы превратить приведенный выше пример в бесконечный цикл, все, что мне нужно сделать, это переместить одну строку в неправильную позицию, и у нас возникнет проблема:

 DECLARE @min int
ОБЪЯВИТЬ @max int
ОБЪЯВИТЬ @curr int

ВЫБЕРИТЕ @min = MIN (ProductID), @max = MAX (ProductID)
ОТ продуктов, ГДЕ CategoryID = 2

УСТАНОВИТЬ @curr = @min
ПОКА @curr <= @max
НАЧИНАТЬ
    ОБЪЯВЛЕНИЕ @prodname NVARCHAR (125) = NULL
    ВЫБЕРИТЕ @prodname = ProductName
    ОТ продуктов, ГДЕ ProductID = @curr AND CategoryID = 2
    ЕСЛИ НЕ @prodname ЕСТЬ NULL
    НАЧИНАТЬ
        
        ПЕЧАТЬ 'Работа с' + CAST (@curr как varchar)
        УСТАНОВИТЬ @curr = @curr + 1
    КОНЕЦ
КОНЕЦ
 

Когда этот цикл достигает строки, которую необходимо пропустить, переменная @curr больше не увеличивается, поскольку эта строка кода находится внутри внутреннего оператора IF.

Резюме и дополнительная литература

Надеюсь, я продемонстрировал, что в 99 случаях из 100 вам действительно не нужно использовать реальный цикл при работе с запросами SQL.

Я ограничил все свои примеры использованием SQL-запросов в Management Studio. В частности, я даже не коснулся использования хранилищ данных, бизнес-аналитики или кубов. Если вы заинтересованы в дальнейшем чтении, возможно, вы захотите начать с этих вступлений:

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

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

История

Начальная версия - 23 марта 2017 г.

Добавлен пример списков CSV - 31 марта 2017 г.

Визуализация соединений вложенных циклов и понимание их последствий

Этот пост является первым из серии о физических операторах соединения (обязательно ознакомьтесь с часть 2 - объединение слиянием , и часть 3 - хеш-матч присоединяется ).

Посмотрите видео на этой неделе на YouTube

Что нам говорят операторы физического соединения

У каждого есть свой метод чтения плана выполнения при настройке производительности медленного SQL-запроса. Одна из первых вещей, на которую я хотел бы обратить внимание, это то, какие операторы соединения используются:

Эти три маленьких значка могут показаться не самым очевидным местом для начала устранения неполадок с медленным запросом, но с более крупными планами мне особенно нравится начинать с быстрого взгляда на операторы соединения, потому что они позволяют вам сделать много выводов о том, что SQL Server думает о вашем данные.

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

Соединение вложенных циклов

Объединения вложенных циклов работают следующим образом: SQL Server берет первое значение из нашей первой таблицы (наша «внешняя» таблица - по умолчанию SQL Server решает, какая из двух таблиц это будет ), и сравнивает его с каждым значением в нашей второй «внутренней» таблице, чтобы увидеть, совпадают ли они.

После проверки каждого внутреннего значения SQL Server переходит к следующему значению во внешней таблице, и процесс повторяется до тех пор, пока каждое значение из нашей внешней таблицы не будет сравнено с каждым значением в нашей внутренней таблице.

Это описание является наихудшим примером производительности соединения с вложенным циклом. Существует несколько оптимизаций, которые могут сделать соединение более эффективным. Например, если значения соединения внутренней таблицы отсортированы (из-за созданного вами индекса или спула, созданного SQL Server), SQL Server может обрабатывать строки намного быстрее:

В приведенной выше анимации внутренний ввод - это индекс, отсортированный по ключу соединения, что позволяет SQL Server искать непосредственно в нужных ему строках, уменьшая общее количество сравнений, которые необходимо выполнить.

Для более глубокого объяснения внутреннего устройства и оптимизации соединений вложенных циклов я рекомендую прочитать это сообщение Крейга Фридмана а также Справка Хьюго Корнелиса о вложенных циклах .

Что раскрывают соединения вложенных циклов?

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

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

  • Соединения с вложенными циклами сильно загружают процессор; в худшем случае каждую строку нужно сравнивать с каждой другой строкой, и это может занять некоторое время.Это означает, что когда вы видите соединение вложенных циклов, SQL Server наверное считает, что один из двух входов относительно невелик.
  • ... и если один из входов является относительно небольшой, отличный! Если вместо этого вы видите восходящих операторов, которые перемещают большие объемы данных, у вас может возникнуть проблема с оценкой в ​​этой области плана, и вам может потребоваться обновить статистику / добавить индексы / реорганизовать запрос, чтобы SQL Server предоставил более точные оценки (и возможно более подходящее соединение).

  • Вложенные циклы иногда сопровождают поиск RID или ключей. Я всегда проверяю наличие одного из них, потому что они часто оставляют место для улучшения производительности:

    • Если существует поиск RID, обычно достаточно просто добавить кластеризованный индекс в эту базовую таблицу, чтобы добиться некоторой дополнительной производительности.
  • Если существует либо RID, либо поиск по ключу, я всегда проверяю, какие столбцы возвращаются, чтобы увидеть, можно ли вместо этого использовать меньший индекс (путем включения столбца в ключ / столбец существующего индекса) или можно ли выполнить рефакторинг запроса, чтобы не вернуть эти столбцы (например,избавьтесь от SELECT *).

  • Соединения с вложенными циклами не требуют сортировки данных при вводе. Однако производительность может улучшиться с помощью индексированного внутреннего источника данных (см. Анимацию выше), и SQL Server может выбрать более эффективный оператор, если оба ввода отсортированы.

    • По крайней мере, объединения вложенных циклов заставляют меня задуматься о том, чтобы проверить, не отсортированы ли входные данные из-за каких-то восходящих преобразований или из-за отсутствия индексов.

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

.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Theme: Overlay by Kaira Extra Text
Cape Town, South Africa