Я пытаюсь обновить таблицу с массивом значений. Каждый элемент в массиве содержит информацию, которая соответствует строке в таблице в базе данных SQL Server. Если строка уже существует в таблице, мы обновляем эту строку информацией из данного массива. Иначе, мы вставляем новую строку в таблицу. Я в основном описал upsert.
Теперь я пытаюсь добиться этого в хранимой процедуре, которая принимает параметр XML. Причина, по которой я использую XML, а не параметр с табличным значением, заключается в том, что в последнем случае мне придется создавать пользовательский тип в SQL и связывать этот тип с хранимой процедурой. Если бы я когда-нибудь что-то изменил в своей хранимой процедуре или в моей схеме БД, мне пришлось бы повторить как хранимую процедуру, так и пользовательский тип. Я хочу избежать этой ситуации. Кроме того, преимущество, которое TVP имеет над XML, не полезно для моей ситуации, потому что размер моего массива данных никогда не будет превышать 1000. Это означает, что я не могу использовать предложенное здесь решение: Как вставить несколько записей с использованием XML в SQL Server 2008
Кроме того, подобное обсуждение здесь ( UPSERT - есть ли лучшая альтернатива MERGE или @@ rowcount? ) Отличается от того, что я спрашиваю, потому что я пытаюсь добавить несколько строк в таблицу.
Я надеялся, что просто воспользуюсь следующим набором запросов для сохранения значений из xml. Но это не сработает. Этот подход просто должен работать, когда вход представляет собой одну строку.
begin tran
update table with (serializable) set select * from xml_param
where key = @key
if @@rowcount = 0
begin
insert table (key, ...) values (@key,..)
end
commit tran
Следующей альтернативой является использование исчерпывающего IF EXISTS или одного из его вариантов следующей формы. Но я отвергаю это из-за неоптимальной эффективности:
IF (SELECT COUNT ... ) > 0
UPDATE
ELSE
INSERT
Следующим вариантом было использование оператора Merge, как описано здесь: http://www.databasejournal.com/features/mssql/using-the-merge-statement-to-perform-an-upsert.html . Но затем я прочитал о проблемах с запросом Merge здесь: http://www.mssqltips.com/sqlservertip/3074/use-caution-with-sql-servers-merge-statement/ . По этой причине я пытаюсь избежать слияния.
Итак, теперь мой вопрос: есть ли какой-либо другой вариант или лучший способ добиться множественного восстановления с использованием параметра XML в хранимой процедуре SQL Server 2008?
Обратите внимание, что данные в параметре XML могут содержать некоторые записи, которые не должны быть UPSERTed из-за того, что они старше, чем текущая запись. В ModifiedDate
таблице XML и в таблице назначения есть поле, которое необходимо сравнить, чтобы определить, должна ли запись быть обновлена или отброшена.
MERGE
которые указывает Бертран, - это, в основном, крайние случаи и неэффективность, а не пробки - MS не выпустила бы это, если бы это было настоящее минное поле. Вы уверены, что извилины, которые вы проходите, чтобы избежатьMERGE
, не создают больше потенциальных ошибок, чем они сохраняют?MERGE
. Шаги INSERT и UPDATE в MERGE по-прежнему обрабатываются отдельно. Основным отличием в моем подходе является переменная таблицы, которая содержит обновленные идентификаторы записей, и запрос DELETE, который использует эту переменную таблицы для удаления этих записей из временной таблицы входящих данных. И я полагаю, что SOURCE может быть прямым из @ XMLparam.nodes () вместо дампа во временную таблицу, но, тем не менее, это не так уж много лишних вещей, чтобы не беспокоиться о том, что вы окажетесь в одном из этих крайних случаев; ).Ответы:
Является ли источник XML или TVP, не имеет большого значения. Общая операция по существу:
Вы делаете это в таком порядке, потому что если вы сначала ВСТАВИТЕ, то все строки существуют, чтобы получить ОБНОВЛЕНИЕ, и вы будете выполнять повторную работу для всех строк, которые были только что вставлены.
Помимо этого, существуют разные способы достижения этой цели и различные способы настройки дополнительной эффективности.
Начнем с самого минимума. Поскольку извлечение XML, вероятно, будет одной из самых дорогих частей этой операции (если не самой дорогой), мы не хотим делать это дважды (так как нам нужно выполнить две операции). Итак, мы создаем временную таблицу и извлекаем в нее данные из XML:
Оттуда мы делаем ОБНОВЛЕНИЕ, а затем вставляем:
Теперь, когда у нас отключены основные операции, мы можем сделать несколько вещей для оптимизации:
захватить @@ ROWCOUNT вставки в временную таблицу и сравнить с @@ ROWCOUNT ОБНОВЛЕНИЯ. Если они одинаковые, тогда мы можем пропустить ВСТАВКУ
записать значения идентификаторов, обновленные с помощью предложения OUTPUT, и УДАЛИТЬ значения из временной таблицы. Тогда вставка не нужна
WHERE NOT EXISTS(...)
Если во входных данных есть какие-либо строки, которые не следует синхронизировать (т.е. не вставлять и не обновлять), то эти записи должны быть удалены перед выполнением ОБНОВЛЕНИЯ
Я несколько раз использовал эту модель в Imports / ETL, которые либо имеют более 1000 строк, либо, может быть, 500 в пакете из общего набора в 20 КБ - более миллиона строк. Однако я не проверял разницу в производительности между DELETE обновленных строк из временной таблицы и простым обновлением поля [IsUpdate].
Обратите внимание на решение об использовании XML поверх TVP, поскольку в каждый момент времени необходимо импортировать не более 1000 строк (упомянуто в вопросе):
Если это вызывается несколько раз здесь и там, то вполне возможно, что незначительное увеличение производительности в TVP может не стоить дополнительных затрат на обслуживание (необходимость отменить процедуру перед изменением пользовательского типа таблицы, изменений кода приложения и т. Д.) , Но если вы импортируете 4 миллиона строк, отправляя по 1000 за раз, то есть 4000 выполнений (и 4 миллиона строк XML для анализа независимо от того, как он разбит), и даже небольшая разница в производительности при выполнении всего несколько раз приведет к добавьте к заметной разнице.
При этом метод, как я описал, не меняется, за исключением замены SELECT FROM @XmlInputParam на SELECT FROM @TVP. Поскольку TVP доступны только для чтения, вы не сможете удалить их. Я полагаю, вы можете просто добавить
WHERE NOT EXISTS(SELECT * FROM @UpdateIDs ids WHERE ids.IDField = tmp.IDField)
к этому окончательному SELECT (привязанному к INSERT) вместо простогоWHERE IsUpdate = 0
. Если бы вы использовали@UpdateIDs
переменную таблицы таким образом, то вы могли бы даже избежать сброса входящих строк во временную таблицу.источник