СТАТЬЯ
13.06.02

Предыдущая часть

Особенности работы с Microsoft SQL Server в Delphi 5
(Часть 2)

У Анатолий Тенцер
Статья была опубликована в "КомпьютерПресс", №6-2001

INSERT

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

INSERT author_sales EXECUTE get_author_sales

UPDATE и DELETE

Сервер поддерживает расширенный синтаксис

UPDATE MyTable
          SET Name = ‘Иванов’
     FROM MyTable T INNER JOIN AnotherTable A ON T.Id = A.MyTableId
          AND A.SomeField = 20

CREATE TABLE

В версии 7.0 поддерживаются следующие типы данных:

Тип
Синоним
Примечание
BIT    Целое число, равное 0 или 1. В Delphi возможно обращение к полю этого типа при помощи свойства AsBoolean (1 = True, 0 = False)
INT INTEGER 32-битное целое число в диапазоне от – 2 147 483 648 до 2 147 483 647
SMALLINT    16-битное целое число в диапазоне от 32 768 до 32 767
TINYINT    8-битное целое число в диапазоне от 0 до 255
DECIMAL[(P[, S])] NUMERIC, DEC Десятичное число с фиксированной точностью в диапазоне от – 1038 –1 до 1038 – 1. P — максимальное количество знаков в числе S — количество знаков после запятой
MONEY    Денежный тип данных. Целое 64-битное число, младшие 4 разряда которого отведены под дробную часть. Может хранить числа в диапазоне от –922 337 203 685 477,5808 до 922 337 203 685 477,5807. В Delphi соответствует типу данных Currency
SMALLMONEY    Аналогичен Money, но 32-разрядный и ограничен диапазоном от –214 748,3648 до 214 748,3647
FLOAT DOUBLE PRECISION Число с плавающей точкой в диапазоне от –1.79E + 308 до 1.79E + 308
REAL    Число с плавающей точкой в диапазоне от –3.40E + 38 до 3.40E + 38
DATETIME    Дата и время в диапазоне от 1 января 1753 г. до 31 декабря 9999 г. с точностью 3.33 мс
SMALLDATETIME    Дата и время в диапазоне от 1 января 1900 г. до 6 июня 2079 г. с точностью до 1 мин
TIMESTAMP    Уникальный идентификатор в пределах базы данных. Этот тип данных НЕ СОДЕРЖИТ времени и лишь гарантирует, что поле этого типа уникально в рамках базы данных
UNIQUEIDENTIFIER    Глобальный уникальный идентификатор. Статистически уникальное 16-битное значение. Над этим типом данных определены лишь операции =, <>, IS NULL и IS NOT NULL
CHAR[(N)] CHARACTER,
VARYING VARCHAR
Строка фиксированной длины. N — длина строки. Максимальная длина — 8 тыс. символов
VARCHAR[(N)] CHARACTER VARYING(N) Строка переменной длины. N — длина строки. Максимальная длина — 8 тыс. символов
TEXT    Строка произвольной длины (до 2 147 483 647 символов)
NCHAR[(N)] NATIONAL CHARACTER,
NATIONAL CHAR
Строка фиксированной длины в формате Unicode. N — длина строки. Максимальная длина — 4 тыс. символов
NVARCHAR[(N)] NATIONAL CHARACTER VARYING(N),
NATIONAL CHAR VARYING(N)
Строка переменной длины в формате Unicode N — длина строки. Максимальная длина — 4000 символов
NTEXT NATIONAL TEXT Строка произвольной длины (до 1 073 741 823 символов)
BINARY[(N)] VARYING VARBINARY Двоичные данные фиксированной длины (до 8000 байт) N — длина данных
VARBINARY[(N)]    Двоичные данные переменной длины (до 8000 байт) N — длина данных
IMAGE    Двоичные данные произвольной длины (до 2 147 483 647 байт)

В версии SQL 2000 дополнительно предусмотрены следующие типы данных:

Тип
Синоним
Примечание
BIGINT    64-битное целое число
SQL_VARIANT    Может хранить данные произвольного типа

В версии 7.0 поддерживается создание вычисляемых полей

CREATE TABLE MyTable (
  Direction BIT NOT NULL,
  Amount MONEY,
  CASE Direction
  WHEN 1 THEN Amount
  ELSE -Amount
  END AS SignedAmount
)

Выражение не должно содержать подзапросов. В версии Microsoft SQL Server 2000 по вычисляемому полю может быть построен индекс.

Написание триггеров

Триггеры в Microsoft SQL Server срабатывают после обновления и лишь один раз на оператор (а не на каждую обновленную запись). Количество триггеров на таблицу не ограничено. В триггере доступны обновленная таблица и две виртуальные таблицы Inserted и Deleted.

