MERGE hedef tablonun bir alt kümesini


71

MERGETablodan satır eklemek veya silmek için bir ifade kullanmaya çalışıyorum , ancak yalnızca bu satırların bir alt kümesinde hareket etmek istiyorum. Belgeleri MERGEoldukça güçlü ifadelere sahiptir:

Yalnızca eşleşme amacıyla kullanılan hedef tablodaki sütunları belirlemek önemlidir. Yani, hedef tablonun kaynak tablonun karşılık gelen sütunu ile karşılaştırılan sütunları belirtin. ON yan tümcesinde hedef tablodaki satırları filtreleyerek, örneğin AND NOT target_table.column_x = value belirterek sorgu performansını artırmaya çalışmayın. Bunu yapmak beklenmedik ve yanlış sonuçlara neden olabilir.

ama bu benim işimi yapmak için yapmam gereken gibi görünüyor MERGE.

Sahip olduğum veriler, kategorilere (örneğin, hangi kategorilerin hangi kategorilerde yer aldığı gibi) kategoriler için çoktan çoğa katılan standart bir tablodur:

CategoryId   ItemId
==========   ======
1            1
1            2
1            3
2            1
2            3
3            5
3            6
4            5

Yapmam gereken, belirli bir kategorideki tüm satırları yeni bir öğe listesiyle değiştirmek. Bunu yapmak için ilk girişimi şuna benziyor:

MERGE INTO CategoryItem AS TARGET
USING (
  SELECT ItemId FROM SomeExternalDataSource WHERE CategoryId = 2
) AS SOURCE
ON SOURCE.ItemId = TARGET.ItemId AND TARGET.CategoryId = 2
WHEN NOT MATCHED BY TARGET THEN
    INSERT ( CategoryId, ItemId )
    VALUES ( 2, ItemId )
WHEN NOT MATCHED BY SOURCE AND TARGET.CategoryId = 2 THEN
    DELETE ;

Bu , testlerimde çalışıyor gibi görünüyor , ancak tam olarak MSDN'nin yapmamam konusunda beni uyardığı şeyi yapıyorum. Bu, daha sonra beklenmedik problemlerle karşılaşacağım konusunda endişelenmeme neden oluyor, ancak MERGEyalnızca belirli alan değeri olan satırları etkilemem CategoryId = 2ve başka kategorilerdeki satırları yok saymamın başka bir yolunu göremiyorum .

Bu aynı sonucu elde etmek için "daha doğru" bir yol var mı? Ve MSDN'nin beni uyardığı "beklenmeyen veya yanlış sonuçlar" nelerdir?


Evet, belgeler "beklenmeyen ve yanlış sonuçlar" konusunda somut bir örneğe sahipse daha faydalı olacaktır.
AK

3
@AlexKuznetsov Burada bir örnek var .
Paul White

@SQLKiwi bağlantı için teşekkür ederiz - IMO dokümantasyonu orijinal sayfadan yönlendirilmiş olsaydı daha iyi olurdu.
AK

1
@AlexKuznetsov Kabul edildi. Maalesef, 2012 için BOL'nin yeniden düzenlenmesi, diğerlerinin yanı sıra bunu da bozdu. 2008 R2 belgelerinde oldukça iyi bir şekilde bağlandı.
Paul Beyaz

Yanıtlar:


103

MERGEİfadesi karmaşık bir sözdizimi ve daha da karmaşık bir uygulama vardır, ama aslında fikri iki tabloyu birleştirmek değişti (takılı, güncellenmiş veya silinmiş) gereken satırlara filtre uygulayıp, sonra istenen değişiklikleri gerçekleştirmek için. Aşağıdaki örnek veriler göz önüne alındığında:

DECLARE @CategoryItem AS TABLE
(
    CategoryId  integer NOT NULL,
    ItemId      integer NOT NULL,

    PRIMARY KEY (CategoryId, ItemId),
    UNIQUE (ItemId, CategoryId)
);

DECLARE @DataSource AS TABLE
(
    CategoryId  integer NOT NULL,
    ItemId      integer NOT NULL

    PRIMARY KEY (CategoryId, ItemId)
);

INSERT @CategoryItem
    (CategoryId, ItemId)
VALUES
    (1, 1),
    (1, 2),
    (1, 3),
    (2, 1),
    (2, 3),
    (3, 5),
    (3, 6),
    (4, 5);

INSERT @DataSource
    (CategoryId, ItemId)
