Основы Transact-SQL. Введение в MS SQL Server и T-SQL Характеристика языка transact sql

Последнее обновление: 24.06.2017

SQL Server является одной из наиболее популярных систем управления базами данных (СУБД) в мире. Данная СУБД подходит для самых различных проектов: от небольших приложений до больших высоконагруженных проектов.

SQL Server был создан компанией Microsoft. Первая версия вышла в 1987 году. А текущей версией является версия 16, которая вышла в 2016 году и которая будет использоваться в текущем руководстве.

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

SQL Server характеризуется такими особенностями как:

    Производительность. SQL Server работает очень быстро.

    Надежность и безопасность. SQL Server предоставляет шифрование данных.

    Простота. С данной СУБД относительно легко работать и вести администрирование.

Центральным аспектом в MS SQL Server, как и в любой СУБД, является база данных. База данных представляет хранилище данных, организованных определенным способом. Нередко физически база данных представляет файл на жестком диске, хотя такое соответствие необязательно. Для хранения и администрирования баз данных применяются системы управления базами данных (database management system) или СУБД (DBMS). И как раз MS SQL Server является одной из такой СУБД.

Для организации баз данных MS SQL Server использует реляционную модель. Эта модель баз данных была разработана еще в 1970 году Эдгаром Коддом. А на сегодняшний день она фактически является стандартом для организации баз данных.

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

Для идентификации каждой строки в рамках таблицы применяется первичный ключ (primary key). В качестве первичного ключа может выступать один или несколько столбцов. Используя первичный ключ, мы можем ссылаться на определенную строку в таблице. Соответственно две строки не могут иметь один и тот же первичный ключ.

Через ключи одна таблица может быть связана с другой, то есть между двумя таблицами могут быть организованы связи. А сама таблица может быть представлена в виде отношения ("relation").

Для взаимодействия с базой данных применяется язык SQL (Structured Query Language). Клиент (например, внешняя программа) отправляет запрос на языке SQL посредством специального API. СУБД должным образом интерпретирует и выполняет запрос, а затем посылает клиенту результат выполнения.

Изначально язык SQL был разработан в компании IBM для системы баз данных, которая называлась System/R. При этом сам язык назывался SEQUEL (Structured English Query Language). Хотя в итоге ни база данных, ни сам язык не были впоследствии официально опубликованы, по традиции сам термин SQL нередко произносят как "сиквел".

В 1979 году компания Relational Software Inc. разработала первую систему управления баз данных, которая называлась Oracle и которая использовала язык SQL. В связи с успехом данного продукта компания была переименована в Oracle.

Впоследствии стали появляться другие системы баз данных, которые использовали SQL. В итоге в 1989 году Американский Национальный Институт Стандартов (ANSI) кодифицировал язык и опубликовал его первый стандарт. После этого стандарт периодически обновлялся и дополнялся. Последнее его обновление состоялось в 2011 году. Но несмотря на наличие стандарта нередко производители СУБД используют свои собственные реализации языка SQL, которые немного отличаются друг от друга.

Выделяются две разновидности языка SQL: PL-SQL и T-SQL. PL-SQL используется в таких СУБД как Oracle и MySQL. T-SQL (Transact-SQL) применяется в SQL Server. Собственно поэтому в рамках текущего руководства будет рассматриваться именно T-SQL.

В зависимости от задачи, которую выполняет команда T-SQL, он может принадлежать к одному из следующих типов:

    DDL (Data Definition Language / Язык определения данных). К этому типу относятся различные команды, которые создают базу данных, таблицы, индексы, хранимые процедуры и т.д. В общем определяют данные.

    В частности, к этому типу мы можем отнести следующие команды:

    • CREATE : создает объекты базы данных (саму базу даных, таблицы, индексы и т.д.)

      ALTER : изменяет объекты базы данных

      DROP : удаляет объекты базы данных

      TRUNCATE : удаляет все данные из таблиц

    DML (Data Manipulation Language / Язык манипуляции данными). К этому типу относят команды на выбору данных, их обновление, добавление, удаление - в общем все те команды, с помощью которыми мы можем управлять данными.

    К этому типу относятся следующие команды:

    • SELECT : извлекает данные из БД

      UPDATE : обновляет данные

      INSERT : добавляет новые данные

      DELETE : удаляет данные

    DCL (Data Control Language / Язык управления доступа к данным). К этому типу относят команды, которые управляют правами по доступу к данным. В частности, это следующие команды:

    • GRANT : предоставляет права для доступа к данным

      REVOKE : отзывает права на доступ к данным

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

Процедурный язык был создан для расширения возможностей SQL с возможностью хорошо интегрировать с SQL . Добавлены некоторые функции, как локальные переменные и обработка строк/данных. Эти функции делают язык Turing-complete (**).

Они так же используются для записи процедур хранения: Фрагмент кода находящийся на сервере управляет сложными бизнес правилами, которые сложно или невозможно управлять операциями на основе набора (pure set-based operations).

A Turing Complete system means a system in which a program can be written that will find an answer (although with no guarantees regarding runtime or memory).

2- Обзор Transact-SQL

T-SQL организован блоками команд, один блок команд можно вложить в другой блок команд, блок команд начинающийся с BEGIN и заканчивающийся на END , в блоке имеется много конманд, и команды отделены друг от друга точкой запятой(;).

Структура блока:

BEGIN -- Declare variables -- T-SQL Statements END;

3- Начать с SQL Server Management Studio

В данной статье я покажу вам программирование SQL Server , на визуальном инструменте SQL Server Management Studio .

Это иллюстрация SQL Server Management Studio при открытии. Есть некоторые примеры database , когда вы полностью устанавливаете SQLServer .

Или вы можете создать learningsql , маленькую базу данных использующуюся в некоторых статьях по рукодству пользования SQLServer на сайт .

Нажать на правую мышь на database , выбрать "New Query" чтобы открыть окно для этого database .

Вы готовы программировать database с SQL Server .

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

Begin -- Declaring a variable Declare @v_Result Int; -- Declaring a variable with a value of 50 Declare @v_a Int = 50; -- Declaring a variable with a value of 100 Declare @v_b Int = 100; -- Print out Console (For developer). -- Using Cast to convert Int to String -- Using + operator to concatenate 2 string Print "v_a= " + Cast(@v_a as varchar(15)); -- Print out Console Print "v_b= " + Cast(@v_b as varchar(15)); -- Sum Set @v_Result = @v_a + @v_b; -- Print out Console Print "v_Result= " + Cast(@v_Result as varchar(15)); End;

Нажмите на знак чтобы запустить блок команд, и смотрите результат на SQL Server Management Studio :

4- Базовые команды Transact-SQL

4.1- Команда веток If-elsif-else

Синтаксис:

IF THEN Job 1; END IF;

