Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

НОВЫЙ КУРС БД 2013

.pdf
Скачиваний:
15
Добавлен:
18.05.2015
Размер:
3.49 Mб
Скачать

71

Оператор вывода Print

Прежде чем более подробно рассмотреть применение системных переменных следует отметить очень важный оператор Print. Он служит для вывода информации в отладочное окно СУБД. Синтаксис оператора очень прост:

Print @NameVar

Использование данного оператора позволяет быстрее отлаживать запросы, показывая значение переменных в ходе их выполнения. Следует отметить важную деталь – в запрос в качестве вывода команды Print можно подставлять либо текст, либо число, результат запроса в print выводить нельзя, т.е. следующий запрос приведет к ошибке:

Print (Select * from Mans)

и так тоже нельзя:

Print (Select Max(id) from Mans)

, а вот так можно

Declare @M int

SET @M= (Select Max(id) from Mans)

Print @m

и так тоже можно:

Declare @M int

SET @M= (Select Max(id) from Mans)

Print ‘Значение переменной M = ’ + CONVERT(VARCHAR,@m)

Использование переменной @@Identity

Как было сказано выше переменная @@Identity хранит номер вставляемой строки при выполнении операции вставки при условии, что столбец имеет автоинкремент. Следует отметить важную особенность применения этой переменной. Каждый раз при вставке в любую таблицу, содержащую автоинкрементное поле значение переменной перезаписывается! Т.е. если мы выполняем вставку в несколько таблиц нам нужно сохранить результаты в локальной переменной. Приведем пример использования @@Identity:

/*

так задаются многострочные комментарии в SQL */

-- а так задаются однострочные

DECLARE @I Int

Insert Into Jobs (Name) Values (‘Бухгалтер’) Set @I = @@Identity

--теперь тут последнее вставленное значение для столбца id таблицы Jobs Print ‘Это значение ID для таблицы Jobs ’+ CONVERT(varchar,(2),@I) Insert Into Mans (Name, Job) Values (‘Иван’,@I)

Set @I = @@Identity

-- теперь тут последнее вставленное значение для столбца id таблицы Mans Print ‘Это значение ID для таблицы Mans ’+ CONVERT(varchar,(2),@I)

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

72

Использование переменной @@ROWCOUNT

Данная переменная хранит количество строк, затронутое запросом. Т.е. она хранит информацию, которая представлена в отладочном окне анализатора запросов. Ее можно увидеть на рисунке 2.4.5

Рисунок 2.4.5 Информация о количество строк, затронутых запросом

Если после выполнения запроса на рисунке 2.4.5 обратиться к переменной @@ROWCOUNT, то в ней будет содержаться значение равное 10. Покажем это на примере запроса – предположим, что есть таблица Mans, в которой содержится 13 записей, и мы выполняем такой запрос:

Select * from Mans Declare @RCount int

Set @RCount = @@ROWCOUNT

PRINT 'Строк затронуто ' + CAST(@RCount as Varchar)

Этот запрос вернет 13 строк с данными, а в окне отладки появится следующая информация:

Следует отметить тот факт, что значение этой переменной устанавливают все виды запросов. Поэтому обращаться с ней нужно еще осторожнее, чем с @@Identity.

73

4.2.2 Пакеты и команда GO

Пакетом называется часть сценария, предваренная командой GO. Т.е. если сценарий это набор операторов SQL, то пакеты это части сценария разделенные командами GO. Оператор GO отделяет один пакет от другого и

Должен находится на отдельной строке

Вызывает интерпретацию (или компиляцию в зависимости от версии) операторов пакета, после чего полученный план отправляется на сервер независимо от всех других пакетов

Это не оператор TSQL, а команда распознаваема различными утилитами, вроде анализатора запросов и других утилит MS Management Studio. Некоторые разработки, базирующиеся на TSQL, других компаний не поддерживают команду GO.

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

Синтаксические ошибки в пакете

Ошибки во время исполнения пакета

