简体   繁体   中英

How to get nodes path in XML by value?

I have XML with multiple node structure and would like to get nodes path in XML by searching value "12345" using SQL Server 2012.

Sample XML :

<?xml version="1.0" encoding="UTF-8"?>
<Parent>
  <DataExtensions>
   <DataItem key="Key1" type="String">
     <value>12345</value>
   </DataItem>
    <DataItem key="Key2" type="String">
     <value>6789</value>
   </DataItem>
 </DataExtensions>
 <TestKey1>12345</TestKey1>
 <Answers>
    <AnswersList>
        <Entry key="Key2" type="Answer">
          <value ID="Key1ID" Text="12345" />
       </Entry>
       <Entry key="Key3" type="Answer">
          <value ID="Key3ID" Text="6789" />
       </Entry>
     </AnswersList>
  </Answers>
  <TestKey2>12345</TestKey2>
  <TestKey3>67890</TestKey3>
  <TestKey4>12345</TestKey3>
</Parent> 

I am able to get values by writing for each select statement, but I would like to get node path where key value or value is equal to 12345 in the whole XML.

Desired output in a table would be:

(/*/TestKey1/text())
(/*/TestKey2/text())
(/*/TestKey4/text())
(/*/DataExtensions/DataItem[@key=("Key1")]/value())
 (/*/Answers/AnswersList/Entry[@key=("Key2")]/value/text())

Please help.

You can try the following solution. It does many powerful things, including XML visual tree representation. Your answer is in the [XPath] column of the output.

T-SQL

DECLARE @xml XML = 
N'<Parent>
    <DataExtensions>
        <DataItem key="Key1" type="String">
            <value>12345</value>
        </DataItem>
        <DataItem key="Key2" type="String">
            <value>6789</value>
        </DataItem>
    </DataExtensions>
    <TestKey1>12345</TestKey1>
    <Answers>
        <AnswersList>
            <Entry key="Key2" type="Answer">
                <value ID="Key1ID" Text="6789"/>
            </Entry>
            <Entry key="Key3" type="Answer">
                <value ID="Key3ID" Text="12345"/>
            </Entry>
        </AnswersList>
    </Answers>
    <TestKey2>12345</TestKey2>
    <TestKey3>67890</TestKey3>
    <TestKey4>12345</TestKey4>
</Parent>';

-- Solution # 2.
-- a thing of beauty.
WITH cte AS (  
SELECT 1 AS lvl,  
        x.value('local-name(.)','NVARCHAR(MAX)') AS Name,  
        CAST(NULL AS NVARCHAR(MAX)) AS ParentName, 
        CAST(1 AS INT) AS ParentPosition, 
        CAST(N'Element' AS NVARCHAR(20)) AS NodeType,  
        '/' + x.value('local-name(.)','NVARCHAR(MAX)') AS FullPath,  
        '/' + x.value('local-name(.)','NVARCHAR(MAX)')  
        + N'[' 
        + CAST(ROW_NUMBER() OVER(ORDER BY (SELECT 1)) AS NVARCHAR)  
        + N']' AS XPath,  
        ROW_NUMBER() OVER(ORDER BY (SELECT 1)) AS Position, 
        x.value('local-name(.)','NVARCHAR(MAX)') AS Tree,  
        x.value('text()[1]','NVARCHAR(MAX)') AS Value,  
        x.query('.') AS this,         
        x.query('*') AS t,  
        CAST(CAST(1 AS VARBINARY(4)) AS VARBINARY(MAX)) AS Sort,  
        CAST(1 AS INT) AS ID  
FROM @xml.nodes('/*') a(x)  
UNION ALL 
SELECT p.lvl + 1 AS lvl,  
        c.value('local-name(.)','NVARCHAR(MAX)') AS Name,  
        CAST(p.Name AS NVARCHAR(MAX)) AS ParentName, 
    CAST(p.Position AS INT) AS ParentPosition, 
        CAST(N'Element' AS NVARCHAR(20)) AS NodeType,  
        CAST(p.FullPath + N'/' + c.value('local-name(.)','NVARCHAR(MAX)') AS NVARCHAR(MAX)) AS FullPath,

        CAST(p.XPath + N'/'+ c.value('local-name(.)','NVARCHAR(MAX)')
        + N'['
        + CAST(ROW_NUMBER() OVER(PARTITION BY c.value('local-name(.)','NVARCHAR(MAX)') ORDER BY (SELECT 1)) AS NVARCHAR)
        + N']' AS NVARCHAR(MAX)) AS XPath,  

        ROW_NUMBER() OVER(PARTITION BY c.value('local-name(.)','NVARCHAR(MAX)')
        ORDER BY (SELECT 1)) AS Position, 
        CAST( SPACE(2 * p.lvl - 1) + N'|' + REPLICATE(N'-', 1) + c.value('local-name(.)','NVARCHAR(MAX)') AS NVARCHAR(MAX)) AS Tree,  
        CAST( c.value('text()[1]','NVARCHAR(MAX)') AS NVARCHAR(MAX) ) AS Value, c.query('.') AS this,  
        c.query('*') AS t,  
        CAST(p.Sort + CAST( (lvl + 1) * 1024 + (ROW_NUMBER() OVER(ORDER BY (SELECT 1)) * 2) AS VARBINARY(4)) AS VARBINARY(MAX) ) AS Sort,  
        CAST((lvl + 1) * 1024 + (ROW_NUMBER() OVER(ORDER BY (SELECT 1)) * 2) AS INT)  
FROM cte p  
CROSS APPLY p.t.nodes('*') b(c))
, cte2 AS (  
SELECT lvl AS Depth,  
        Name AS NodeName,  
        ParentName, 
        ParentPosition, 
        NodeType,  
        FullPath,  
        XPath,  
        Position, 
        Tree AS TreeView,  
        Value,  
        this AS XMLData,  
        Sort, ID  
        FROM cte  
UNION ALL 
SELECT p.lvl,  
        x.value('local-name(.)','NVARCHAR(MAX)'),  
        p.Name, 
        p.Position, 
        CAST(N'Attribute' AS NVARCHAR(20)),  
        p.FullPath + N'/@' + x.value('local-name(.)','NVARCHAR(MAX)'),  
        p.XPath + N'/@' + x.value('local-name(.)','NVARCHAR(MAX)'),  
        1, 
        SPACE(2 * p.lvl - 1) + N'|' + REPLICATE('-', 1)  
        + N'@' + x.value('local-name(.)','NVARCHAR(MAX)'),  
        x.value('.','NVARCHAR(MAX)'),  
        NULL,  
        p.Sort,  
        p.ID + 1  
FROM cte p  
CROSS APPLY this.nodes('/*/@*') a(x)  
)  
SELECT ROW_NUMBER() OVER(ORDER BY Sort, ID) AS ID,  
    ParentName, ParentPosition,Depth, NodeName, Position,   
    NodeType, FullPath, XPath, TreeView, Value, XMLData