BEGIN -- Declare a variable DECLARE @v_Option integer; DECLARE @v_Action varchar(30); SET @v_Option = 2; IF @v_Option = 1 SET @v_Action = "Run"; ELSE IF @v_Option = 2 BEGIN PRINT "In block else if @v_Option = 2"; SET @v_Action = "Backup"; END; ELSE IF @v_Option = 3 SET @v_Action = "Stop"; ELSE SET @v_Action = "Invalid"; -- Logging PRINT "@v_Action= " + @v_Action; END;

Результаты запуска примера:

4.2- Цикл WHILE

В цикле WHILE вы можете использовать BREAK чтобы выйти из цикла.
Используйте команду CONTINUE чтобы пропустить команды в блоке WHILE и ниже, чтобы начать новый цикл.

< @y WHILE (@x < @y) BEGIN SET @step = @step + 1; -- Every time loop execute, x increases by 1. SET @x = @x + 1; -- Every time loop execute, x decreases by 2. SET @y = @y - 2; PRINT "Step =" + CAST(@step AS varchar(10)); PRINT "@x =" + CAST(@x AS varchar(10)) + " / @y = " + CAST(@y AS varchar(10)); END; -- Write log PRINT "x,y = " + CAST(@x AS varchar(10)) + ", " + CAST(@y AS varchar(10)); END;

Результаты запуска примера:

BEGIN -- Declaring 2 variables x and y DECLARE @x integer = 0; DECLARE @y integer = 10; -- Step DECLARE @step integer = 0; -- While @x < @y WHILE (@x < @y) BEGIN SET @step = @step + 1; -- Every time the loop execute, x increases by 1 SET @x = @x + 1; -- Every time the loop execute, y decreases by 1 SET @y = @y - 2; PRINT "Step =" + CAST(@step AS varchar(10)); PRINT "@x =" + CAST(@x AS varchar(10)) + " / @y = " + CAST(@y AS varchar(10)); -- If @x > 2 then exit the loop -- (Although conditions in the WHILE is still true). IF @x > 2 BREAK; END; -- Write log PRINT "x,y = " + CAST(@x AS varchar(10)) + ", " + CAST(@y AS varchar(10)); END;

Результаты запуска примера:

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

BEGIN -- Declaring 2 variables x and y. DECLARE @x integer = 0; DECLARE @y integer = 10; -- Step DECLARE @step integer = 0; -- While @x < @y WHILE (@x < @y) BEGIN SET @step = @step + 1; -- Every time the loop execute, x increases by 1 SET @x = @x + 1; -- Every time the loop execute, x decreases by 2 SET @y = @y - 2; -- If @x < 3 , then skip the statements below -- And continue new step IF @x < 3 CONTINUE; -- If @x < 3 the statements below "CONTINUE" will not be run. PRINT "Step =" + CAST(@step AS varchar(10)); PRINT "@x =" + CAST(@x AS varchar(10)) + " / @y = " + CAST(@y AS varchar(10)); END; -- Write log PRINT "x,y = " + CAST(@x AS varchar(10)) + ", " + CAST(@y AS varchar(10)); END;

5- Прикрепить данные запроса в переменную

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

Assign_Value_Example

BEGIN -- Declaring a variable @v_Emp_ID DECLARE @v_Emp_ID integer = 1; DECLARE @v_First_Name varchar(30); DECLARE @v_Last_Name varchar(30); DECLARE @v_Dept_ID integer; -- Assgin values to variables SELECT @v_First_Name = emp.First_Name, @v_Last_Name = emp.Last_Name, @v_Dept_Id = emp.Dept_Id FROM Employee Emp WHERE Emp.Emp_ID = @v_Emp_Id; -- Print out values PRINT "@v_First_Name = " + @v_First_Name; PRINT "@v_Last_Name = " + @v_Last_Name; PRINT "@v_Dept_Id = " + CAST(@v_Dept_ID AS varchar(15)); END;

Результаты запуска примера:

6- Особенные виды данных в T-SQL

6.1- Виды данных TABLE (Неявный вид)

T-SQL позволяет вам объявит переменные с видом данных TABLE .

Синтаксис:

Define a variable of type TABLE. -- NOTE: The constraints can also participate in declaration (See example). Declare @v_variable_name TABLE (Column1 DataType1, Column2 DataType2);

Declare a variable of type TABLE. Declare @v_Table TABLE (First_Name Varchar(30), Last_Name Varchar(30), Dept_ID Integer, Salary Float); -- The constraints can also participate in declaration: Declare @v_table TABLE (Product_ID Integer IDENTITY(1,1) PRIMARY KEY, Product_Name DataType2 NOT NULL Default ("Unknown"), Price Money CHECK (Price < 10.0));

Пример: Вставить данные в переменные вида TABLE .

Вы так же можете обновить Update на переменных вида TABLE :

Delete на переменных вида TABLE :

Query данных на переменных вида TABLE :

BEGIN DECLARE @v_Emp_ID integer = 1; -- Declare a variable of type TABLE. DECLARE @v_Table TABLE (First_Name varchar(30), Last_Name varchar(30), Dept_Id integer, Salary float DEFAULT 1000); -- Using INSERT INTO statement to insert data into @v_Table. INSERT INTO @v_Table (First_name, Last_Name, Dept_ID) SELECT emp.First_Name, emp.Last_Name, emp.Dept_Id FROM Employee Emp WHERE Emp.Emp_ID < 4; -- Update @v_Table UPDATE @v_Table SET Salary = Salary + 100 WHERE First_name = "Susan"; -- Query @v_Table. SELECT * FROM @v_Table; END;

Результаты запуска примера:

6.2- Вид данных TABLE (Явный вид)

T-SQL позволяет вам объявить переменные вида TABLE косвенным образом. Название переменной начинается с # .

BEGIN -- Using SELECT INTO statement to insert data into #v_My_Table. SELECT emp.First_Name, emp.Last_Name, emp.Dept_Id, 1000 Salary INTO #v_My_Table FROM Employee Emp WHERE Emp.Emp_ID < 4; -- Update #v_My_Table UPDATE #v_My_Table SET Salary = Salary + 100 WHERE First_name = "Susan"; -- Query #v_My_Table. SELECT * FROM #v_My_Table; END;

Результаты запуска примера:

7- Курсор (Cursor)

7.1- Что такое курсор?

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

7.2- Объявить курсор

Синтаксис

ISO Syntax DECLARE cursor_name [ INSENSITIVE ] [ SCROLL ] CURSOR FOR select_statement [ FOR { READ ONLY | UPDATE [ OF column_name [ ,...n ] ] } ] [;] -- Transact-SQL Extended Syntax DECLARE cursor_name CURSOR [ LOCAL | GLOBAL ] [ FORWARD_ONLY | SCROLL ] [ STATIC | KEYSET | DYNAMIC | FAST_FORWARD ] [ READ_ONLY | SCROLL_LOCKS | OPTIMISTIC ] [ TYPE_WARNING ] FOR select_statement [ FOR UPDATE [ OF column_name [ ,...n ] ] ] [;]

7.3- Пример с курсором

