<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:copyright="http://blogs.law.harvard.edu/tech/rss" xmlns:image="http://purl.org/rss/1.0/modules/image/">
    <channel>
        <title>MSFT MVP</title>
        <link>http://geekswithblogs.net/ktegels/category/3742.aspx</link>
        <description>Stuff about being an MVP</description>
        <language>en-US</language>
        <copyright>Kent Tegels</copyright>
        <managingEditor>ktegels@gmail.com</managingEditor>
        <generator>Subtext Version 0.0.0.0</generator>
        <item>
            <title>Shreding XML with XQuery</title>
            <link>http://geekswithblogs.net/ktegels/archive/2006/01/20/ShredWithXQuery01.aspx</link>
            <description>&lt;P&gt;Over on the Microsoft.Public.SqlServer.XML newsgroup, one Chris Kilmer asked a good question about how to shred a single XML document into multiple tables using multiple stored procedures. Chris's goal with this was to have each stored procedure update one table and pass the XML remaining nodes of the first document off to the next stored procedure. This is actually fairly easy except for one thing: maintaining referential activity between inserted elements.&lt;/P&gt;
&lt;P&gt;The XML involved is fairly simple:&lt;/P&gt;
&lt;P&gt;&lt;FONT face="Courier New"&gt;&amp;lt;Invoice&amp;gt;&lt;BR&gt;&amp;nbsp; &amp;lt;InvoiceID&amp;gt;1&amp;lt;/InvoiceID&amp;gt;&lt;BR&gt;&amp;nbsp; &amp;lt;Customer&amp;gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;FName&amp;gt;Bill&amp;lt;/FName&amp;gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;LName&amp;gt;Gates&amp;lt;/LName&amp;gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;Address&amp;gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;Street&amp;gt;1 Microsoft Way&amp;lt;/Street&amp;gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;City&amp;gt;Redmond&amp;lt;/City&amp;gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;Zip&amp;gt;98052&amp;lt;/Zip&amp;gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;/Address&amp;gt;&lt;BR&gt;&amp;nbsp; &amp;lt;/Customer&amp;gt;&lt;BR&gt;&amp;lt;/Invoice&amp;gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;We want to shred this data using SQL Server 2005's XML datatype's Nodes method when and where we can. The database schema has three tables scripted as such.&lt;/P&gt;
&lt;P&gt;&lt;FONT face="Courier New"&gt;create table dbo.invoices(pkid tinyint identity(10,1) primary key,InvoiceID int)&lt;BR&gt;create table dbo.customers(pkid tinyint identity(20,1) primary key,invoicePKID tinyint constraint fkCustomersInvoices foreign key references dbo.invoices(pkid) on delete cascade on update cascade,fname varchar(20),lname varchar(20))&lt;BR&gt;create table dbo.addresses(pkid tinyint identity(30,1) primary key,customerPKID tinyint constraint fkAddressesCustomers foreign key references dbo.customers(pkid) on delete cascade on update cascade,street varchar(50),locale varchar(50),postalCode varchar(25))&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;So no big deal with that. While we could argue that there's better designs, this one works for now. Our first stored procedure really needs to do&amp;nbsp;three things:&lt;/P&gt;
&lt;OL&gt;
&lt;LI&gt;Extract the InvoiceID and insert that info the dbo.invoices table&lt;/LI&gt;
&lt;LI&gt;Get the identity value for the just inserted row as we'll use that we'll need that when we insert the customer's detail into dbo.customers&lt;/LI&gt;
&lt;LI&gt;We need to get the Customer element as a fresh XML document to pass on the next stored procedure&lt;/LI&gt;&lt;/OL&gt;
&lt;P&gt;Here's the code for a procedure that does that.&lt;/P&gt;
&lt;P&gt;&lt;FONT face="Courier New"&gt;create procedure dbo.shred1(@in xml)&lt;BR&gt;as&lt;BR&gt;begin&lt;BR&gt;begin try&lt;BR&gt;&amp;nbsp;begin tran&lt;BR&gt;&lt;/FONT&gt;&lt;FONT face="Courier New"&gt;&amp;nbsp;declare @id tinyint&lt;BR&gt;&amp;nbsp;declare @c table(pkid tinyint)&lt;BR&gt;&amp;nbsp;declare @cust xml&lt;BR&gt;&lt;FONT color=#006400&gt;&amp;nbsp;-- Get the invoiceID from the XML and insert that into the invoices table,&lt;BR&gt;&amp;nbsp;-- capturing the PKID for row just inserted.&lt;BR&gt;&lt;/FONT&gt;&amp;nbsp;insert into dbo.invoices(invoiceid)&lt;BR&gt;&amp;nbsp;output inserted.pkid into @c&lt;BR&gt;&amp;nbsp;select c.cust.value('InvoiceID[1]','int')&lt;BR&gt;&amp;nbsp;from @in.nodes('/Invoice') as c(cust)&lt;BR&gt;&lt;FONT color=#006400&gt;&amp;nbsp;-- Get the Customer sub-element&lt;BR&gt;&lt;/FONT&gt;&amp;nbsp;select @cust = @in.query('/Invoice/Customer')&lt;BR&gt;&lt;FONT color=#006400&gt;&amp;nbsp;-- Get that PKID and insert it into the the Customer element&lt;BR&gt;&amp;nbsp;-- so we can pass that along to the next procedure.&lt;BR&gt;&lt;/FONT&gt;&amp;nbsp;select @id = pkid from @c&lt;BR&gt;&amp;nbsp;set @cust.modify('insert &amp;lt;invoiceID&amp;gt;{sql:variable("@id")}&amp;lt;/invoiceID&amp;gt; into /Customer[1]')&lt;BR&gt;&amp;nbsp;exec dbo.shred2 @cust&lt;BR&gt;&amp;nbsp;commit&lt;BR&gt;end try&lt;BR&gt;begin catch&lt;BR&gt;&amp;nbsp;rollback&lt;BR&gt;end catch&lt;BR&gt;end&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face="Courier New"&gt;&lt;FONT face="Times New Roman"&gt;A few comments need to be made about this example as there's some interesting things being done.&lt;/FONT&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;The inbound and outbound instances really should be bound to an XML Schema collection to make sure they valid. That's not shown here to save time, space and detailing of schema collection behavior.&lt;/LI&gt;
&lt;LI&gt;This method works great if the inbound instance (@in) contains a single invoice only. If it doesn't, things get more complicated in terms of getting the host row identity value into the outbound XML instance.&lt;/LI&gt;
&lt;LI&gt;We didn't have to embed the hosting row's identity value in the passed-down instance. We've done that here to keep in-line with Chris's request to pass XML instance between the procedures. However, a positive from this is that we could somewhat more easily resolve the issue above since the we could pass a single XML instance containing all of the customers.&lt;/LI&gt;
&lt;LI&gt;I wouldn't normally choose to use the .Nodes method to shred a single XML instance, however, Chris said that this is what he does so I'm showing that for the sake of consistency. Here there's no need to do that, per se, as we could just as easily and performantly pick element values out resorting to a .Nodes call.&lt;/LI&gt;
&lt;LI&gt;&lt;FONT face="Courier New"&gt;&lt;FONT face="Times New Roman"&gt;Although you can shred XML using what we're looking at here, once you get more than four or five XQuery method calls in a T-SQL Server, you're probably better to have put the instance in a table with a primary XML index on the column holding the instance. Otherwise, you're generating a Node Table for each method call which isn't cheap.&lt;/FONT&gt;&lt;/FONT&gt;&lt;/LI&gt;&lt;/UL&gt;
&lt;P&gt;The real meat of Chris's question in the post was this: &amp;#8220;However, I do not understand how to retreive the Customer node from the Invoice xml as a new xml doc and then send the new xml doc to the next sproc.&amp;#8220; Getting the Customer element is fairly easy -- declare a variable of type XML and then use a SET query calling the .Query method on the source instance to select the desired elements. Ala:&lt;/P&gt;
&lt;P&gt;&lt;FONT face="Courier New"&gt;select @cust = @in.query('/Invoice/Customer')&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;Since we already have our desired instance as an XML instance, we can simply pass it to the next Stored Procedure as parameter thusly:&lt;/P&gt;
&lt;P&gt;&lt;FONT face="Courier New"&gt;exec dbo.shred2 @cust&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;Our next stored procedure is similar. It takes in the customer's data, parses out their name dumps that into the dbo.customers table. The address portion of the data is extracted as passed along to the next procedure. Again, this has some of the same considerations in terms of performance as noted for the first procedure.&lt;/P&gt;
&lt;P&gt;&lt;FONT face="Courier New"&gt;&lt;FONT face="Times New Roman"&gt;&lt;/FONT&gt;create procedure dbo.shred2(@cust xml)&lt;BR&gt;as&lt;BR&gt;begin&lt;BR&gt;&amp;nbsp;declare @c table(pkid tinyint);&lt;BR&gt;&amp;nbsp;declare @addr xml;&lt;BR&gt;&amp;nbsp;declare @id tinyint;&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face="Courier New"&gt;&lt;FONT color=#006400&gt;&amp;nbsp;-- Shred out the customer information and&lt;BR&gt;&amp;nbsp;-- insert that into the customers table,&lt;BR&gt;&amp;nbsp;-- capturing the PKID.&lt;BR&gt;&lt;/FONT&gt;&amp;nbsp;insert into dbo.customers(fname,lname,invoicePKID)&lt;BR&gt;&amp;nbsp;output inserted.pkid into @c&lt;BR&gt;&amp;nbsp;select&amp;nbsp;c.x.value('FName[1]','varchar(20)'),&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;c.x.value('LName[1]','varchar(20)'),&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;c.x.value('invoiceID[1]','tinyint')&lt;BR&gt;&amp;nbsp;from @cust.nodes('/Customer') as c(x);&lt;BR&gt;&lt;FONT color=#006400&gt;&amp;nbsp;-- Get the ID for that just inserted row and embed it into Address Data.&lt;BR&gt;&lt;/FONT&gt;&amp;nbsp;select @id = pkid from @c&lt;BR&gt;&amp;nbsp;select @addr = @cust.query('/Customer/Address')&lt;BR&gt;&amp;nbsp;set @addr.modify('insert &amp;lt;customerID&amp;gt;{sql:variable("@id")}&amp;lt;/customerID&amp;gt; into /Address[1]')&lt;BR&gt;&lt;FONT color=#006400&gt;&amp;nbsp;-- Shred the Address data&lt;BR&gt;&lt;/FONT&gt;&amp;nbsp;exec dbo.shred3 @addr&lt;BR&gt;end&lt;BR&gt;go&lt;BR&gt;&lt;BR&gt;&lt;/FONT&gt;&lt;FONT face="Times New Roman"&gt;Our last stored procedure is pretty straight forward, and the same comments apply:&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face="Courier New"&gt;create procedure dbo.shred3(@addr xml)&lt;BR&gt;as&lt;BR&gt;begin&lt;BR&gt;&amp;nbsp;insert into dbo.addresses(street,locale,postalCode,customerPKID)&lt;BR&gt;&amp;nbsp;select&amp;nbsp;a.f.value('Street[1]','varchar(50)'),&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;a.f.value('City[1]','varchar(50)'),&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;a.f.value('Zip[1]','varchar(25)'),&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;a.f.value('customerID[1]','tinyint')&lt;BR&gt;&amp;nbsp;from&amp;nbsp;@addr.nodes('/Address') as a(f)&lt;BR&gt;end&lt;BR&gt;go&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face="Courier New"&gt;&lt;FONT face="Times New Roman"&gt;The only real pain here was figuring a good way to capture the identity value for the just inserted row. Sure, saving off SCOPE_IDENTITY()&amp;nbsp;is option, but this example also demonstrates that can get the identity using the new OUTPUT clause. More, SCOPE_IDENTITY wouldn't have worked well enough&amp;nbsp;if there had been multiple Invovices serialized into the first Stored Procedure's input XML as it returns the last identity generated, not all of the identities generated.&lt;/FONT&gt;&lt;BR&gt;&lt;/P&gt;&lt;/FONT&gt;&lt;FONT face="Courier New"&gt;&lt;/FONT&gt;
&lt;P&gt;&lt;FONT face="Courier New"&gt;&amp;nbsp;&lt;/P&gt;&lt;/FONT&gt;&lt;p&gt;&lt;a href="http://www.pheedo.com/click.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=66550"&gt;&lt;img src="http://www.pheedo.com/img.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=66550" border="0"/&gt;&lt;/a&gt;&lt;/p&gt;&lt;iframe src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;PageID=31016&amp;amp;SiteID=1" width=1 height=1 Marginwidth=0 Marginheight=0 Hspace=0 Vspace=0 Frameborder=0 Scrolling=No&gt;
&lt;script language='javascript1.1' src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Browser=NETSCAPE4&amp;amp;NoCache=True&amp;PageID=31016&amp;amp;SiteID=1"&gt;&lt;/script&gt;
&lt;noscript&gt;&lt;a href="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Click&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" target="_blank"&gt;
&lt;img src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" width="1" height="1" border="0"  alt=""&gt;&lt;/a&gt;
&lt;/noscript&gt;
&lt;/iframe&gt;
&lt;img src="http://geekswithblogs.net/ktegels/aggbug/66550.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Kent Tegels</dc:creator>
            <guid>http://geekswithblogs.net/ktegels/archive/2006/01/20/ShredWithXQuery01.aspx</guid>
            <pubDate>Fri, 20 Jan 2006 19:02:00 GMT</pubDate>
            <wfw:comment>http://geekswithblogs.net/ktegels/comments/66550.aspx</wfw:comment>
            <comments>http://geekswithblogs.net/ktegels/archive/2006/01/20/ShredWithXQuery01.aspx#feedback</comments>
            <slash:comments>4</slash:comments>
            <wfw:commentRss>http://geekswithblogs.net/ktegels/comments/commentRss/66550.aspx</wfw:commentRss>
            <trackback:ping>http://geekswithblogs.net/ktegels/services/trackbacks/66550.aspx</trackback:ping>
        </item>
    </channel>
</rss>