Xml parametresini kullanarak birden çok veriyi yükseltirken Birleştirme sorgusunu nasıl kullanırım?


10

Değerler dizisi içeren bir tablo güncelleştirmeye çalışıyorum. Dizideki her öğe, SQL Server veritabanındaki bir tablodaki bir satırla eşleşen bilgiler içerir. Satır zaten tabloda varsa, o satırı verilen dizideki bilgilerle güncelleriz. Aksi takdirde, tabloya yeni bir satır ekliyoruz. Temelde upert tanımladım.

Şimdi, bunu bir XML parametresi alan saklı bir yordamda elde etmeye çalışıyorum. XML kullanıyorum ve tablo değerli param kullanmamın nedeni, ikincisini yaparken, SQL'de özel tür oluşturmak ve bu türü saklı yordamla ilişkilendirmek zorunda olacağım. Saklı yordamda veya db şemasında bir şey değiştirdi, hem saklı yordamı hem de özel türü yeniden yapmak gerekir. Bu durumdan kaçınmak istiyorum. Ayrıca, TVP'nin XML üzerindeki üstünlüğü benim durumum için yararlı değildir, çünkü veri dizisi boyutum asla 1000'i aşmayacaktır. Bu, burada önerilen çözümü kullanamadığım anlamına gelir: SQL Server 2008'de XML kullanarak birden çok kayıt nasıl eklenir

Ayrıca, burada benzer bir tartışma ( UPSERT - MERGE veya @@ rowcount için daha iyi bir alternatif var mı? ) Sormak istediğimden farklı, çünkü bir tabloya birden çok satır eklemeye çalışıyorum .

Ben sadece xml değerlerini yükseltmek için aşağıdaki sorgu kümesini kullanmak umuyordum. Ama bu işe yaramayacak. Bu yaklaşımın yalnızca girdi tek bir satır olduğunda işe yarayacağı düşünülmektedir.

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

Bir sonraki alternatif, kapsamlı bir IF EXISTS veya aşağıdaki formdaki varyasyonlarından birini kullanmaktır. Ancak, bunu optimalin altında bir verimlilik olması nedeniyle reddediyorum:

IF (SELECT COUNT ... ) > 0
    UPDATE
ELSE
    INSERT

Sonraki seçenek, burada açıklandığı gibi Birleştirme ifadesini kullanıyordu: http://www.databasejournal.com/features/mssql/using-the-merge-statement-to-perform-an-upsert.html . Ancak, Birleştirme sorgusu ile ilgili sorunları burada okudum: http://www.mssqltips.com/sqlservertip/3074/use-caution-with-sql-servers-merge-statement/ . Bu nedenle Birleştirmeden kaçınmaya çalışıyorum.

Yani, şimdi sorum şu: SQL Server 2008 saklı yordamda XML parametresini kullanarak birden fazla upsert elde etmek için başka bir seçenek veya daha iyi bir yolu var mı?

XML parametresindeki verilerin, geçerli kayıttan daha eski olması nedeniyle UPSERTed olmaması gereken bazı kayıtlar içerebileceğini lütfen unutmayın. ModifiedDateKaydın güncellenmesi veya atılması gerekip gerekmediğini belirlemek için hem XML hem de hedef tabloda karşılaştırılması gereken bir alan vardır .


Gelecekte proc'ta değişiklik yapmaktan kaçınmaya çalışmak, TVP kullanmamak için iyi bir neden değildir. verilerde değişiklik olursa, kodda her iki şekilde de değişiklik yapabilirsiniz.
Max Vernon

1
@ MaxVernon İlk başta aynı düşünceye sahiptim ve neredeyse çok benzer bir yorum yaptım çünkü tek başına TVP'den kaçınmak için bir neden değil. Ama biraz daha fazla çaba harcıyorlar ve "asla 1000 sıradan fazla" uyarısı ile (bazen, hatta sık sık ima edilir) biraz fırlatma. Ancak, bir kerede <1000 satırın arka arkaya 10 bin kez çağrılmadığı sürece XML'den çok farklı olmadığını belirtmek için yanıtımı nitelendirmeliyim. O zaman küçük performans farklılıkları kesinlikle artıyor.
Solomon Rutzky

MERGEBertrand'ın işaret ettiği konular çoğunlukla tıpaları göstermemek gibi son durum ve verimsizliklerdir - MS, gerçek bir mayın tarlası olsaydı serbest bırakmazdı. Kaçınılması gereken kıvrımların kaydettiklerinden MERGEdaha fazla potansiyel hata yaratmadığından emin misiniz ?
Tüm