USE learningsql; BEGIN -- -- Declare a variable: DECLARE @v_Emp_ID integer; DECLARE @v_First_Name varchar(50); DECLARE @v_Last_Name varchar(50); DECLARE @v_Count integer; -- Declare a CURSOR. DECLARE My_Cursor CURSOR FOR SELECT Emp.EMP_ID, Emp.FIRST_NAME, Emp.LAST_NAME FROM Employee Emp WHERE Emp.EMP_ID < 3; -- Open Cursor OPEN My_Cursor; -- Move the cursor to the first record. -- And assign column values to variables. FETCH NEXT FROM My_Cursor INTO @v_Emp_ID, @v_First_Name, @v_Last_Name; -- The FETCH statement was successful. (@@FETCH_STATUS = 0) WHILE @@FETCH_STATUS = 0 BEGIN PRINT "First Name = "+ @v_First_Name+" / Last Name = "+ @v_Last_Name; -- Move to the next record. -- And assign column values to the variables FETCH NEXT FROM My_Cursor INTO @v_Emp_ID, @v_First_Name, @v_Last_Name; END -- Close Cursor. CLOSE My_Cursor; DEALLOCATE My_Cursor; END;

Результаты запуска примера:

7.4- Пример использования курсора (Объявление вида переменной)

USE learningsql; BEGIN -- -- Declare a variable: DECLARE @v_Emp_ID integer; DECLARE @v_First_Name varchar(50); DECLARE @v_Last_Name varchar(50); -- Declaring a cursor variable. DECLARE @My_Cursor CURSOR; -- Set Select statement for CURSOR variable. Set @My_Cursor = CURSOR FOR SELECT Emp.EMP_ID, Emp.FIRST_NAME, Emp.LAST_NAME FROM Employee Emp WHERE Emp.EMP_ID < 3; -- Open Cursor OPEN @My_Cursor; -- Move the cursor to the first line. -- And assign column values to the variables. FETCH NEXT FROM @My_Cursor INTO @v_Emp_ID, @v_First_Name, @v_Last_Name; -- The FETCH statement was successful. (@@FETCH_STATUS = 0) WHILE @@FETCH_STATUS = 0 BEGIN PRINT "First Name = "+ @v_First_Name+" / Last Name = "+ @v_Last_Name; -- Move to the next record. -- And assign column values to the variables. FETCH NEXT FROM @My_Cursor INTO @v_Emp_ID, @v_First_Name, @v_Last_Name; END -- Close Cursor. CLOSE @My_Cursor; DEALLOCATE @My_Cursor; END;

The results run the example:

8- Обработка исключения

При программировании T-SQL могут появиться некоторые ошибки в вашем коде, например ошибка при делении на 0. Или ошибка когда вы вставляете запись, но она дублирует значение с первичным ключом, ... Вам нужно исправить эти ситуации.

Смотрите простой пример, как исправить ошибку при делении на 0.

TryCatch_Example

USE learningsql; BEGIN -- -- Declare a variable: DECLARE @v_a float = 20; DECLARE @v_b float = 0; DECLARE @v_c float; DECLARE @v_Error_Number integer; -- Use BEGIN TRY .. END TRY to trap errors. -- If an error occurs in this block -- It will jump to block BEGIN CATCH .. END CATCH. BEGIN TRY --- PRINT "@v_a = " + CAST(@v_a AS varchar(15)); PRINT "@v_b = " + CAST(@v_b AS varchar(15)); -- Divide by 0 error, occurring here. SET @v_c = @v_a / @v_b; -- Below this line will not be running. -- Program jump to block BEGIN CATCH .. END CATCH PRINT "@v_c= " + CAST(@v_c AS varchar(15)); END TRY -- BEGIN CATCH .. END CATCH must be placed immediately behind BEGIN TRY .. END TRY. BEGIN CATCH -- Error Number. SET @v_Error_Number = ERROR_NUMBER(); -- Print out error number: PRINT "Error Number: " + CAST(@v_Error_Number AS varchar(15)); -- Error message: PRINT "Error Message: " + ERROR_MESSAGE(); -- The severity of the error: PRINT "Error Severity: " + CAST(ERROR_SEVERITY() AS varchar(15)); -- Error State: PRINT "Error State: " + CAST(ERROR_STATE() AS varchar(15)); -- Line Number: PRINT "Error Line: " + CAST(ERROR_LINE() AS varchar(15)); -- Name of procedure (or function, or trigger). PRINT "Error Procedure: " + ERROR_PROCEDURE(); END CATCH; END;

Pезультат

Информация ошибки:

Функция Описание
ERROR_NUMBER() Возвращает номер ошибки.
ERROR_MESSAGE() Возвращает полностью сообщение об ошибке. Сообщение включает значения предоставленные параметрам, как длина, название объекта, или время.
ERROR_SEVERITY() Возвращает степень серьезности ошибки.
ERROR_STATE() Возвращает статус ошибки.
ERROR_LINE() Возвращает номер строки кода совершившая ошибку
ERROR_PROCEDURE() Возвращает название stored procedure или trigger , где произошла ошибка.

9- Функция (Function)

Как procedure (процедура), function (функция) являются командами T-SQL , выполняющими определенную роль. В отличии от процедуры, функция возвращает значение сразу при вызове.
Функция так же может быть сохранена в database в виде Store procedure .

Синтаксис создания function (Функции).

Function_name: -- argument: -- mode: INPUT, OUTPUT, default INPUT -- datatype: CREATE FUNCTION ([ @argument1 datatype1 , @argument2 datatype2 , ... ]) RETURNS datatype AS BEGIN -- Declare variables -- Statements -- Return value END;

Function with parameters CREATE FUNCTION Sum_Ab(a Integer, b Integer) RETURNS Integer AS Begin return a + b; End; -- Function without parameters CREATE FUNCTION Get_Current_Datetime() RETURNS Date AS Begin return CURRENT_TIMESTAMP; End;

Отмена функции (Drop function):

Drop Function DROP FUNCTION ; -- For example: DROP FUNCTION My_Function;

Пример создания функции:

Пример создания вашей первой function (функции) с SQL Server :

  1. Создать функцию (Function)
  2. Компилировать данную функцию
  3. Запуск функции

Check the existence of the function -- If it did exist, should drop it in order to create a new one. IF OBJECT_ID(N"dbo.My_Sum", N"FN") IS NOT NULL DROP FUNCTION My_Sum; GO CREATE FUNCTION My_Sum (@p_a float, @p_b float) RETURNS float AS BEGIN -- Declaring a variable type of Float DECLARE @v_C float; -- Assign value for v_C SET @V_C = @p_A + @p_B; -- Return value. RETURN @v_C; END;

Нажмите на знак чтобы компилировать функцию.

Функция, которую вы создали, является простой функцией возвращающая скалярное значение (Scalar-value). Вы можете увидеть как она создается на SQLServer Management Studio :

