2016-01-29 4 views
9

मान लीजिए हमने:एसक्यूएल सर्वर - केवल .modify का उपयोग कर दो एक्सएमएल विलय()

:

CREATE TABLE #Users(id INT PRIMARY KEY, name VARCHAR(100), suggestions XML); 

INSERT INTO #Users(id, name, suggestions) 
SELECT 1, 'Bob', N'<Products> 
        <Product id="1" score="1"/> 
        <Product id="2" score="5"/> 
        <Product id="3" score="4"/> 
        </Products>' 
UNION ALL 
SELECT 2, 'Jimmy', N'<Products> 
         <Product id="6" score="3"/> 
        </Products>'; 

DECLARE @userId INT = 1, 
     @suggestions XML = N'<Products> 
           <Product id="2" score="5"/> 
           <Product id="3" score="2"/> 
           <Product id="7" score="1" /> 
          </Products>'; 

Playground

अब मैं id विशेषता के आधार पर 2 XMLs मर्ज करना चाहते हैं

आईडी = 1:

<Products> 
    <Product id="1" score="1"/> -- nothing changed (but not exists in @suggestions) 
    <Product id="2" score="5"/> -- nothing changed (but exists in @suggestions) 
    <Product id="3" score="2"/> -- update score to 2 
    <Product id="7" score="1"/> -- insert new element 
</Products> 
के साथ उपयोगकर्ता के लिए अंतिम परिणाम

कृपया ध्यान दें कि यह 2 एक्सएमएल के संयोजन नहीं है लेकिन "अपरर्ट" ऑपरेशन है।

टिप्पणी:

  • मुझे पता है कि स्कीमा इस तरह का डेटाबेस सामान्यीकरण उल्लंघन करती है और सामान्य यह जाने का रास्ता है (लेकिन इस मामले में)
  • मैं समाधान है कि व्युत्पन्न तालिकाओं का उपयोग पता है, .nodes() और .value() कार्यों दोनों XML को पार्स, तो विलय और वापस लिखें

मैं तलाश कर रहा हूँ करने के लिए पहलेहैअभिव्यक्ति जो इसे एक कथन में विलय करेगी (कोई व्युत्पन्न तालिकाओं/गतिशील-एसक्यूएल *):

* यदि पूरी तरह से आवश्यक है, तो डायनामिक एसक्यूएल का उपयोग किया जा सकता है, लेकिन मैं इससे बचना चाहता हूं।

UPDATE #Users 
SET suggestions.modify(... sql:variable("@suggestions") ...); --changes only here 
WHERE id = @userId; 


/* replace ... for ... where ... with sql:variable */ 

उत्तर

4

थोड़ी देर के चारों ओर की कोशिश के बाद मुझे लगता है कि यह संभव नहीं है ...

यहाँ समान सवाल ही नहीं है: XQuery adding or replacing attribute in single SQL update command

.modify(insert Expression1 ...) एक एक्सएमएल के भीतर डेटा प्राप्त करने के लिए अनुमति नहीं है @sql:variable() या sql:column()

के माध्यम से पारित किया गया: https://msdn.microsoft.com/en-us/library/ms175466.aspx अभिव्यक्ति 1 -> "निरंतर एक्सएमएल या खड़े अकेले एसक्यूएल: स्तंभ/एसक्यूएल: चर या XQuery (समान उदाहरण के लिए)

DECLARE @xml1 XML= --the existing XML 
'<Products> 
    <Product id="1" score="1" /> 
    <Product id="2" score="5" /> 
    <Product id="3" score="4" /> 
</Products>'; 

DECLARE @xml2 XML= --the XML with new or changed data 
'<Products> 
    <Product id="2" score="5" /> 
    <Product id="3" score="2" /> 
    <Product id="7" score="1" /> 
</Products>'; 

SET @xml1.modify('insert sql:variable("@xml2") as first into /Products[1]'); 

SELECT @xml1; 

/* The full node is inserted! 
Without any kind of preparation there is NO CHANCE to get the inner nodes only 

<Products> 
    <Products> 
    <Product id="2" score="5" /> 
    <Product id="3" score="2" /> 
    <Product id="7" score="1" /> 
    </Products> 
    <Product id="1" score="1" /> 
    <Product id="2" score="5" /> 
    <Product id="3" score="4" /> 
</Products> 
*/ 

आप इस तरह के रूप में दूसरा एक्सएमएल घोषित हो सकता है:

