एक्सएमएल पैरामीटर का उपयोग करके कई डेटा को अपग्रेड करते समय मर्ज क्वेरी का उपयोग करने से कैसे बचें?


10

मैं मानों की एक सरणी के साथ एक तालिका को अपडेट करने की कोशिश कर रहा हूं। सरणी के प्रत्येक आइटम में SQL सर्वर डेटाबेस में एक तालिका में एक पंक्ति से मेल खाती जानकारी होती है। यदि पंक्ति पहले से ही तालिका में मौजूद है, तो हम उस पंक्ति को दिए गए सरणी में जानकारी के साथ अद्यतन करते हैं। और, हम तालिका में एक नई पंक्ति सम्मिलित करते हैं। मैंने मूल रूप से मुखर वर्णन किया है।

अब, मैं इसे एक एक्सएमएल पैरामीटर लेने वाली संग्रहीत प्रक्रिया में प्राप्त करने का प्रयास कर रहा हूं। कारण मैं XML का उपयोग कर रहा हूं और तालिका-मूल्यवान नहीं है, क्योंकि बाद में, मुझे SQL में कस्टम प्रकार बनाना होगा और इस प्रकार को संग्रहीत कार्यविधि के साथ जोड़ना होगा। अगर मैंने कभी अपनी संग्रहीत प्रक्रिया या सड़क के नीचे मेरे db स्कीमा में कुछ बदला है, तो मुझे संग्रहीत प्रक्रिया और कस्टम प्रकार दोनों को फिर से करना होगा। मैं इस स्थिति से बचना चाहता हूं। इसके अलावा, टीवीपी की एक्सएमएल पर जो श्रेष्ठता है वह मेरी स्थिति के लिए उपयोगी नहीं है क्योंकि, मेरे डेटा सरणी का आकार कभी भी 1000 से अधिक नहीं होगा। इसका मतलब है कि मैं यहां प्रस्तावित समाधान का उपयोग नहीं कर सकता: SQL सर्वर 2008 में XML का उपयोग करके कई रिकॉर्ड कैसे सम्मिलित करें

साथ ही, यहां एक समान चर्चा ( 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 EXTSTS या निम्न में से किसी एक रूप का उपयोग करना है। लेकिन, मैं इसे उप-इष्टतम दक्षता के होने के आधार पर अस्वीकार करता हूं:

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

अगला विकल्प मर्ज विवरण का उपयोग कर रहा था जैसा कि यहां वर्णित है: http://www.databasejournal.com/features/mssql/use-the-merge-statement-to-perform-an-upsert.html । लेकिन, फिर मैंने यहां मर्ज क्वेरी के साथ मुद्दों के बारे में पढ़ा: http://www.mssqltips.com/sqlservertip/3074/use-caution-with-sql-servers-merge-statement/ । इस कारण मैं मर्ज से बचने की कोशिश कर रहा हूं।

तो, अब मेरा सवाल है: SQL Server 2008 में संग्रहीत कार्यविधि में XML पैरामीटर का उपयोग करके किसी भी अन्य विकल्प को प्राप्त करने के लिए कोई अन्य विकल्प या बेहतर तरीका है?

कृपया ध्यान दें कि XML पैरामीटर में डेटा में कुछ रिकॉर्ड हो सकते हैं जो वर्तमान रिकॉर्ड से पुराने होने के कारण UPSERTed नहीं होने चाहिए। ModifiedDateXML और गंतव्य तालिका दोनों में एक फ़ील्ड है जिसकी तुलना यह निर्धारित करने के लिए की जानी चाहिए कि रिकॉर्ड अपडेट किया जाए या छोड़ दिया जाए।


भविष्य में खरीद के बदलाव से बचने की कोशिश करना वास्तव में टीवीपी का उपयोग नहीं करने का एक अच्छा कारण नहीं है। यदि डेटा परिवर्तनों में पारित हो गया है तो आप कोड में किसी भी तरह से बदलाव करेंगे।
मैक्स वर्नोन

1
@MaxVernon मैं पहले से ही एक ही सोच रखता था और लगभग एक जैसी टिप्पणी करता था क्योंकि अकेले टीवीपी से बचने का कोई कारण नहीं है। लेकिन वे थोड़ा और प्रयास करते हैं, और "कभी 1000 से अधिक पंक्तियों" (कभी कभी, या शायद यहां तक ​​कि अक्सर?) के कैविटी के साथ यह एक टॉस-अप का एक सा है। हालाँकि, मुझे लगता है कि मुझे अपने उत्तर को यह बताने के लिए योग्य होना चाहिए कि एक समय में <1000 पंक्तियाँ XML से बहुत अधिक भिन्न नहीं हैं जब तक कि इसे पंक्ति में 10k बार नहीं कहा जाता है। फिर मामूली प्रदर्शन अंतर निश्चित रूप से जोड़ते हैं।
सोलोमन रटज़की

MERGEबर्ट्रेंड के मुद्दों के साथ ज्यादातर किनारे के मामले और अक्षमताएं हैं, स्टॉपर्स नहीं दिखाते हैं - यदि यह एक वास्तविक मेरा क्षेत्र था तो एमएस ने इसे जारी नहीं किया होगा। क्या आप सुनिश्चित हैं कि जिन परिवृत्तियों से आप बच MERGEरहे हैं, वे बचत करने की तुलना में अधिक संभावित त्रुटि पैदा नहीं कर रहे हैं?
जॉन ऑफ ऑल ट्रेड्स

@JonofAllTrades निष्पक्ष होने के लिए, जो मैंने प्रस्तावित किया है वह वास्तव में ऐसा नहीं है कि इसकी तुलना में जटिल है MERGE। MERGE के INSERT और UPDATE चरणों को अभी भी अलग से संसाधित किया जाता है। मेरे दृष्टिकोण में मुख्य अंतर तालिका चर है जो अद्यतन रिकॉर्ड आईडी और DELETE क्वेरी रखता है जो आने वाले डेटा के अस्थायी तालिका से उन रिकॉर्ड को निकालने के लिए उस तालिका चर का उपयोग करता है। और मुझे लगता है कि टेम्परेचर टेबल पर डंप करने के बजाय @ XMLRam.nodes () से प्रत्यक्ष हो सकता है, लेकिन फिर भी, उन अतिरिक्त मामलों में से किसी एक में खुद को खोजने के बारे में चिंता करने की ज़रूरत नहीं है। )।
सोलोमन रटज़की