Можете протестировать функцию, нажав на правую мышь, выберите:

  • Script function as -> SELECT to -> New Query Editor Window

Откроется тестовое окно, вы можете поменять параметры значений:

Поменять параметры значений и нажать на запуск.

Функции могут участвовать в команде SELECT .

SELECT acc.account_id, acc.cust_id, acc.avail_balance, acc.pending_balance, dbo.MY_SUM(acc.avail_balance, acc.pending_balance) balance FROM account acc;

Результаты запроса SQL:

10- Процедура (Procedure)

Группа команд T-SQL , выполняющие определенные функции могут быть собраны в процедуре (procedure), чтобы увеличить возможность выполнения, общего пользования, безопасности, безопасности данных, и полезность в развитии.

Процедуры могут быть сохранены в database как объект в database , готовый для переиспользования. Процедура в данный момент называется Store procedure . Чтобы выполнить Store procedure , сразу после сохранения Store procedure , они компилируются в p-code поэтому могут повысить возможность выполненния.

Процедуры не возвращают значения напрямую как функции (function ). Но могут иметь 0 или более параметров на выход.

Синтаксис создания процедуры:

Procedure_name: -- argument: -- mode: input type: INPUT or OUTPUT, default is INPUT -- datatype: -- Note: The procedure parameters can put in an (), or unnecessary. CREATE PROCEDURE [ argument1 datatype1 , argument2 datatype2 , ... ] AS BEGIN -- Declare variables. -- Statements .. END; -- OR: CREATE PROCEDURE ([ argument1 datatype1 , argument2 datatype2 , ... ]) AS BEGIN -- Declare variables. -- Statements .. END;

Procedure without parameters. CREATE Procedure Do_Something AS Begin -- Declare variables here. Declare @v_a Integer; -- Do something here -- .... End; -- Procedure with parameters -- 1 input parameter and 2 output parameters CREATE Procedure Do_Something (@p_Param1 Varchar(20), @v_Param2 Varchar(50) OUTPUT) AS Begin -- Declare variables Declare @v_a Integer; -- Do something here. -- ... End;

Отменить процедуру (Drop procedure):

Drop Procedure: DROP PROCEDURE

Шаги для выполнения процедуры:

Пример создания процедуры:

Get_Employee_Infos

