2010-02-15 12 views
10

Tengo una tabla con una columna XML en SQL Server 2k8. El siguiente código SQL recupera algo de XML:¿Cómo puedo obtener una lista de nombres de elementos de un valor XML en SQL Server?

SELECT TOP 1 my_xml_column FROM my_table 

Digamos que me devuelve el siguiente código XML

<a> 
    <b /> 
    <c> 
    <d /> 
    <d /> 
    <d /> 
    </c> 
</a> 

Lo que me gustaría conseguir es

/a 
/a/b 
/a/c 
/a/c/d 
/a/e 

En otras palabras, ¿cómo puedo obtener SQL Server para que me diga la estructura de mi XML?

que pueda hacer lo siguiente para obtener todos los nombres de los elemtns individuales:

SELECT C1.query('fn:local-name(.)') 
FROM my_table 
CROSS APPLY my_xml_column.nodes('//*') AS T (C1) 

Tal vez si había un equivalente a "local-name()" que devuelve la ruta completa del elemento que lo haría ¿Haz el truco?

Respuesta

16

Usted puede hacer esto sin problemas con XQuery y una CTE recursiva (sin OPENXML):

DECLARE @xml xml 
SET @xml = '<a><b /><c><d /><d /><d /></c></a>'; 

WITH Xml_CTE AS 
(
    SELECT 
     CAST('/' + node.value('fn:local-name(.)', 
      'varchar(100)') AS varchar(100)) AS name, 
     node.query('*') AS children 
    FROM @xml.nodes('/*') AS roots(node) 

    UNION ALL 

    SELECT 
     CAST(x.name + '/' + 
      node.value('fn:local-name(.)', 'varchar(100)') AS varchar(100)), 
     node.query('*') AS children 
    FROM Xml_CTE x 
    CROSS APPLY x.children.nodes('*') AS child(node) 
) 
SELECT DISTINCT name 
FROM Xml_CTE 
OPTION (MAXRECURSION 1000) 

realmente no está haciendo mucha magia XQuery, pero al menos está todo en línea, no requiere procedimientos almacenados, permisos especiales, etc.

1

sospecho que la implementación de XQuery de SQL Server no es hasta esta tarea, pero esta es otra manera de hacerlo (inspirado por this, adaptar según sea necesario):

DECLARE @idoc INT, @xml XML 
SET @xml = (SELECT TOP 1 my_xml_column FROM my_table) 
EXEC sp_xml_preparedocument @idoc OUTPUT, @xml; 

WITH 
    E AS (SELECT * FROM OPENXML(@idoc,'/',3)), 
    P AS 
    (
    -- anchor member 
    SELECT id, parentid, localname AS [Path] 
    FROM E WHERE parentid IS NULL 
    UNION ALL 
    -- recursive member 
    SELECT E.id, E.parentid, P.[Path] + '/' + localname AS [Path] 
    FROM P INNER JOIN E ON E.parentid = P.id 
    ) 
SELECT [Path] FROM P 

EXEC sp_xml_removedocument @idoc 
4

UDF para usted .....

SET ANSI_NULLS ON 
GO 
SET QUOTED_IDENTIFIER ON 
GO 
CREATE FUNCTION [dbo].[XMLTable](@x XML) 
RETURNS TABLE 
AS RETURN 
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 @x.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 
+2

cayendo en tarde, pero esto es una cosa de belleza. – KenJ

+0

¡Esto es genial! Gracias – M3SSYM4RV1N

Cuestiones relacionadas