जवाबों:


12

चाहे स्रोत एक्सएमएल हो या टीवीपी कोई बड़ा फर्क नहीं पड़ता है। समग्र ऑपरेशन अनिवार्य रूप से है:

  1. मौजूदा पंक्तियों को अद्यतन करें
  2. लापता पंक्तियाँ

आप इसे उसी क्रम में करते हैं क्योंकि यदि आप पहले INSERT करते हैं, तो UPDATE को प्राप्त करने के लिए सभी पंक्तियाँ मौजूद हैं और आप उन सभी पंक्तियों के लिए बार-बार काम करेंगे जो अभी डाली गई थीं।

इसके अलावा कि इसे पूरा करने के लिए अलग-अलग तरीके हैं और इसमें से कुछ अतिरिक्त दक्षता को ट्विस्ट करने के विभिन्न तरीके हैं।

चलो नंगे न्यूनतम के साथ शुरू करते हैं। चूंकि एक्सएमएल निकालने से इस ऑपरेशन के अधिक महंगे भागों में से एक होने की संभावना है (यदि सबसे महंगी नहीं है), हम ऐसा नहीं करना चाहते हैं दो बार (जैसा कि हमारे पास प्रदर्शन करने के लिए दो ऑपरेशन हैं)। तो, हम एक अस्थायी तालिका बनाते हैं और डेटा को एक्सएमएल से बाहर निकालते हैं:

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);

वहाँ से हम अद्यतन करते हैं और फिर INSERT:

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
                     );

अब जब हमारा मूल ऑपरेशन डाउन हो गया है, हम अनुकूलन करने के लिए कुछ चीजें कर सकते हैं:

  1. अस्थायी तालिका में डालने के @@ पर कब्जा करें और UPDATE के @@ राशि की तुलना करें। यदि वे समान हैं तो हम INSERT को छोड़ सकते हैं

  2. OUTPUT क्लॉज़ के माध्यम से अपडेट किए गए ID मानों को कैप्चर करें और उन्हें अस्थायी तालिका से हटाएं। फिर INSERT की जरूरत नहीं हैWHERE NOT EXISTS(...)

  3. यदि आने वाले डेटा में ऐसी कोई पंक्तियाँ हैं जिन्हें सिंक नहीं किया जाना चाहिए (यानी न तो डाला गया और न ही अपडेट किया गया), तो उन रिकॉर्ड्स को हटा दिया जाना चाहिए

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;

मैंने इस मॉडल का कई बार Imports / ETL पर उपयोग किया है जो या तो 1000 पंक्तियों में अच्छी तरह से है या शायद एक बैच में 20k के कुल सेट में से 500 पंक्तियों में - एक लाख से अधिक पंक्तियों में। हालाँकि, मैंने अस्थायी तालिका के बाहर अद्यतन पंक्तियों के DELETE के बीच प्रदर्शन अंतर का परीक्षण नहीं किया है, बस [IsUpdate] फ़ील्ड को अद्यतन करने के लिए।


कृपया टीवीपी पर XML का उपयोग करने के निर्णय के बारे में कृपया ध्यान दें, अधिक से अधिक, 1000 पंक्तियों को एक बार में आयात करने के लिए (प्रश्न में उल्लिखित):

यदि इसे यहां और वहां कुछ समय कहा जा रहा है, तो संभवतः टीवीपी में मामूली प्रदर्शन लाभ अतिरिक्त रखरखाव लागत (उपयोगकर्ता-परिभाषित तालिका प्रकार, ऐप कोड परिवर्तन आदि) को बदलने से पहले खरीद को छोड़ने की आवश्यकता के लायक नहीं हो सकता है) । लेकिन अगर आप 4 मिलियन पंक्तियों को आयात कर रहे हैं, तो एक बार में 1000 भेज रहे हैं, जो कि 4000 निष्पादन (और XML की 4 मिलियन पंक्तियां हैं, चाहे वह कैसे भी टूट जाए), और यहां तक ​​कि एक मामूली प्रदर्शन अंतर केवल कुछ ही बार निष्पादित किया जाएगा। ध्यान देने योग्य अंतर तक जोड़ें।

कहा जा रहा है, जैसा कि मैंने बताया है कि विधि का चयन बाहर से नहीं बदलता है। चूंकि टीवीपी केवल पढ़ने के लिए हैं, आप उनसे हटा नहीं पाएंगे। मुझे लगता है कि आप WHERE NOT EXISTS(SELECT * FROM @UpdateIDs ids WHERE ids.IDField = tmp.IDField)साधारण के बजाय बस उस अंतिम चयन (INSERT से बंधा हुआ) में एक जोड़ सकते हैं WHERE IsUpdate = 0। यदि आप @UpdateIDsटेबल चर का उपयोग इस तरीके से करते हैं, तो आप आने वाली पंक्तियों को टेम्प टेबल में डंप न करने के साथ दूर भी कर सकते हैं।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.