Выполнить проверку на наличие синтаксических ошибок очень просто, достаточно нажать кнопку флажка на панели анализатора запросов:

Рисунок 2.4.6 Окно анализатора запросов проверка синтаксиса

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

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

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

Рассмотрим такой пример:

CREATE DATABASE MyDB CREATE TABLE Mans

(

id int not null identity primary key, name varchar(25) not null

)

74

На первый взгляд, кажется, все верно и запрос выполнится успешно, однако проблема в том, что мы забыли указать в какой базе нужно создать таблицу Mans и таблица Mans была создана в какой угодно базе (это зависит от настроек системы пользователя), но только не в MyDB. Давайте исправим это:

CREATE DATABASE MyDB USE MyDB

CREATE TABLE Mans

(

id int not null identity primary key, name varchar(25) not null

)

Вроде бы все логично, но почему мы получили сообщение об ошибке? Компилятор пишет о том, что базы MyDB на сервере нет! Как же так? Дело в том, что наш сценарий – это единый пакет, и он не будет отправлен на сервер, пока не будет скомпилирован средой исполнения. Таким образом, все операции в нем будут завершены только после окончания компиляции этого пакета, но среда не может скомпилировать пакет, поскольку база данных MyDB на момент ее использования (т.е. во второй строке) еще не создана. Поэтому СУБД отвергает такой пакет. Чтобы исправить эту ситуацию нужно поместить создание базы в отдельный пакет, а использование в другой пакет, тогда все пройдет без ошибок:

CREATE DATABASE MyDB GO

USE MyDB

CREATE TABLE Mans

(

id int not null identity primary key, name varchar(25) not null

)

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

4.2.3 Динамическое программирование на TSQL

Возможность создания сценариев позволяет многократно их использовать, но что если нам нужно прямо во время выполнения указывать какой код исполнять, т.е. писать программу во время исполнения программы. И такую возможность SQL Server тоже предоставляет посредством специальной команды EXEC или EXECUTE. Оба варианта являются верными, и какой использовать – выбор за пользователем. Синтаксис команды очень прост:

Exec (переменная строкового типа или константная строка)

Например, скрипт:

Exec (‘select * from Mans’)

вызовет на выполнение запрос указный в кавычках, тот же результат даст запрос ниже:

DECLARE @Str Varchar(255)

SET @Str = ‘select * from Mans’

EXEC (@Str)

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

Самая главная особенность это область видимости переменных, нельзя определить переменную во внешнем пакете и использовать внутри запроса передаваемой строки в функцию exec. Чтобы стало понятнее, рассмотрим такой пример:

75

USE TestBD

DECLARE

--эта переменная будет внутри строки запроса, который мы будем передавать в exec

@InsideVar Varchar(255),

--эта переменная будет хранить сам текст запроса в строковом виде

@OutsideVar Varchar(255)

SET @OutsideVar = ‘SELECT @InsideVar = Name From Mans where id = 1’

EXEC (@OutsideVar)

SELECT @InsideVar

При попытке выполнить данный запрос СУБД сразу сообщает об ошибке:

Server: Msg 137, Level 15, State 1, Line 1 Must declare the variable '@InsideVar'. Null

(1 row(s) affected)

СУБД пишет, что должна быть объявлена переменная @InsideVar! Однако, мы уже объявляли ее в коде раннее, в чем же причина? А причина кроется в области видимости переменных. Переменная видна только в том пакете, где она объявлена. Например, код:

USE HS DECLARE

@InsideVar Varchar(255),

@OutsideVar Varchar(255) SET @InsideVar = 'g'

GO

-- здесь будет начинаться новый пакет, и переменной @OutsideVar в нем нет!

SET @OutsideVar = 'g'

Завершится такой ошибкой (заметим, что компилятор указывает на строку 1,т.е. нумерация строк в каждом пакете начинается заново, имейте в виду это при отладке):

Server: Msg 137, Level 15, State 1, Line 1

Must declare the variable '@OutsideVar'.