@JonofAllTrades Adil olmak gerekirse, teklif ettiğim şey aslında karşılaştırıldığında bu kadar kıvrımlı değil MERGE. MERGE'nin INSERT ve UPDATE adımları hala ayrı olarak işlenir. Yaklaşımımdaki temel fark, güncellenmiş kayıt kimliklerini tutan tablo değişkeni ve bu kayıtları gelen verilerin geçici tablosundan kaldırmak için bu tablo değişkenini kullanan DELETE sorgusudur. Ve KAYNAK geçici bir tabloya damping yerine doğrudan @ XMLparam.nodes () olabilir, ama yine de, bu kendinizi bu uç durumlardan birinde kendinizi bulmak konusunda endişelenmenize gerek yok; ).
Solomon Rutzky

Yanıtlar:


12

Kaynağın XML veya TVP olması büyük bir fark yaratmaz. Genel operasyon esasen:

  1. Mevcut satırları GÜNCELLE
  2. INSERT eksik satır

Bu sırayla yaparsınız, çünkü önce INSERT yaparsanız, UPDATE'i almak için tüm satırlar vardır ve yeni eklenen tüm satırlar için tekrarlanan işler yaparsınız.

Bunun ötesinde, bunu başarmanın farklı yolları ve bazı ek verimlilikleri düzeltmenin çeşitli yolları vardır.

Çıplak minimumla başlayalım. XML'in çıkarılması büyük olasılıkla bu işlemin daha pahalı kısımlarından biri (en pahalı değilse) olduğundan, bunu iki kez yapmak zorunda değiliz (gerçekleştirecek iki işlemimiz olduğu için). Bu nedenle, bir geçici tablo oluştururuz ve verileri XML'den içine çıkarırız:

CREATE TABLE #TempImport
(
  Field1 DataType1,
  Field2 DataType2,
  ...
);

INSERT INTO #TempImport (Field1, Field2, ...)
  SELECT tab.col.value('XQueryForField1', 'DataType') AS [Field1],
         tab.col.value('XQueryForField2', 'DataType') AS [Field2],
         ...
  FROM   @XmlInputParam.nodes('XQuery') tab(col);

Oradan UPDATE ve sonra INSERT yapıyoruz:

UPDATE tab
SET    tab.Field1 = tmp.Field1,
       tab.Field2 = tmp.Field2,
       ...
FROM   [SchemaName].[TableName] tab
INNER JOIN #TempImport tmp
        ON tmp.IDField = tab.IDField
        ... -- more fields if PK or alternate key is composite

INSERT INTO [SchemaName].[TableName]
  (Field1, Field2, ...)
  SELECT tmp.Field1, tmp.Field2, ...
  FROM   #TempImport tmp
  WHERE  NOT EXISTS (
                       SELECT  *
                       FROM    [SchemaName].[TableName] tab
                       WHERE   tab.IDField = tmp.IDField
                       ... -- more fields if PK or alternate key is composite
                     );

Artık temel işlemimiz bittiğine göre, optimize etmek için birkaç şey yapabiliriz:

  1. geçici tabloya @@ ROWCOUNT ekleme yapın ve GÜNCELLEME'nin @@ ROWCOUNT değeriyle karşılaştırın. Eğer aynıysa INSERT'i atlayabiliriz

  2. OUTPUT yantümcesi ile güncellenen kimlik değerlerini yakalayın ve geçici tablodan bu değerleri SİLİN. O zaman INSERTWHERE NOT EXISTS(...)

  3. Gelen verilerde senkronize edilmemesi gereken (yani ne eklenecek ne de güncellenmeyecek) satırlar varsa, bu kayıtlar GÜNCELLEME yapılmadan önce kaldırılmalıdır.

CREATE TABLE #TempImport
(
  Field1 DataType1,
  Field2 DataType2,
  ...
);

DECLARE @ImportRows INT;
DECLARE @UpdatedIDs TABLE ([IDField] INT NOT NULL);