DECLARE @xml2 XML= --the XML with new or changed data 
'<Product id="2" score="5" /> 
<Product id="3" score="2" /> 
<Product id="7" score="1" />'; 

लेकिन तुलना में आप का उपयोग करने के लिए कोई मौका होगा XQuery के रूप में आईडी के मूल्य

SET @xml1.modify('insert sql:variable("@xml2") as first into /Products[**How should one filter here?**]'); 

और अंत में कम से कम मुझे लगता है कि .modify() में से एक कॉल के भीतर दो अलग-अलग XML_DML कथनों को संयोजित करने के लिए कोई संभावना नहीं है फ़िल्टर करें।

मेरे पास एकमात्र विचार था, लेकिन यह काम नहीं करता है।नहीं, यह संभव नहीं है ...

समाधान मैं यहाँ https://stackoverflow.com/a/35060150/5089204 प्रदान की: यदि प्रयोग करने योग्य हो रहा है केवल के भीतर एक अभिव्यक्ति है, लेकिन नहीं दो दो निष्पादन रास्तों

SET @xml1.modify('if (1=1) then 
        insert sql:variable("@xml2") as first into /Products[1] 
        else 
        replace value of /Products[1]/Product[@id=1][1]/@score with 100'); 

तो मेरी निष्कर्ष के बीच अंतर दूसरे खंड में ("यदि आप दो पुस्तकें-संरचनाओं को 'विलय करना चाहते हैं") इसे हल करने का मेरा तरीका होगा।

+0

मैं आवश्यकताओं आराम कर सकते हैं, इतने पर जाना और गतिशील-एसक्यूएल दिखाएं :) वैसे भी मैं 2 दिनों के बाद उपहार प्रदान करूंगा और इसे कुछ दिनों के बाद स्वीकार करूँगा। – lad2025

+0

@ lad2025 गतिशील रूप से इसे दो बार संशोधित करने की आवश्यकता होगी()। और विज्ञापन-प्रसार एसक्यूएल के लिए कोई मौका नहीं है। वास्तव में कोई विकल्प नहीं ... – Shnugo

0

ऐसा कुछ करने का प्रयास करें, यह समझना आसान है, लेकिन यह लंबा है।

अगर आपको कोई समस्या है तो मुझे बताएं।

declare @Users TABLE(id INT PRIMARY KEY, name VARCHAR(100), suggestions XML); 

INSERT INTO @Users(id, name, suggestions) 
SELECT 1, 'Bob', N'<Products> 
        <Product id="1" score="1"/> 
        <Product id="2" score="5"/> 
        <Product id="3" score="4"/> 
        </Products>' 
UNION ALL 
SELECT 2, 'Jimmy', N'<Products> 
         <Product id="6" score="3"/> 
        </Products>'; 

DECLARE @userId INT = 1, 
     @suggestions XML = N'<Products> 
           <Product id="2" score="5"/> 
           <Product id="3" score="2"/> 
           <Product id="7" score="1" /> 
          </Products>'; 

declare @Users1 TABLE(userid INT , productid int,score int); 
insert into @Users1 
SELECT id userid, 
    t.c.value('(@id[1])', 'VARCHAR(50)') AS Productid, 
    t.c.value('(@score[1])', 'VARCHAR(50)') AS score 

FROM @Users yt 
    cross APPLY yt.suggestions.nodes('Products/Product') t(c) 
--select * from @Users1 
;With CTE1 as 
(
    select @userId userid, 
    t.c.value('(@id[1])', 'VARCHAR(50)') AS Productid, 
    t.c.value('(@score[1])', 'VARCHAR(50)') AS score 
    from @suggestions.nodes('Products/Product') t(c) 
) 

Merge @users1 as trg 
using cte1 as src 
on trg.userid=src.userid and trg.productid=src.productid 
when not matched then 
insert (userid,productid,score) values(src.userid,src.productid,src.score); 

select distinct a.userid 
,(select b.productid as '@Productid',b.score as '@Score' 
from @users1 b where a.userid=b.userid 
for xml path('Product'),root('Products')) 
from @users1 a 
+0