VALUES
    (2, 2);

Hedef

╔════════════╦════════╗
 CategoryId  ItemId 
╠════════════╬════════╣
          1       1 
          2       1 
          1       2 
          1       3 
          2       3 
          3       5 
          4       5 
          3       6 
╚════════════╩════════╝

Kaynak

╔════════════╦════════╗
 CategoryId  ItemId 
╠════════════╬════════╣
          2       2 
╚════════════╩════════╝

İstenilen sonuç, hedefteki verileri kaynaktan gelen verilerle değiştirmek, ancak bunun içindir CategoryId = 2. MERGEYukarıda verilen açıklamanın ardından, kaynağı ve hedefi yalnızca tuşlara birleştiren bir sorgu yazmalı ve satırları yalnızca WHENyan tümcelerde filtrelemeliyiz :

MERGE INTO @CategoryItem AS TARGET
USING @DataSource AS SOURCE ON 
    SOURCE.ItemId = TARGET.ItemId 
    AND SOURCE.CategoryId = TARGET.CategoryId
WHEN NOT MATCHED BY SOURCE 
    AND TARGET.CategoryId = 2 
    THEN DELETE
WHEN NOT MATCHED BY TARGET 
    AND SOURCE.CategoryId = 2 
    THEN INSERT (CategoryId, ItemId)
        VALUES (CategoryId, ItemId)
OUTPUT 
    $ACTION, 
    ISNULL(INSERTED.CategoryId, DELETED.CategoryId) AS CategoryId,
    ISNULL(INSERTED.ItemId, DELETED.ItemId) AS ItemId
;

Bu, aşağıdaki sonuçları verir:

╔═════════╦════════════╦════════╗
 $ACTION  CategoryId  ItemId 
╠═════════╬════════════╬════════╣
 DELETE            2       1 
 INSERT            2       2 
 DELETE            2       3 
╚═════════╩════════════╩════════╝
╔════════════╦════════╗
 CategoryId  ItemId 
╠════════════╬════════╣
          1       1 
          1       2 
          1       3 
          2       2 
          3       5 
          3       6 
          4       5 
╚════════════╩════════╝

Yürütme planı: Birleştirme planı

Her iki tablonun da tam olarak tarandığına dikkat edin. Bunun yetersiz olduğunu düşünebiliriz, çünkü yalnızca CategoryId = 2hedef tabloda etkilenecek satırlar . Bu, Çevrimiçi Kitaplar'daki uyarıların girdiği yerdir. Hedefte yalnızca gerekli satırlara dokunmak için yanlış yönlendirilmiş bir girişimde bulunma:

MERGE INTO @CategoryItem AS TARGET
USING 
(
    SELECT CategoryId, ItemId
    FROM @DataSource AS ds 
    WHERE CategoryId = 2
) AS SOURCE ON
    SOURCE.ItemId = TARGET.ItemId
    AND TARGET.CategoryId = 2
WHEN NOT MATCHED BY TARGET THEN
    INSERT (CategoryId, ItemId)
    VALUES (CategoryId, ItemId)
WHEN NOT MATCHED BY SOURCE THEN
    DELETE
OUTPUT 
    $ACTION, 
    ISNULL(INSERTED.CategoryId, DELETED.CategoryId) AS CategoryId,
    ISNULL(INSERTED.ItemId, DELETED.ItemId) AS ItemId
;

Maddede yer alan mantık ONbirleştirme işleminin bir parçası olarak uygulanır. Bu durumda, birleşim tam bir dış birleşimdir (nedense bu Çevrimiçi Kitaplar girişine bakın). Kategori 2 kontrolünü hedef satırlara bir dış birleştirmenin parçası olarak uygulamak, sonuçta farklı bir değerin silindiği satırlara neden olur (çünkü kaynakla eşleşmezler):

╔═════════╦════════════╦════════╗
 $ACTION  CategoryId  ItemId 
╠═════════╬════════════╬════════╣
 DELETE            1       1 
 DELETE            1       2 
 DELETE            1       3 
 DELETE            2       1 
 INSERT            2       2 
 DELETE            2       3 
 DELETE            3       5 
 DELETE            3       6 
 DELETE            4       5 
╚═════════╩════════════╩════════╝

╔════════════╦════════╗
 CategoryId  ItemId 
╠════════════╬════════╣
          2       2 
╚════════════╩════════╝