В них находятся:

  

Inserted

Deleted
INSERT Вставленные записи Нет записей
UPDATE Новые версии записей Старые версии записей
DELETE Нет записей Удаленные записи

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

CREATE TRIGGER T1 ON MyTable FOR INSERT, UPDATE
   AS BEGIN
      -- Заносим в поля:
      -- LastUserName – имя пользователя, последним обновившего запись
      -- LastDateTime – дату и время последнего обновления
      UPDATE MyTable
         SET LastUserName = SUSER_NAME(),
            LastDateTime = GETDATE()
         FROM Inserted I INNER JOIN MyTable T ON I.Id = T.Id
END

CREATE TRIGGER T2 ON MyTable FOR DELETE
AS BEGIN

   -- Этот триггер откатывает и снимает всю транзакцию
   -- вызвавшую ошибку
   IF EXISTS (SELECT * FROM Deleted
               WHERE Position = ‘Boss’) BEGIN
      RAISERROR(‘Нельзя удалять начальника’, 16, 1)
      ROLLBACK
   END
END

CREATE TRIGGER T3 ON MyTable FOR DELETE
AS BEGIN
   -- А этот просто не дает удалить запись
   -- позволяя продолжить транзакцию
   IF EXISTS (SELECT * FROM Deleted
               WHERE Position = ‘Programmer’) BEGIN
   INSERT INTO MyTable
      SELECT * FROM Deleted
         WHERE Position = ‘Programmer’
      RAISERROR(‘Программиста удалить тоже не получится’, 16, 1)
   END
END

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

CREATE TABLE Main (
   Id INTEGER PRIMARY KEY
   )

   CREATE TABLE Child (
   Id INTEGER PRIMARY KEY,
   MainId INTEGER NOT NULL REFERENCES Main(Id)
   )

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

CREATE PROCEDURE DeleteFromMain
   @Id INTEGER
   AS BEGIN
      DECLARE @Result INTEGER
      BEGIN TRANSACTION
         SAVE TRANSACTION DeleteFromMain
            DELETE Child WHERE MainId = @Id
            DELETE Main WHERE Id = @Id
         SET @Result = @@ERROR
         IF @Result <> 0
               ROLLBACK TRANSACTION DeleteFromMain
         COMMIT
END

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

Кроме того, в версии Microsoft SQL Server 2000 возможно создание триггеров INSTEAD OF, которые выполняются вместо вызвавшей их операции. При этом ответственность за запись данных в таблице полностью лежит на программисте. Такие триггеры могут быть созданы на представлениях (VIEW), что позволяет сделать обновляемым любое представление, независимо от его сложности.

Пакеты команд

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

BEGIN TRANSACTION
      INSERT One (SomeField) VALUES (:1)
      INSERT Two (AnotherField) VALUES (:2)
      IF @@ERROR = 0
            COMMIT
      ELSE
            ROLLBACK

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

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

Разделителем пакетов команд служит оператор GO.

Обработка ошибок

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

При возникновении ошибки в каком-либо из операторов внутри пакета выполнение пакета продолжается, а функция @@ERROR возвращает код ошибки, который можно обработать.

INSERT MyTable (Name) VALUES (‘Петров’)
      IF @@ERROR != 0
         PRINT ‘Ошибка вставки’.

После успешного оператора @@ERROR возвращает 0, поэтому если значение ошибки может понадобиться впоследствии, то его необходимо сохранить в переменной.

DECLARE @ErrCode INTEGER

   SET @ErrCode = 0

      BEGIN TRANSACTION
      INSERT MyTable (Name) VALUES (‘Иванов’)
      IF @@ERROR != 0
               @ErrCode = @@ERROR

      INSERT MyTable (Name) VALUES (‘Петров’)
      IF @@ERROR != 0
               @ErrCode = @@ERROR

      IF @ErrCode = 0
         COMMIT
      ELSE BEGIN
         ROLLBACK
         RAISERROR(‘Не удалось обновить данные’, 16, 1)
END

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

UPDATE MyTable
            SET Name = ‘Сидоров’
      WHERE Name = ‘Петров’

      IF @@ROWCOUNT = 0
            PRINT ‘Петров не найден’

Продолжение статьи

Дополнительную информацию Вы можете получить в компании Interface Ltd.

Обсудить на форуме Borland
Отправить ссылку на страницу по e-mail


Interface Ltd.
Тel/Fax: +7(095) 105-0049 (многоканальный)
Отправить E-Mail
http://www.interface.ru
Ваши замечания и предложения отправляйте автору
По техническим вопросам обращайтесь к вебмастеру
Документ опубликован: 13.06.02