उत्तर के लिए धन्यवाद लेकिन मुझे जवाब पता है कि '.nodes() /। मान()' ** (टिप्पणियां देखें) ** का उपयोग करें। इस प्रश्न का पूरा बिंदु केवल '.modify() 'का उपयोग करके परिणाम प्राप्त करना है। और ** [डेमो] (https://data.stackexchange.com/stackoverflow/query/430681) ** की जांच करें। 1) आपका उत्तर 'सुझाव' कॉलम 2 अपडेट नहीं करता है) यह 'उत्पाद आईडी = 3' सही तरीके से संभाल नहीं करता है (देखें कि इसमें स्कोर 4 नहीं 2 है)। – lad2025

0

यह एक सुरुचिपूर्ण एक-लाइनर नहीं है जिसे आप उम्मीद कर रहे थे। फिर भी, यह काम करता है, इसलिए मैं साझा करूंगा। (और मैं वहाँ बेहतर बनाया जा यकीन है।)

IF OBJECT_ID('tempdb..#Users') IS NOT NULL DROP TABLE #Users 

    CREATE TABLE #Users(id INT PRIMARY KEY, name VARCHAR(100), suggestions XML); 

    INSERT INTO #Users(id, name, suggestions) 
    SELECT 1, 'Bob', N'<Products> 
       <Product id="1" score="1"/> 
       <Product id="2" score="5"/> 
       <Product id="3" score="4"/> 
       </Products>' 
    UNION ALL 
    SELECT 2, 'Jimmy', N'<Products> 
          <Product id="6" score="3"/> 
         </Products>'; 

    [email protected] is slightly different (note the "<Products>" parent element is gone). 
    DECLARE @userId INT = 1, 
      @suggestions XML = N' <Product id="2" score="5"/> 
            <Product id="3" score="2"/> 
            <Product id="7" score="1" />'; 

    --Capture the original suggestions so we can compare later 
    DECLARE @OldXML As XML = (SELECT suggestions from #Users where id = @userId) 

    UPDATE #Users --this inserts the new suggestions (we delete old ones as needed, below). 
    SET suggestions.modify('insert sql:variable("@suggestions") as first into /Products[1]') 
    WHERE id = @userId; 

    WHILE (@OldXML.exist('/Products/Product') = 1) 
     BEGIN --For each of the user's original Products, delete the second instance of it. 
     DECLARE @prodId int = (SELECT @OldXML.value('(/Products/Product/@id)[1]', 'nvarchar(max)') FROM #Users where id = @userId) 
     UPDATE #Users SET suggestions.modify('delete /Products/Product[@id= sql:variable("@prodId")][2]') WHERE id = @userId 
     SET @OldXML.modify('delete /Products/Product[@id=sql:variable("@prodId")][1]') 
     END 

    SELECT * FROM #Users where id = @userId 
    DROP TABLE #Users 
+1

इस प्रश्न का कारण यह था: http://stackoverflow.com/q/35046529/5089204। ओपी ने स्वयं स्वीकार किए गए उत्तर को लिखा था। उसके उत्तर पर और मेरे उत्तर पर भी और टिप्पणियों पर एक नज़र डालें ... इस उत्तर में दिखाए गए एक से बेहतर तरीके हैं (बीटीडब्ल्यू: ** कोई लूप नहीं! * अगर टालने योग्य है)। यहां सवाल यह है कि एक कॉल में एक सशर्त यूपीएसईआरटी करने के लिए '.odifyify') को कॉल करें। जैसा कि मैंने बताया है, मुझे लगता है कि यह संभव नहीं है ... – Shnugo

0

मुझे लगता है कि आप इस तरह से एक क्वेरी का उपयोग कर सकते हैं:

UPDATE #Users 
SET suggestions = (
    SELECT id, score 
    FROM 
     (SELECT 
      *, 
      ROW_NUMBER() OVER (PARTITION BY id ORDER BY ord) As seq 
     FROM (
      SELECT 
       c.value('@id', 'INT') AS id, 
       c.value('@score', 'INT') AS score, 
       1 As ord 
      FROM 
       @suggestions.nodes('/Products/Product') As t(c) 
      UNION ALL 
      SELECT 
       c.value('@id', 'INT') AS id, 
       c.value('@score', 'INT') AS score, 
       2 as ord 
      FROM 
       (SELECT suggestions x FROM #Users WHERE id = @userId) As u CROSS APPLY 
       u.x.nodes('/Products/Product') As t(c)) dt) Product 
    WHERE seq = 1 
    FOR XML AUTO, ROOT('Products')) 
WHERE 
    (id = @userId); 

SELECT * 
FROM #Users; 

[Playground]

संबंधित मुद्दे