Kök sebep, aynı nedenin, bir dış birleştirme ONyan tümcesinde yan tümce belirtilmişse yaptıklarından farklı davranmalarını WHEREöngörmesidir. MERGESözdizimi (ve belirtilen maddelere bağlı olarak katılmak uygulama) sadece zor bu yüzden olduğunu görmek için yapın.

Çevrimiçi Kitapları rehberlik (genişletilmiş Performansı girişi) kullanılarak ifade edilir doğru semantik sağlayacaktır rehberlik sunmaktadır MERGEkullanıcı mutlaka iyileştirici meşru yeniden düzenlemek olabilir hangi yollar tüm uygulama ayrıntılarını veya hesabı anlamak zorunda kalmadan, sözdizimi yürütme verimliliği nedeniyle şeyler.

Belgeler, erken filtrelemeyi uygulamak için üç potansiyel yol sunar:

Bir filtre durum belirtme WHENmadde , doğru sonuçlar garanti, ancak kesin olarak gerekli olandan (birinci örnekte olduğu gibi), daha fazla satır okumak ve kaynak ve hedef tablolardan işlenir anlamına gelebilir.

Filtreleme koşulunu içeren bir görünüm aracılığıyla güncelleme yapmak da doğru sonuçları garanti eder (çünkü değiştirilen satırların görünüm boyunca güncelleme için erişilebilir olması gerekir) ancak bu özel bir görünüm gerektirir ve bu görünümlerin güncellenmesi için tuhaf koşulları izler.

Yaygın bir tablo ifadesi kullanmak , ONmaddeye öngörü ekleyerek benzer riskler taşır , ancak biraz farklı nedenlerle. Çoğu durumda güvenli olacaktır, ancak bunu doğrulamak için yürütme planının uzman analizini gerektirir (ve kapsamlı pratik testler). Örneğin:

WITH TARGET AS 
(
    SELECT * 
    FROM @CategoryItem
    WHERE CategoryId = 2
)
MERGE INTO TARGET
USING 
(
    SELECT CategoryId, ItemId
    FROM @DataSource
    WHERE CategoryId = 2
) AS SOURCE ON
    SOURCE.ItemId = TARGET.ItemId
    AND SOURCE.CategoryId = TARGET.CategoryId
WHEN NOT MATCHED BY TARGET THEN
    INSERT (CategoryId, ItemId)
    VALUES (CategoryId, ItemId)
WHEN NOT MATCHED BY SOURCE THEN
    DELETE
OUTPUT 
    $ACTION, 
    ISNULL(INSERTED.CategoryId, DELETED.CategoryId) AS CategoryId,
    ISNULL(INSERTED.ItemId, DELETED.ItemId) AS ItemId
;

Bu, daha optimal bir planla doğru sonuçlar (tekrarlanmayan) üretir:

Birleştirme planı 2

Plan sadece kategori 2 için hedef tablodan satır okur. Hedef tablo büyükse bu önemli bir performans değerlendirmesi olabilir, ancak MERGEsözdizimini kullanarak bu yanlış elde etmek çok kolaydır .

Bazen MERGEayrı DML işlemlerini yazmak daha kolaydır . Bu yaklaşım, insanları şaşırtan bir gerçek olan tek bir şeyden daha iyi bir performans sergileyebilirMERGE .

DELETE ci
FROM @CategoryItem AS ci
WHERE ci.CategoryId = 2
AND NOT EXISTS 
(
    SELECT 1 
    FROM @DataSource AS ds 
    WHERE 
        ds.ItemId = ci.ItemId
        AND ds.CategoryId = ci.CategoryId
);

INSERT @CategoryItem
SELECT 
    ds.CategoryId, 
    ds.ItemId
FROM @DataSource AS ds
WHERE
    ds.CategoryId = 2;

Bunun gerçekten eski bir soru olduğunu biliyorum ... ama "Ortak bir tablo ifadesi kullanmak, ON yan tümcesine tahminler ekleme konusunda benzer riskler taşır, ancak biraz farklı nedenlerle". BOL’nin de benzer şekilde belirsiz bir uyarısı olduğunu biliyorum "Bu yöntem, ON tümcesinde ek arama ölçütlerini belirtmeye benzer ve yanlış sonuçlar üretebilir. Bu yöntemi kullanmaktan kaçınmanızı öneririz." CTE yöntemi kullanım davamı çözüyor gibi görünüyor, ancak düşünmediğim bir senaryo olup olmadığını merak ediyorum.
Henry Lee,
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.