Итак, делаем вывод, переменная видна только на уровне того пакета, в котором она объявлена, а это значит, что строка запроса внутри функции exec является отдельным пакетом! Следовательно, чтобы использовать переменную @InsideVar в запросе, передаваемом в функцию EXEC, нужно прямо в этом запросе ее и объявить, тогда, она будет видна этому пакету. Так и поступим:

USE TestBD

DECLARE

--уберем объявление этой переменное прямо во внутренний запрос

--@InsideVar Varchar(255),

--эта переменная будет хранить сам текст запроса в строковом виде

@OutsideVar Varchar(255)

SET @OutsideVar = ‘DECLARE @InsideVar Varchar(255)

SELECT @InsideVar = Name From Mans where id = 1 SELECT @InsideVar’

EXEC (@OutsideVar)

На этот раз все прошло успешно. И получили имя человека с идентификатором = 1. На этом краткое знакомство с оператором EXEC заканчивается, но мы вернемся к нему в пункте, посвященном описанию функций и процедур.

76

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

Оператор IF … ELSE

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

IF <Логическое выражение>

[BEGIN] <Операторы SQL> [END]

[ELSE

[BEGIN] <Операторы SQL> [END]]

Под логическим выражением может быть любое выражение, которое приводит к получению истины или лжи. При использовании логических выражений следует помнить о Null значениях. Проверка на Null-значение должна производиться только через конструкцию IS NULL. Ее не следует путать с функцией ISNULL(переменная, значение). Функция ISNULL возвращает значение проверяемой переменной, если переменная не Null и возвращает значение, указанное в скобках в противном случае. Давайте рассмотри пример этой функции:

DECLARE @M int

SET @M = ISNULL(@M,0)

Эта функция проверяет, является ли значение переменной @M Null, если да, то этой переменной присваивается 0, если нет, то она присваивается самой себе. Следует всячески избегать проверок вида:

DECLARE @M int IF @M = Null

--никогда так не делайте!

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

DECLARE @M int IF @M IS NULL @M = 0

Приведем пример полной конструкции IF. Предположим, есть таблица с заказами, мы возвращаем максимальную сумму заказа и если возвращаемое значение больше 10000, то вернем это значение, умноженное на 3%, а если меньше, то вернем умноженное на 1%:

DECLARE @S int

SET @S = SELECT MAX(OrderSum) FROM Orders IF @S >= 10000

SELECT @S*0,03 as ‘Большой заказ’ ELSE

SELECT @S*0,01 as ‘Маленький заказ’

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

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

77

Оператор CASE

Этот оператор является оператором множественного выбора, он эквивалентен оператору switch в языках C, C++, C#, оператору Select Case в Visual Basic. Этот оператор в языке TSQL

имеет две формы:

1 форма. ростой оператор CASE

= CASE <входное выражение>

WHEN <значение входного выражения 1> THEN <результат1> [WHEN <значение входного выражения 2> THEN <результат2>

WHEN <значение входного выражения N> THEN <результатN>]

[ELSE <результатELSE>]

END

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

2 форма. оисковый оператор CASE

= CASE

WHEN <логическое выражение1> THEN <результат1> [WHEN <логическое выражение2> THEN <результат2>

WHEN <логическое выражениеN> THEN <результатN>]

[ELSE <результатELSE>]

END

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

1 форма.

Select Name, ‘Номер прибытия’ = CASE id

WHEN 1 THEN ‘ ервый’

WHEN 2 THEN ‘Второй’

ELSE WHEN ‘ рибыл после первых двух…’

END

FROM Mans

Здесь выбирается 2 столбца, при этом 2-ой столбец является вычисляемым, его значение получается путем сравнения значения столбца id, таблицы mans со всеми значениями указанными в операторе CASE. Если такое совпадение найдено, то выполняется ветка THEN, если такого значения не найдено, то выполняется ветка ELSE.

Например, для такой таблицы Mans:

ID

Name

1

Иван

2

Игорь

4

Сергей