BEGIN TRY

  INSERT INTO #TempImport (Field1, Field2, ...)
    SELECT tab.col.value('XQueryForField1', 'DataType') AS [Field1],
           tab.col.value('XQueryForField2', 'DataType') AS [Field2],
           ...
    FROM   @XmlInputParam.nodes('XQuery') tab(col);

  SET @ImportRows = @@ROWCOUNT;

  IF (@ImportRows = 0)
  BEGIN
    RAISERROR('Seriously?', 16, 1); -- no rows to import
  END;

  -- optional: test to see if it helps or hurts
  -- ALTER TABLE #TempImport
  --   ADD CONSTRAINT [PK_#TempImport]
  --   PRIMARY KEY CLUSTERED (PKField ASC)
  --   WITH FILLFACTOR = 100;


  -- optional: remove any records that should not be synced
  DELETE tmp
  FROM   #TempImport tmp
  INNER JOIN [SchemaName].[TableName] tab
          ON tab.IDField = tmp.IDField
          ... -- more fields if PK or alternate key is composite
  WHERE  tmp.ModifiedDate < tab.ModifiedDate;

  BEGIN TRAN;

  UPDATE tab
  SET    tab.Field1 = tmp.Field1,
         tab.Field2 = tmp.Field2,
         ...
  OUTPUT INSERTED.IDField
  INTO   @UpdatedIDs ([IDField]) -- capture IDs that are updated
  FROM   [SchemaName].[TableName] tab
  INNER JOIN #TempImport tmp
          ON tmp.IDField = tab.IDField
          ... -- more fields if PK or alternate key is composite

  IF (@@ROWCOUNT < @ImportRows) -- if all rows were updates then skip, else insert remaining
  BEGIN
    -- get rid of rows that were updates, leaving only the ones to insert
    DELETE tmp
    FROM   #TempImport tmp
    INNER JOIN @UpdatedIDs del
            ON del.[IDField] = tmp.[IDField];

    -- OR, rather than the DELETE, maybe add a column to #TempImport for:
    -- [IsUpdate] BIT NOT NULL DEFAULT (0)
    -- Then UPDATE #TempImport SET [IsUpdate] = 1 JOIN @UpdatedIDs ON [IDField]
    -- Then, in below INSERT, add:  WHERE [IsUpdate] = 0

    INSERT INTO [SchemaName].[TableName]
      (Field1, Field2, ...)
      SELECT tmp.Field1, tmp.Field2, ...
      FROM   #TempImport tmp
  END;

  COMMIT TRAN;

END TRY
BEGIN CATCH
  IF (@@TRANCOUNT > 0)
  BEGIN
    ROLLBACK;
  END;

  -- THROW; -- if using SQL 2012 or newer, use this and remove the following 3 lines
  DECLARE @ErrorMessage NVARCHAR(4000) = ERROR_MESSAGE();
  RAISERROR(@ErrorMessage, 16, 1);
  RETURN;
END CATCH;

Bu modeli, 1000 satırdan fazla veya belki de 500'den fazla toplam 20k'lık bir kümeden bir milyon satırın üzerinde olan İthalat / ETL'lerde birkaç kez kullandım. Ancak, temp tablosundan güncellenen satırların DELETE ile sadece [IsUpdate] alanının güncellenmesi arasındaki performans farkını test etmedim.


Bir kerede içe aktarılacak en fazla 1000 satır olduğu için (soruda belirtilmiştir) TVP üzerinden XML kullanma kararına dikkat edin:

Buraya orada birkaç kez çağrılırsa, muhtemelen TVP'deki küçük performans kazancı ek bakım maliyetine değmeyebilir (Kullanıcı Tanımlı Tablo Türünü, uygulama kodu değişikliklerini vb. Değiştirmeden önce proc'u düşürmeniz gerekir) . Ancak 4 milyon satır içe aktarıyorsanız, bir seferde 1000 gönderiyorsanız, bu 4000 yürütme (ve nasıl ayrılırsa ayrıştırılacak 4 milyon XML satırı) ve hatta yalnızca birkaç kez yürütüldüğünde küçük bir performans farkı olacaktır. fark edilir bir fark yaratır.

Söylediğim gibi, açıkladığım yöntem, @XmlInputParam SELECT FROM @TVP SELECT FROM yerine değiştirmek dışında değişmez. TVP'ler salt okunur olduğundan, onlardan silemezsiniz. Sanırım basit bir WHERE NOT EXISTS(SELECT * FROM @UpdateIDs ids WHERE ids.IDField = tmp.IDField)yerine bu son SELECT (INSERT bağlı) bir ekleyebilirsiniz WHERE IsUpdate = 0. Kullanmak için olsaydı @UpdateIDsbu şekilde tablo değişkeni, o zaman bile geçici tabloya gelen satırları damping değil kurtulabilirsin.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.