Drop procedure Get_Employee_Infos if it already exists. -- (To enable recreate) IF OBJECT_ID(N"dbo.Get_Employee_Infos", N"P") IS NOT NULL DROP PROCEDURE Get_Employee_Infos; GO -- Procedure with input parameter: p_Emp_Id -- And output: v_First_Name, v_Last_Name, v_Dept_Id. CREATE PROCEDURE Get_Employee_Infos (@p_Emp_Id integer , @v_First_Name varchar(50) OUTPUT , @v_Last_Name varchar(50) OUTPUT , @v_Dept_Id integer OUTPUT) AS BEGIN -- Use the Print command to print out a string (for programmers). -- Use Cast to convert Integer to string (Varchar). -- Use the + operator to concatenate two strings. PRINT "Parameter @p_Emp_Id = " + CAST(@p_Emp_ID AS varchar(15)); -- -- Query data from the table and assign values to variables. -- SELECT @v_First_Name = Emp.First_Name, @v_Last_Name = Emp.Last_Name, @v_Dept_Id = Emp.Dept_Id FROM Employee Emp WHERE Emp.Emp_Id = @p_Emp_Id; -- -- Log (For developers). -- PRINT "Found Record!"; PRINT " @v_First_Name= " + @v_First_Name; PRINT " @v_Last_Name= " + @v_Last_Name; PRINT " @v_Dept_Id= " + CAST(@v_Dept_Id AS varchar(15)); END;

  • Mark a savepoint in transaction: save transaction name_of_savepoint
  • @@trancount variable: shows the number of transactions is being executed (has not been finished with rollback or commit) in the current connection.
    1. Команда rollback tran + tên_của_savepoint помогает отменить(rollback) транзакцию до соответствующей позиции savepoint (без эффекта завершения транзакции), блокировки (locks) будут разблокированы (unlock) при выполнении манипуляций rollback определенных частей.
    2. При объявлении явной транзакции, нужно удостовериться что она может быть отменена (rollback ) или зафиксирована в явном виде (commit ), если нет, транзакция будет продолжать существовать и занимать ресурсы, предотвращая выпонения других транзакций .
    3. Команда rollback помогает отменить только транзакции в базе данных (insert , delete , update ). Другие команды, например прикрепить, не будут зависеть от команды rollback .

    Transaction_Example1

    BEGIN -- In this example the accounts ACCOUNT_ID = 1, 2 actually exists in DB -- In fact you can write statements to check before the start of transaction -- -- account A (Already guarantees exist in DB) DECLARE @Account_Id_A integer = 1; -- account B (Already guarantees exist in DB) DECLARE @Account_Id_B integer = 2; -- Amount DECLARE @Amount float = 10; -- Bank DECLARE @Execute_Branch_Id integer = 1; -- Write out transaction Count. -- In fact, at this time there is no transaction yet PRINT "@@TranCount = " + CAST(@@Trancount AS varchar(5)); PRINT "Begin transaction"; -- Begin transaction BEGIN TRAN; -- Error trapping. BEGIN TRY -- -- Subtract $10 from account A UPDATE Account SET AVAIL_BALANCE = AVAIL_BALANCE - @Amount WHERE Account_Id = @Account_Id_A; -- -- Insert transaction info into Acc_Transaction table. INSERT INTO ACC_TRANSACTION (TXN_DATE, FUNDS_AVAIL_DATE, TXN_TYPE_CD, ACCOUNT_ID, AMOUNT, EXECUTION_BRANCH_ID) VALUES (CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, "CDT", @Account_Id_A, , @Execute_Branch_Id); -- -- Add $10 to Account B. UPDATE Account SET AVAIL_BALANCE = AVAIL_BALANCE + @Amount WHERE Account_Id = @Account_Id_B; -- -- Insert transaction info into Acc_Transaction table. INSERT INTO ACC_TRANSACTION (TXN_DATE, FUNDS_AVAIL_DATE, TXN_TYPE_CD, ACCOUNT_ID, AMOUNT, EXECUTION_BRANCH_ID) VALUES (CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, "CDT", @Account_Id_B, @Amount, @Execute_Branch_Id); -- Commit transaction IF @@Trancount > 0 PRINT "Commit Transaction"; COMMIT TRAN; END TRY -- If there are errors Catch block will be execute. BEGIN CATCH PRINT "Error: " + ERROR_MESSAGE(); PRINT "Error --> Rollback Transaction"; IF @@Trancount > 0 ROLLBACK TRAN; END CATCH; END;

    Результаты запуска примера:

    12- Trigger

    Статья про Trigger отделена, вы можете посмотреть инструкцию по ссылке:

    • TODO Link!
    • Перевод

    Недостаточно писать код хорошо читаемым: он также должен быстро выполняться.

    Существует три базовых правила для написания такого T-SQL кода, который будет работать хорошо. Они кумулятивные – выполнение всех этих правил окажет положительное влияние на код. Пропуск или изменение любого из них – скорее всего приведет к отрицательному влиянию на производительность вашего кода.

    • Пишите, исходя из структуры хранения данных: если вы храните данные типа datetime, используйте именно datetime, а не varchar или что-нибудь еще.
    • Пишите, исходя из наличия индексов: если на таблице построены индексы, и они должны там быть, пишите код так, чтобы он мог использовать все преимущества, предоставляемые этими индексами. Убедитесь, что кластерный индекс, а для каждой таблицы он может быть только один, используется наиболее эффективным образом.
    • Пишите так, чтобы помочь оптимизатору запросов: оптимизатор запросов – восхитительная часть СУБД. К сожалению, вы можете сильно затруднить ему работу, написав запрос, который ему «тяжело» будет разбирать, например, содержащий вложенные представления – когда одно представление получает данные из другого, а то из третьего – и так далее. Потратьте свое время для того, чтобы понять как работает оптимизатор и писать запросы таким образом, чтобы он мог вам помочь, а не навредить.
    Существует несколько типичных ошибок, которые люди допускают в своем коде на T-SQL – не совершайте их.

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

    В теории избежать этой ошибки очень просто, но вот на практике она довольно часто встречается. Например, вы используете какой-либо тип данных в своей базе данных. Используйте его же в своих параметрах и переменных! Да, я знаю, что SQL Server может неявно приводить один тип данных к другому. Но, когда происходит неявное преобразование типа, или же вы сами приводите тип данных столбца к другому типу, вы выполняете преобразование для всего столбца. Когда вы выполняете это преобразование для столбца в выражении WHERE или же в условии соединения – вы всегда будете видеть сканирование таблицы (table scan). По этому столбцу может быть построен превосходный индекс, но поскольку вы делаете CAST для значений, хранящихся в этом столбце, чтобы сравнить, например дату, хранящуюся в этом столбце, с типом char, который вы использовали в условии, индекс не будет использоваться.

    Не верите? Давайте посмотрим на этот запрос:

    SELECT e.BusinessEntityID, e.NationalIDNumber FROM HumanResources.Employee AS e WHERE e.NationalIDNumber = 112457891;
    Хорошо написан и очень прост. Он должен покрываться индексом, созданным на этой таблице. Но вот план выполнения:

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

    Правильно. Это предупреждение (новое в SQL Server 2012) о том, что выполняется преобразование типов, влияющее на план выполнения. Вкратце – это потому, что в запросе используется неверный тип данных:

    SELECT e.BusinessEntityID, e.NationalIDNumber FROM HumanResources.Employee AS e WHERE e.NationalIDNumber = "112457891";
    И мы получаем вот такой план выполнения запроса:

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

    Используйте правильные типы данных.

    Использование функций при составлении условий соединения и в выражениях WHERE

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

    SELECT a.AddressLine1, a.AddressLine2, a.City, a.StateProvinceID FROM Person.Address AS a WHERE "4444" = LEFT(a.AddressLine1, 4) ;
    Эта функция, LEFT, получает в качестве аргумента столбец, что выливается в этот план выполнения:

    В результате, осуществляется 316 операций чтения, чтобы найти нужные данные, и это занимает 9 миллисекунд (у меня очень быстрые диски). Все потому что ‘4444’ должно сравниться с каждой строкой, возвращенной этой функцией. SQL Server не может даже просто просканировать таблицу, ему необходимо выполнить LEFT для каждой строки. Однако, вы можете сделать нечто вроде этого:

    SELECT a.AddressLine1, a.AddressLine2, a.City, a.StateProvinceID FROM Person.Address AS a WHERE a.AddressLine1 LIKE "4444%" ;
    И вот мы видим совершенно другой план выполнения:

    Для выполнения запроса требуется 3 операции чтения и 0 миллисекунд. Ну или пусть будет 1 миллисекунда, для объективности. Это огромный прирост производительности. А все потому что я использовал такую функцию, которая может быть использована для поиска по индексу(ранее это называлось sargeable – непереводимое, в общем-то, слово: SARG – Search Arguments –able, если функция SARGeable – в нее можно передавать столбец в качестве аргумента и все равно будет использоваться Index Seek, если не SARGeable – увы, всегда будет использоваться Index Scan - прим. переводчика ). В любом случае, не используйте функции в выражениях WHERE или условиях поиска, либо используйте только те, которые могут быть использованы в условиях поиска по индексу.

    Использование Multi-statement UDF

    Multi-statement UDF в русской редакции msdn переводится примерно как «Функции, определяемые пользователем, состоящие из нескольких инструкций, но звучит это, на мой взгляд, как-то странно, поэтому в заголовке и дальше по тексту я старался избегать перевода этого термина - прим. переводчика

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

    Проблема с этими функциями заключается в том, что они строятся на табличных переменных. Табличные переменные – это очень крутая штука, если вы используете их по назначению. У них есть одно явное отличие от временных таблиц – по ним не строится статистика. Это отличие может быть очень полезным, а может … убить вас. Если у вас нет статистики, оптимизатор предполагает, что любой запрос, выполняющийся к табличной переменной или UDF, возвратит всего одну строку. Одну (1) строку. Это хорошо, если они действительно возвращают несколько строк. Но, однажды они возвратят сотни или тысячи строк и вы решите соединить одну UDF с другой… Производительность упадет очень-очень быстро и очень-очень сильно.

    Пример достаточно велик. Вот несколько UDF:

    CREATE FUNCTION dbo.SalesInfo () RETURNS @return_variable TABLE (SalesOrderID INT, OrderDate DATETIME, SalesPersonID INT, PurchaseOrderNumber dbo.OrderNumber, AccountNumber dbo.AccountNumber, ShippingCity NVARCHAR(30)) AS BEGIN; INSERT INTO @return_variable (SalesOrderID, OrderDate, SalesPersonID, PurchaseOrderNumber, AccountNumber, ShippingCity) SELECT soh.SalesOrderID, soh.OrderDate, soh.SalesPersonID, soh.PurchaseOrderNumber, soh.AccountNumber, a.City FROM Sales.SalesOrderHeader AS soh JOIN Person.Address AS a ON soh.ShipToAddressID = a.AddressID ; RETURN ; END ; GO CREATE FUNCTION dbo.SalesDetails () RETURNS @return_variable TABLE (SalesOrderID INT, SalesOrderDetailID INT, OrderQty SMALLINT, UnitPrice MONEY) AS BEGIN; INSERT INTO @return_variable (SalesOrderID, SalesOrderDetailId, OrderQty, UnitPrice) SELECT sod.SalesOrderID, sod.SalesOrderDetailID, sod.OrderQty, sod.UnitPrice FROM Sales.SalesOrderDetail AS sod ; RETURN ; END ; GO CREATE FUNCTION dbo.CombinedSalesInfo () RETURNS @return_variable TABLE (SalesPersonID INT, ShippingCity NVARCHAR(30), OrderDate DATETIME, PurchaseOrderNumber dbo.OrderNumber, AccountNumber dbo.AccountNumber, OrderQty SMALLINT, UnitPrice MONEY) AS BEGIN; INSERT INTO @return_variable (SalesPersonId, ShippingCity, OrderDate, PurchaseOrderNumber, AccountNumber, OrderQty, UnitPrice) SELECT si.SalesPersonID, si.ShippingCity, si.OrderDate, si.PurchaseOrderNumber, si.AccountNumber, sd.OrderQty, sd.UnitPrice FROM dbo.SalesInfo() AS si JOIN dbo.SalesDetails() AS sd ON si.SalesOrderID = sd.SalesOrderID ; RETURN ; END ; GO
    Отличная структура. Она позволяет составлять очень простые запросы. Ну, например, вот:

    SELECT csi.OrderDate, csi.PurchaseOrderNumber, csi.AccountNumber, csi.OrderQty, csi.UnitPrice FROM dbo.CombinedSalesInfo() AS csi WHERE csi.SalesPersonID = 277 AND csi.ShippingCity = "Odessa" ;
    Один, очень простой запрос. Вот его план выполнения, так же очень простой:

    Вот только выполняется он 2,17 секунды, возвращает 148 строк и использует 1456 операций чтения. Обратите внимание, что наша функция имеет нулевую стоимость и только сканирование таблицы, табличной переменной, влияет на стоимость запроса. Хм, правда что ли? Попробуем посмотреть что скрывается за оператором выполнения UDF с нулевой стоимостью. Этот запрос достанет план выполнения функции из кэша:

    SELECT deqp.query_plan, dest.text, SUBSTRING(dest.text, (deqs.statement_start_offset / 2) + 1, (deqs.statement_end_offset - deqs.statement_start_offset) / 2 + 1) AS actualstatement FROM sys.dm_exec_query_stats AS deqs CROSS APPLY sys.dm_exec_query_plan(deqs.plan_handle) AS deqp CROSS APPLY sys.dm_exec_sql_text(deqs.sql_handle) AS dest WHERE deqp.objectid = OBJECT_ID("dbo.CombinedSalesInfo");
    И вот что там происходит на самом деле:

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

    Вот! А теперь мы видим Clustered Index Scan, при котором сканируется большое число строк. Это уже не здорово. Вообще, во всей этой ситуации, UDF кажутся все менее и менее привлекательными. Что если мы, ну, я прямо не знаю, просто попробуем напрямую обратиться к таблицам. Вот так, например:

    SELECT soh.OrderDate, soh.PurchaseOrderNumber, soh.AccountNumber, sod.OrderQty, sod.UnitPrice FROM Sales.SalesOrderHeader AS soh JOIN Sales.SalesOrderDetail AS sod ON soh.SalesOrderID = sod.SalesOrderID JOIN Person.Address AS ba ON soh.BillToAddressID = ba.AddressID JOIN Person.Address AS sa ON soh.ShipToAddressID = sa.AddressID WHERE soh.SalesPersonID = 277 AND sa.City = "Odessa" ;
    Теперь, выполнив этот запрос, мы получим абсолютно те же самые данные, но всего за 310 миллисекунд, а не за 2170. Плюс, SQL Server выполнит всего 911 операций чтения, а не 1456. Честно говоря, очень просто получить проблемы с производительностью, используя UDF

    Включение настройки «Работай быстрее!»: использование «Грязных чтений»

    Возвращаясь в прошлое, к старым компьютерам с 286-ми процессорами на борту, можно вспомнить, что по ряду причин, на передней панели у них располагалась кнопка «Turbo». Если вы случайно «отжимали» ее, то компьютер сразу же начинал безумно тормозить. Таким образом, вы поняли, что некоторые вещи всегда должны быть включены, чтобы обеспечить максимальную пропускную способность. Точно так же, многие люди смотрят на уровень изоляции READ_UNCOMMITTED и хинт NO_LOCK, как на турбо-кнопку для SQL Server. При их использовании, будьте уверены – практически любой запрос и вся система в целом станут быстрее. Это связано с тем, что при чтении не будут накладываться и проверяться никакие блокировки. Меньше блокировок – быстрее результат. Но…

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

    Необоснованное использование хинтов в запросах

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

    Например, множество людей считает, что LOOP JOIN – это лучший способ соединения таблиц. Они приходят к такому выводу, поскольку он наиболее часто встречается в небольших и быстрых запросах. Поэтому они решают принудительно заставить SQL Server использовать именно LOOP JOIN. Это совсем не сложно:

    SELECT s. AS StoreName, p.LastName + ", " + p.FirstName FROM Sales.Store AS s JOIN sales.SalesPerson AS sp ON s.SalesPersonID = sp.BusinessEntityID JOIN HumanResources.Employee AS e ON sp.BusinessEntityID = e.BusinessEntityID JOIN Person.Person AS p ON e.BusinessEntityID = p.BusinessEntityID OPTION (LOOP JOIN);
    Этот запрос выполняется 101 миллисекунду и совершает 4115 операций чтений. В общем-то неплохо, но если мы уберем этот хинт, тот же самый запрос выполнится за 90 миллисекунд и произведет всего 2370 чтений. Чем более загружена будет система, тем более очевидной будет эффективность запроса без использования хинта.

    А вот еще один пример. Люди часто создают индекс на таблице, ожидая, что он решит проблему. Итак, у нас есть запрос:

    SELECT * FROM Purchasing.PurchaseOrderHeader AS poh WHERE poh.PurchaseOrderID * 2 = 3400;
    Проблема опять-таки в том, что когда вы выполняете преобразование столбца, ни один индекс не будет адекватно использоваться. Производительность падает, поскольку выполняется сканирование кластерного индекса. И вот, когда люди видят, что их индекс не используется, они делают вот что:

    SELECT * FROM Purchasing.PurchaseOrderHeader AS poh WITH (INDEX (PK_PurchaseOrderHeader_PurchaseOrderID)) WHERE poh.PurchaseOrderID * 2 = 3400;
    И теперь они получают сканирование выбранного ими, а не кластерного, индекса, так что индекс «используется», правда ведь? Но вот производительность запроса изменяется – теперь вместо 11 операций чтения выполняется 44 (время выполнения у обоих около 0 миллисекунд, поскольку у меня реально быстрые диски). «Использоваться»-то он используется, но совсем не так как предполагалось. Решение этой проблемы заключается в том, чтобы переписать запрос таким образом:

    SELECT * FROM Purchasing.PurchaseOrderHeader poh WHERE PurchaseOrderID = 3400 / 2;
    Теперь количество операций чтения упало до двух, поскольку используется поиск по индексу – индекс используется правильно.

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

    Использование построчной обработки результата выполнения запроса (‘Row by Agonizing Row’ processing)

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

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

    BEGIN TRANSACTION DECLARE @Name NVARCHAR(50) , @Color NVARCHAR(15) , @Weight DECIMAL(8, 2) DECLARE BigUpdate CURSOR FOR SELECT p. ,p.Color ,p. FROM Production.Product AS p ; OPEN BigUpdate ; FETCH NEXT FROM BigUpdate INTO @Name, @Color, @Weight ; WHILE @@FETCH_STATUS = 0 BEGIN IF @Weight < 3 BEGIN UPDATE Production.Product SET Color = "Blue" WHERE CURRENT OF BigUpdate END FETCH NEXT FROM BigUpdate INTO @Name, @Color, @Weight ; END CLOSE BigUpdate ; DEALLOCATE BigUpdate ; SELECT * FROM Production.Product AS p WHERE Color = "Blue" ; ROLLBACK TRANSACTION
    В каждой итерации мы совершаем две операции чтения, а количество продукции, отвечающей нашим критериям, исчисляется сотнями. На моей машине, без нагрузки, время выполнения составляет больше секунды. Это совершенно неприемлемо, тем более что переписать этот запрос очень просто:

    BEGIN TRANSACTION UPDATE Production.Product SET Color = "BLUE" WHERE < 3 ; ROLLBACK TRANSACTION
    Теперь выполняется всего 15 операций чтения и время выполнения составляет всего 1 миллисекунду. Не смейтесь. Люди часто пишут такой код и даже хуже. Курсоры – это такая штука, которую следует избегать и использовать только там, где без них нельзя обойтись – например в задачах обслуживания, где вам надо «пробегать» по разным таблицам или базам данных.

    Необоснованное использование вложенных представлений

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

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

    CREATE VIEW dbo.SalesInfoView AS SELECT soh.SalesOrderID, soh.OrderDate, soh.SalesPersonID, soh.PurchaseOrderNumber, soh.AccountNumber, a.City AS ShippingCity FROM Sales.SalesOrderHeader AS soh JOIN Person.Address AS a ON soh.ShipToAddressID = a.AddressID ; CREATE VIEW dbo.SalesDetailsView AS SELECT sod.SalesOrderID, sod.SalesOrderDetailID, sod.OrderQty, sod.UnitPrice FROM Sales.SalesOrderDetail AS sod ; CREATE VIEW dbo.CombinedSalesInfoView AS SELECT si.SalesPersonID, si.ShippingCity, si.OrderDate, si.PurchaseOrderNumber, si.AccountNumber, sd.OrderQty, sd.UnitPrice FROM dbo.SalesInfoView AS si JOIN dbo.SalesDetailsView AS sd ON si.SalesOrderID = sd.SalesOrderID ;
    А вот здесь автор текста забыл указать запрос, но он приводит его в комментариях (прим. переводчика):
    SELECT csi.OrderDate FROM dbo. CominedSalesInfoView csi WHERE csi.SalesPersonID = 277
    В итоге наш запрос выполняется 155 миллисекунд и использует 965 операций чтения. Вот его план выполнения:

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

    SELECT soh.OrderDate FROM Sales.SalesOrderHeader AS soh WHERE soh.SalesPersonID = 277 ;
    А теперь запрос выполняется за 3 миллисекунды и использует 685 операций чтения – довольно-таки сильно отличается. И вот его план выполнения:

    Как вы можете убедиться, оптимизатор не в силах выкинуть все лишние таблицы в рамках процесса упрощения запроса. Поэтому, в первом плане выполнения есть две лишние операции – Index Scan и Hash Match, собирающий данные воедино. Вы могли бы избавить SQL Server от лишней работы, написав этот запрос без использования представлений. И помните – этот пример очень прост, большинство запросов в реальной жизни намного сложнее и приводят к гораздо большим проблемам производительности.

    В комментариях к этой статье есть небольшой спор, суть которого в том, что Грант (автор статьи), похоже выполнял свои запросы не на стандартной базе AdventureWorks, а на похожей БД, но с несколько иной структурой, из-за чего план выполнения „неоптимального“ запроса, приведенного в последнем разделе, отличается от того, что можно увидеть, проводя эксперимент самостоятельно. Прим. переводчика.
    Если где-то я был излишне косноязычен (а я это могу) и текст труден для понимания, или вы можете мне предложить лучшую формулировку чего бы то ни было - с радостью выслушаю все замечения.

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

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

    Язык программирования T-SQL

    Transact-SQL (T-SQL ) – расширение языка SQL от компании Microsoft и используется в SQL Server для программирования баз данных.

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

    И сегодня мы с Вами рассмотрим ту часть основ языка T-SQL, которая подразумевает написание кода для реализации некого функционала (например, в процедуре или функции ), а не просто какого-то запроса к базе данных.

    Примечание! Код я буду писать в окне запроса среды SQL Server Management Studio, о том, как установить SQL Server и Management Studio в редакции Express мы с Вами разговаривали вот .

    Переменные в T-SQL

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

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

    Локальные переменные объявляются с помощью ключевого слова DECLARE и начинаются со знака @ . Как и во многих языках программирования, переменные в T-SQL должны иметь свой тип данных. Типов данных в SQL сервере достаточно много мы их подробно рассмотрели в справочнике, который я упоминал чуть выше.

    Для присвоения значения переменной можно использовать команды SET или Select .

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

    • @@ROWCOUNT – хранит количество записей, обработанных предыдущей командой;
    • @@ERROR – возвращает код ошибки для последней команды;
    • @@SERVERNAME — имя локального SQL сервера;
    • @@VERSION — номер версии SQL Server;
    • @@IDENTITY — последнее значение счетчика, используемое в операции вставки (insert ).

    Теперь для примера давайте создадим две переменной с типом данных INT, присвоим им значения, первой с помощью команды SET, а второй с помощью команды Select, затем просто выведем на экран эти значения, а также выведем и значение переменной @@VERSION, т.е. узнаем версию SQL сервера.

    DECLARE @TestVar1 INT DECLARE @TestVar2 INT SET @TestVar1 = 1 SELECT @TestVar2 = 2 SELECT @TestVar1 AS [Переменная 1], @TestVar2 AS [Переменная 2], @@VERSION AS [Версия SQL Server]

    Пакеты

    Пакет в T-SQL — это команды или инструкции SQL, которые объединены в одну группу и при этом SQL сервер будет компилировать, и выполнять их как одно целое.

    Для того чтобы дать понять SQL серверу, что Вы передаете пакет команд необходимо указывать ключевое слово GO после всех команд, которые Вы хотите объединить в пакет.

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

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


    Т.е. мы видим, что у нас вышла ошибка, связанная с тем, что переменная @TestVar1 у нас не объявлена.

    Условные конструкции

    Эти конструкции подразумевают ветвление, т.е. в зависимости от выполнения или невыполнения определенных условий инструкции T-SQL будут менять свое направление.

    IF…ELSE

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

    DECLARE @TestVar1 INT DECLARE @TestVar2 VARCHAR(20) SET @TestVar1 = 5 IF @TestVar1 > 0 SET @TestVar2 = "Больше 0" ELSE SET @TestVar2 = "Меньше 0" SELECT @TestVar2 AS [Значение TestVar1]

    IF EXISTS

    Данная конструкция позволяет определить наличие записей определенных условием. Например, мы хотим знать есть ли в таблице те или иные записи и при обнаружении первого совпадения обработка команды прекращается. По сути это то же самое, что и COUNT(*) > 0.

    К примеру, мы хотим проверить есть ли записи со значение id >=0 в таблице test_table, и на основе этого мы будем принимать решение, как действовать дальше


    DECLARE @TestVar VARCHAR(20) IF EXISTS(SELECT * FROM test_table WHERE id > = 0) SET @TestVar = "Записи есть" ELSE SET @TestVar = "Записей нет" SELECT @TestVar AS [Наличие записей]

    CASE

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


    DECLARE @TestVar1 INT DECLARE @TestVar2 VARCHAR(20) SET @TestVar1 = 1 SELECT @TestVar2 = CASE @TestVar1 WHEN 1 THEN "Один" WHEN 2 THEN "Два" ELSE "Неизвестное" END SELECT @TestVar2 AS [Число]

    BEGIN…END

    Эта конструкция необходима для создания блока команд, т.е. например, если бы мы хотели выполнить не одну команду после блока IF, а несколько, то нам бы пришлось писать все команды внутри блока BEGIN…END.

    Давайте модифицируем наш предыдущий пример (про IF EXISTS ) так, чтобы при наличии записей id > = 0 в таблице test_table, мы помимо присвоения значения переменной @TestVar, выполним еще и update, т.е. обновление неких данных в этой же таблице, а также выведем количество строк, которые мы обновили, используя глобальную переменную @@ROWCOUNT.


    DECLARE @TestVar1 VARCHAR(20) DECLARE @TestVar2 INT SET @TestVar2 = 0 IF EXISTS(SELECT * FROM test_table WHERE id > = 0) BEGIN SET @TestVar1 = "Записи есть" UPDATE test_table SET column1 = 5 WHERE id > = 0 SET @TestVar2 = @@ROWCOUNT END ELSE SET @TestVar1 = "Записей нет" SELECT @TestVar1 AS [Наличие записей], @TestVar2 AS [Затронуто строк:]

    Циклы T-SQL

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


    DECLARE @Cnt INT = 1, @result INT = 0, @CountRow INT SELECT @CountRow = COUNT(*) FROM test_table WHILE @Cnt <= @CountRow BEGIN SET @Cnt += 1 SET @result += 1 IF @Cnt = 20 BREAK ELSE CONTINUE END SELECT @result AS [Количество выполнений цикла:]

    В данном примере мы сначала, конечно же, объявляем переменные (Cnt и result мы сразу инициализируем, таким способом можно задавать значения переменных, начиная с SQL Server 2008 ). Затем узнаем, сколько строк в таблице test_table и после этого проверяем, если количество строк в таблице больше или равно нашему счетчику, то входим в наш тестовый цикл. В цикле мы увеличиваем значение счетчика, записываем результат и снова проверяем, если наш счетчик достиг уже значения 20, то мы его принудительно завершим, если нет, то пусть работает дальше, до того как значение счетчика станет больше или равно количеству строк в таблице или до 20, если в таблице строк больше.

    Комментарии

    Они нужны для того, чтобы пояснять, делать заметки в коде, так как если код большой и сложный, то через некоторое время Вы можете просто забыть, почему именно так и для чего Вы написали тот или иной участок кода. В языке T-SQL бывают однострочные (—Текст) и многострочные комментарии (/*Текст*/).


    Команды T-SQL

    GOTO

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


    DECLARE @Cnt INT = 0 Metka: --Устанавливаем метку SET @Cnt += 1 --Прибавляем к переменной 1 if @Cnt < 10 GOTO Metka --Если значение меньше 10, то переходим к метке SELECT @Cnt AS [Значение Cnt =]

    WAITFOR

    Команда может приостановить выполнение кода на время или до наступления заданного времени. Параметр DELAY делает паузу заданной длины, а TIME приостанавливает процесс до указанного времени. Значение параметров задается в формате hh:mi:ss


    DECLARE @TimeStart time, @TimeEnd time SET @TimeStart = CONVERT (time, GETDATE())--Узнаем время WAITFOR DELAY "00:00:05"--Пауза на 5 секунд SET @TimeEnd = CONVERT (time, GETDATE())--Снова узнаем время --Узнаем, сколько прошло времени в секундах SELECT DATEDIFF(ss, @TimeStart, @TimeEnd) AS [Прошло Секунд:]

    RETURN

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


    DECLARE @Cnt INT = 1, @result varchar(15) /*Если значение Cnt меньше 0, то следующие команды не выполнятся, и Вы не увидите колонку [Результат:]*/ IF @Cnt < 0 RETURN SET @result = "Cnt больше 0" SELECT @result AS [Результат:]

    PRINT

    Для передачи служебного сообщения можно использовать команду PRINT . В Management Studio это сообщение отобразится на вкладке «Сообщения» (Messages ).


    DECLARE @Cnt INT = 10, @TestVar varchar(100) IF @Cnt > 0 SET @TestVar = "Значение переменной Cnt больше 0 и равняется " + CAST(@Cnt AS VARCHAR(10)) ElSE SET @TestVar = "Значение переменной Cnt меньше 0 и равняется " + CAST(@Cnt AS VARCHAR(10)) PRINT @TestVar

    Транзакции

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

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

    Узнаем что у нас в таблице (id = IDENTITY) SELECT * FROM test_table --Начинаем транзакцию BEGIN TRAN --Сначала обновим все данные UPDATE test_table SET column1 = column1 - 5 --Затем просто добавим строки с новыми значениями INSERT INTO test_table SELECT column1 FROM test_table --Если ошибка, то все отменяем IF @@error != 0 BEGIN ROLLBACK TRAN RETURN END COMMIT TRAN --Смотрим что получилось SELECT * FROM test_table

    В этом примере, если бы у нас в момент добавления данных (INSERT) возникла ошибка, то UPDATE бы отменился.

    Обработка ошибок — конструкция TRY…CATCH

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


    BEGIN TRY DECLARE @TestVar1 INT = 10, @TestVar2 INT = 0, @result INT SET @result = @TestVar1 / @TestVar2 END TRY BEGIN CATCH SELECT ERROR_NUMBER() AS [Номер ошибки], ERROR_MESSAGE() AS [Описание ошибки] END CATCH

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

    Я думаю для основ этого достаточно, если Вы хотите более подробно изучить все конструкции языка T-SQL, то рекомендую прочитать мою книгу «Путь программиста T-SQL », в которой уже более подробно рассмотрен язык T-SQL, у меня все, удачи!