XML Workshop XVII - Writing a LOOP to process XML elements in TSQL

  • Matt Miller (3/27/2008)


    Christopher Ford (3/27/2008)


    According to the link:

    SUM(OrderQty) OVER(PARTITION BY SalesOrderID)

    That works fine...

    Why can't I remember what it was about that, that would be really nice to have...Several people have talked about it, especially when it comes to ordered updates...

    What did you mean Matt! you're not making sense today! =)

    Hehe...Put the caffeine down Chris...:) Wow!

    We're agreeing. PARTITION BY works fine...Mike C meant he wanted the ORDER BY to work in the OVER clause as well (for running totals) as part of his "wish list" of new features to have. He typoed it and said PARTITION BY (so I pointed it out to him, and he corrected himself), and thus this convoluted thread.

    As of now the SUM() OVER(PARTITION BY) is a way to get grouped totals without a GROUP BY clause. The SQL (92?) standard mentioned using this OVER notation to implement "running" aggregates as well, but that hasn't materialized yet (though there are active rumors that this might make it into 2008).

    Just to clarify for Chris - when you use the OVER clause with the ranking functions (RANK, ROW_NUMBER, etc.) you can use both the ORDER BY and PARTITION clauses. When you use OVER with an aggregate function (SUM, AVG, etc.) you can only use the PARTITION BY clause. If SQL Server implements the PARTITION BY and ORDER BY clauses for the OVER clause in aggregate functions, you can do some pretty handy running sum type calculations without cursors or a bunch of inner joins. I'd like to see the ORDER BY clause option added to the aggregate function OVER clause. This functionality is already available on other DBMS platforms like Oracle and DB2.

    I believe the OVER clause for aggregate functions was introduced either in SQL:1999 or SQL:2003, but not sure which.

    BTW - it doesn't appear to be in CTP 6 of 2008. Maybe it will be implemented around SP 1 time? 🙂

  • This example really spelled out what I should do, and the discussions showed other ways of achieving the same thing.

    I find the documentation in BOL sufficient but the lack of examples showing all of the option is what is really hindering my looking forward to working with XML in T-SQL.


    Over 12yrs in IT and 10yrs happily stuck with SQL.
    - SQL 2008/R2/2012/2014/2016/2017
    - Oracle 8/9/10/11
    - MySQL 4/5 and MariaDB

  • Great post.

    I have a question about parent nodes.

    This is my Xml.

    <?xml version="1.0" encoding="utf-16" standalone="yes"?>

    <PropertyExport Version="1.2">

    <Property Id="85" DescId="2088">

    <PhoneList>

    <Phone PhoneTypeId="48" PhoneTypeString="Booking">

    <AreaCode />

    <CountryCode>45</CountryCode>

    <Number>70121700</Number>

    </Phone>

    <Phone PhoneTypeId="51" PhoneTypeString="FaxBooking">

    <AreaCode />

    <CountryCode>45</CountryCode>

    <Number>33239686</Number>

    </Phone>

    <Phone PhoneTypeId="52" PhoneTypeString="Reception">

    <AreaCode />

    <CountryCode>45</CountryCode>

    <Number>33314801</Number>

    </Phone>

    </PhoneList>

    </Property>

    </PropertyExport>

    I can find all the <Number> and <CountryCode> nodes, like this:

    SELECT

    T.c.value('(CountryCode/text())[1]', 'varchar(256)') as CountryCode,

    T.c.value('(Number/text())[1]', 'varchar(256)') as PhoneNumber

    FROM @x.nodes('//Property/PhoneList/Phone') T(c)

    But i also want to get the id attribute from the <Property> node in the same select statement. How is that done?

    SELECT

    T.c.value('parentnode/parentnode/parentnode/@Id', 'int') as PropertyId,

    T.c.value('(CountryCode/text())[1]', 'varchar(256)') as CountryCode,

    T.c.value('(Number/text())[1]', 'varchar(256)') as PhoneNumber

    FROM @x.nodes('//Property/PhoneList/Phone') T(c)

  • I found the solution.

    Sometimes it helps, when you write your problem down on 'paper'.

    SELECT

    T.c.value('../../@Id[1]','int') AS PropertyId,

    T.c.value('(CountryCode/text())[1]', 'varchar(256)') as CountryCode,

    T.c.value('(Number/text())[1]', 'varchar(256)') as PhoneNumber

    FROM @x.nodes('//Property/PhoneList/Phone') T(c)

  • peter larsen (3/18/2009)


    I found the solution.

    Sometimes it helps, when you write your problem down on 'paper'.

    SELECT

    T.c.value('../../@Id[1]','int') AS PropertyId,

    T.c.value('(CountryCode/text())[1]', 'varchar(256)') as CountryCode,

    T.c.value('(Number/text())[1]', 'varchar(256)') as PhoneNumber

    FROM @x.nodes('//Property/PhoneList/Phone') T(c)

    One caveat is that the ".." parent node axis step can hurt performance. If you think about it, the XML processor has to back up to find the parent node, which can be time consuming depending on how complex your XML is. It might be more efficient to use a CROSS APPLY to extract child nodes from the //Property nodes. Then again, if your XML data is small in size the performance may not be a factor anyway.

  • ...and this example shows how to achieve it with a CROSS APPLY.

    SELECT

    p.value('@Id[1]','int') AS PropertyId,

    c.value('(CountryCode/text())[1]', 'varchar(256)') as CountryCode,

    c.value('(Number/text())[1]', 'varchar(256)') as PhoneNumber

    FROM @x.nodes('//Property') n(p)

    CROSS APPLY p.nodes('PhoneList/Phone') t(c)

    .

  • I was looking for an example how to use sql:variable and iterate through it with an indexer.. I never found one and I thank this for giving me the idea. This is my code how i iterated through an xml doc

    DECLARE @i TINYINT

    SET @i = 0

    WHILE @i < @NumberOfBatchFiles

    BEGIN

    SET @i = @i + 1

    SELECT

    x.value('Table[1]', 'NVARCHAR(20)') AS TableName,

    x.value('File[1]', 'NVARCHAR(100)') AS BatchFile,

    x.value('Fields[1]', 'NVARCHAR(50)') AS Fields

    FROM

    @Xml.nodes('/BatchControlFile/ImportList/ImportFile[position()=sql:variable("@i")]') e(x)

    END

    Thanks and more power to this site

  • Hello All,

    I wonder if anyone can help. I am passing xml to a stored procedure, and would like to iterate through it and save values to few different tables. I need to be able to detect the element name, and its attributes. I am including an xml string that will be passed to a stored procedure. Your help is greatly appreciated.....

    <topic id="1" soundon="true" language="true">

    <page id="1">

    <zone id="1" x="50" y="185" width="540" height="600">

    <textbox id="1">text</textbox>

    </zone>

    <zone id="2" x="620" y="90" width="430" height="765">

    <graphic x="620" y="90" width="430" height="765" linCol="0x314d5b" fillCol="0x314d5b"></graphic>

    <main_image alpha="0.2" x="620" y="90" width="645" height="645">http://www.learninglink12.edcomm.com/CMS/assets/images/right_legsondesk.jpg</main_image&gt;

    <textbox id="1" x="20" y="90" width="320">text</textbox>

    </zone>

    </page>

    <page id="2">

    <zone id="1" x="50" y="185" width="540" height="600">

    <textbox id="1">text</textbox>

    </zone>

    <zone id="2" x="620" y="90" width="430" height="765">

    <main_image alpha="0.2" x="620" y="90" width="645" height="645">http://www.learninglink12.edcomm.com/CMS/assets/images/right_meeting3.jpg</main_image&gt;

    </zone>

    </page>

    </topic>

  • I would recommend reading this article that demonstrates how to iterate over the elements of an XML document. http://www.sqlservercentral.com/articles/XML/62290/[/url]

    The XQuery function "local-name()" can be used to find out the name of an element or attribute.

    .

  • Thanks a lot! I will definitely try that.

  • position()=sql:variable("@i") is not working for me, I am working on SQL 2005, basically I was trying to loop each node and insert an element..so if I use [1] or [2] its inserting in either first or second node, that's why I wanted to use while loop...in that I using the index as [position()=sql:variable("@i")] as well as [sql:variable("@i")] both are not working...please help..

  • Please post a small repro script that shows your problem.

    .

  • DECLARE @index int

    SET @index = 0

    DECLARE @charCount as varchar(1)

    DECLARE @count int

    SET @charcount = CAST(@CustomerData.query('count(/customer/customer)') as varchar)

    SET @Count = CAST(@charCount as int)

    WHILE @index < @count

    BEGIN

    DECLARE @ID2 uniqueidentifier

    SET @ID2 = NewID()

    SET @CustomerData.modify('insert element ID {"x"} before

    (/customer/customer/firstName)[sql:variable("@index")]')

    SET @CustomerData.modify('replace value of (/customer/customer/ID/text())[sql:variable("@index")] with sql:variable("@ID2")')

    SET @index = @index + 1

    END

    Instead of sql:variable("@index") if I use 1 or 2 its working..my question how to get the parameterized index

  • The syntax is not correct. The correct expression is [position()=sql:variable("@var")]. Here is an example:

    DECLARE @x XML, @cnt INT

    SELECT @x = '

    <a>

    <n>node 1</n>

    <n>node 2</n>

    <n>node 3</n>

    </a>'

    SELECT @cnt = 1

    WHILE @cnt <= 3 BEGIN

    SELECT @x.value('(/a/n[position()=sql:variable("@cnt")])[1]','VARCHAR(20)')

    SELECT @cnt = @cnt + 1

    END

    /*

    Prints

    node 1

    node 2

    node 3

    */

    .

  • Jocb, This is not working with SET statement, Its always inserting an element under first node.

    could you please convert my example and send it back...Thanks for your help

Viewing 15 posts - 31 through 45 (of 49 total)

You must be logged in to reply to this topic. Login to reply