78

Результат будет таким:

 

 

 

Name

 

Номер прибытия

Иван

 

Первый

Игорь

 

Второй

Сергей

 

Прибыл после первых двух…

Давайте рассмотрим 2-ую форму оператора CASE:

 

2 форма.

Select Name, ‘Номер прибытия’ = CASE WHEN id=1 THEN ‘ ервый’

WHEN id=2 THEN ‘Второй’

ELSE WHEN ‘ рибыл после первых двух…’

END

FROM Mans

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

Select Name, ‘Номер прибытия’ = CASE WHEN id*3<15 THEN ‘Он был один из первых’

WHEN Name Like’%А%’ THEN ‘Он не один из первых, зато его зовут А…’ ELSE WHEN ‘ рибыл одним из последних…’

END

FROM Mans

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

Циклы в SQL и оператор WHILE

Циклы в SQL Server аналогичны циклам в языках программирования. Это обычный цикл с предусловием:

WHILE <логическое выражение> BEGIN

<операторы SQL> <BREAK>

<операторы SQL> <CONTINUE>

END

Рассмотрим работу оператора цикла: сначала проверяется условие на вход в этот цикл, если это условие выполнено, то выполняются операторы между словами BEGIN и END. При этом разработчик может в любой момент прервать выполнение цикла и вызвать оператор BREAK. Для того чтобы принудительно перейти в начало цикла используется оператор CONTINUE, при этом снова проверяется логическое выражение. Давайте рассмотрим простой пример цикла:

WHILE 1=1 BEGIN

PRINT ‘Тело цикла выполнится только 1 раз’

BREAK END

79

Данный код сначала проверяет условие 1=1, так как это условие будет всегда истинно, цикл будет бесконечным, однако перед оператором END, стоит оператор BREAK, который осуществит выход из цикла и не даст программе «зависнуть». Таким образом, в результате выполнения данного кода только один раз будет выведена надпись, указанная в команде PRINT.

Оператор WAITFOR

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

WAITFOR DELAY<’time’> | TIME <’time’>

В качестве параметров в этот оператор можно передать 2 различных значения:

1.DELAY, которое указывает сколько ждать в часах, минутах и секундах. Максимальный интервал для ожидания 24 часа. Например, WAITFOR DELAY ’01:00’, означает ждать 1 час.

2.TIME, этот параметр указывает ждать точное время суток, при этом нельзя задавать дату. Поэтому и здесь максимальный интервал ожидания 24 часа. Например, WAITFOR TIME ’01:00’, означает ждать часа ночи.

Обработка ошибок. Блоки Try … Catch

Эти блоки предназначены для обработки исключений, возникающих во время выполнения сценария. Следует уточнить, что эти блоки появились только в версии 2005. Если сказать вкратце, то дело обстоит так: в блок try помещается любой код и если в этом коде возникает ошибка, то управление переходит блоку Catch, в котором можно описать действия по информированию пользователя об ошибке или попытаться устранить эту ошибку. Если ошибки в блоке try не возникает, блок catch не выполняется. Давайте теперь посмотрим, какие ошибки может обрабатывать этот составной оператор. Однако прежде рассмотрим, какие типы ошибок есть:

1.Ошибки на этапе компиляции

2.Ошибки на этапе выполнения программы

3.Ошибки в реализации алгоритмов работы программы

Первый вид ошибок невозможно отловить блоком Try…Catch, поскольку при таких ошибках, запуск сценария невозможен. А вот другие два вида ошибок вполне успешно можно отлавливать. Давайте посмотрим на синтаксис оператора Try:

BEGIN TRY <sql код>

END TRY BEGIN CATCH

<sql код> END CATCH

Давайте рассмотрим пример:

USE TEST_DB

BEGIN TRY

INSERT INTO Mans(name, job) VALUES (‘Неизвестный’, 999) END TRY

BEGIN CATCH

PRINT ‘ роизошла ошибка во время вставки в таблицу Mans’ END CATCH

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

80