FROM cte2
WHERE Value = '12345';

T-SQL, short version

DECLARE @DocHandle INT;
EXEC sp_xml_preparedocument @DocHandle OUTPUT, @xml;

--SELECT * FROM OPENXML(@DocHandle,'/*');

;WITH rs AS
(
   SELECT * FROM OPENXML(@DocHandle,'/*')
), cte AS
(
   -- anchor
    SELECT id
      ,ParentID
      --, nodetype
      , [text]
      ,CAST(id AS VARCHAR(100)) AS [Path]
      ,CAST('/' + rs.localname AS VARCHAR(1000)) 
      + N'[' 
        + CAST(ROW_NUMBER() OVER(ORDER BY (SELECT 1)) AS NVARCHAR)  
        + N']' AS [XPath]
    FROM rs
    WHERE ParentID IS NULL
    UNION ALL
    --recursive member
    SELECT t.id
      ,t.ParentID
      --, nodetype = (SELECT nodetype FROM rs WHERE id = t.ParentID)
      , t.[text]
      , CAST(a.[Path] + ',' + CAST( t.ID AS VARCHAR(100)) AS VARCHAR(100)) AS [Path]
      , CAST(a.[XPath] + '/' + IIF(t.nodetype = 2, '@', '')
         + t.localname AS VARCHAR(1000)) 
         + N'['
        + CAST(ROW_NUMBER() OVER(PARTITION BY t.localname ORDER BY (SELECT 1)) AS NVARCHAR)
        + N']' AS [XPath]
    FROM rs AS t
      INNER JOIN cte AS a ON t.ParentId = a.id
)
SELECT ID, ParentID, [Path]
   , REPLACE([XPath],'[1]/#text[1]','') AS XPath, [text] AS [Value]
FROM cte
WHERE [text] IS NOT NULL
    AND CAST([text] AS VARCHAR(30)) = '12345'
ORDER BY [Path];

EXEC sp_xml_removedocument @DocHandle;

Output

+----+----------+---------------------+--------------------------------------------------------------+-------+
| ID | ParentID |        Path         |                            XPath                             | Value |
+----+----------+---------------------+--------------------------------------------------------------+-------+
| 35 |       11 |             0,11,35 | /Parent[1]/TestKey1                                          | 12345 |
| 43 |       25 | 0,12,13,20,23,25,43 | /Parent[1]/Answers[1]/AnswersList[1]/Entry[2]/value[1]/@Text | 12345 |
| 31 |        6 |          0,2,3,6,31 | /Parent[1]/DataExtensions[1]/DataItem[1]/value               | 12345 |
| 44 |       26 |             0,26,44 | /Parent[1]/TestKey2                                          | 12345 |
| 46 |       28 |             0,28,46 | /Parent[1]/TestKey4                                          | 12345 |
+----+----------+---------------------+--------------------------------------------------------------+-------+

..JfF..

declare @x xml = N'<Parent>........';

select t.col.value('.', 'nvarchar(max)') as thepath
from 
(values(@x.query('
let $theval := "12345"
for $e in (//*[text()=$theval], //@*[.=$theval])
return
<p>
{
for $i in ($e/../../../../.., $e/../../../.., $e/../../.., $e/../.., $e/.., $e)[..] (: max depth=6 :)
let $k := if ($i/@key) then concat("[@key=(""", $i/@key, """)]") else ""
return text{concat("/",local-name($i), $k)}
}
{if ($e instance of element()) then "/text()" else ""}
</p>
'))) as v(x)
cross apply v.x.nodes('/p') as t(col);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM