Использование значений по умолчанию в триггере INSTEAD OF INSERT

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

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

Я попробую привести пример.

Таблица в базе данных имеет 3 поля: a, b и c. c является совершенно новым, устаревшее приложение не знает об этом, поэтому у нас также есть представление с двумя полями, a и b.

Когда устаревшее приложение пытается вставить значение в свое представление, мы используем триггер INSTEAD OF INSERT для поиска значения, которое должно быть в поле c, примерно так:

INSERT INTO realTable(a, b, c) SELECT Inserted.a, Inserted.b, Calculated.C FROM...

(Детали поиска не имеют значения.)

Этот триггер работает хорошо, если поле b не имеет значения по умолчанию. Это потому, что если запрос

INSERT INTO legacyView(a) VALUES (123)

выполняется, то в триггере Inserted.b имеет значение NULL, а не значение b по умолчанию. Теперь у меня проблема, потому что я не могу отличить приведенный выше запрос, который помещает значение по умолчанию в b, и это:

INSERT INTO legacyView(a,b) VALUES (123, NULL)

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

РЕДАКТИРОВАТЬ: добавлено, что я бы не хотел дублировать значения по умолчанию в триггере. Значения по умолчанию уже находятся в схеме базы данных, я надеюсь, что смогу просто использовать их напрямую.


person Paul Smith    schedule 04.02.2010    source источник
comment
Зная, что все ваши поля со значениями по умолчанию не могут быть обнулены, это ОГРОМНО! У меня могло бы быть решение для вас, если бы я знал эту информацию.   -  person ErikE    schedule 09.02.2010
comment
Извините, Emtucifor, в то время, когда я разместил вопрос, я не знал, что у нас нет значений по умолчанию для полей, допускающих значение NULL. Каким было бы ваше решение? Если это хорошо, я все еще могу проголосовать за него и, возможно, изменить принятый ответ!   -  person Paul Smith    schedule 11.02.2010


Ответы (3)


Пол: Я решил это; в итоге. Немного грязное решение и может не всем понравиться, но я новичок в SQL Server и тому подобное:

В триггере Вместо_INSERT:

  1. Скопируйте структуру данных виртуальной таблицы Inserted во временную таблицу:

    SELECT * INTO aTempInserted FROM Inserted WHERE 1=2
    
  2. Создайте представление, чтобы определить ограничения по умолчанию для базовой таблицы представления (из системных таблиц) и используйте их для создания операторов, которые будут дублировать ограничения во временной таблице:

    SELECT  'ALTER TABLE dbo.aTempInserted
                   ADD CONSTRAINT ' + dc.name + 'Temp' +
                   ' DEFAULT(' + dc.definition + ') 
                   FOR ' + c.name AS Cmd, OBJECT_NAME(c.object_id) AS Name
      FROM  sys.default_constraints AS dc
     INNER  JOIN sys.columns AS c
              ON dc.parent_object_id = c.object_id 
             AND dc.parent_column_id = c.column_id
    
  3. Используйте курсор для итерации по полученному набору и выполнения каждого оператора. Это оставляет вам временную таблицу с теми же значениями по умолчанию, что и таблица, в которую нужно вставить.

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

    INSERT INTO aTempInserted DEFAULT VALUES
    
  5. Скопируйте записи из виртуальной таблицы Inserted в базовую таблицу представления (где они были бы вставлены изначально, если бы триггер не предотвратил это), присоединив временную таблицу для предоставления значений по умолчанию. Это требует использования функции COALESCE, чтобы по умолчанию использовались только неуказанные значения:

    INSERT INTO realTable([a], [b], 
                SELECT COALESCE(I.[a], T.[a]),
                       COALESCE(I.[a], T.[b])
                FROM   Inserted      AS I,
                       aTempInserted AS T
    
  6. Удалить временную таблицу

person Community    schedule 08.02.2010
comment
Вот и все — вы все еще не можете определить разницу (на шаге 5) между NULL, потому что значение не указано (поэтому используйте значение по умолчанию), и NULL, потому что было указано значение NULL (поэтому не используйте по умолчанию.Однако это проблема только в том случае, если у вас есть поле, допускающее значение NULL, со значением по умолчанию, чего я не думаю, что мы делаем, поэтому этот ответ достаточно хорош, чтобы его принять. - person Paul Smith; 08.02.2010
comment
Это правда. Это довольно сложный подход, но его можно заставить работать там, где требуется дальнейшее объединение/обработка в операторах вставки. - person ; 08.02.2010

Некоторые идеи:

  • Если устаревшее приложение указывает списки столбцов для INSERT и именует столбцы, а не использует SELECT *, то не можете ли вы просто привязать значение по умолчанию к столбцу c и позволить приложению использовать исходную (измененную) таблицу?

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

  • Как насчет того, чтобы оставить исходную таблицу в покое и добавить дополнительные столбцы в отдельную таблицу, которая имеет связь 1-1 с оригиналом? Затем создайте представление, объединяющее эти две таблицы, и поместите соответствующие триггеры вместо триггеров в это новое представление для обработки всех операций с данными, разделенных между двумя таблицами. Я понимаю, что это влияет на производительность, но это может быть единственный способ решить проблему. Это был бы идеальный случай для материализованного представления, которое замедляло бы обновления, но заставляло бы результат работать точно так же, как таблица для чтения. (Материализированные представления лучше всего подходят для внутренних соединений и не требуют агрегирования. Они также накладывают блокировки схемы на исходные таблицы.)

  • Я столкнулся с аналогичной проблемой, когда не мог отличить преднамеренно значения NULL от пропущенных столбцов в триггере вместо UPDATE в представлении. В конце концов я сделал триггер вместо INSERT для представления, чтобы преобразовать вставки в обновления (если ключ уже существовал, это было обновление, в противном случае это была вставка). Хотя это не поможет вам напрямую, это может подстегнуть некоторые идеи для вас или других.

person ErikE    schedule 04.02.2010
comment
Спасибо за эти комментарии, я не могу указать на что-то конкретное, что помогло, но это заставило нас чувствовать себя намного меньше, как будто мы врезались в кирпичную стену! - person Paul Smith; 08.02.2010

Как насчет использования чего-то подобного???:

insert into realtable
values inserted.a, isnull(inserted.b, DEFAULT), computedC
from inserted
person estebanuri    schedule 15.12.2011
comment
Это вставит значение DEFAULT вместо реального NULL. - person Eugen Konkov; 16.01.2019