2010-07-13 13 views
14

ACTUALIZACIÓN: He descubierto que hay un elemento de Microsoft Connect planteado para este problema here¿Cómo se quita espacio de nombres redundantes en consulta anidada cuando se utiliza para la trayectoria XML

Al utilizar FOR XML PATH y WITH XMLNAMESPACES a declarar un espacio de nombres predeterminado, obtendré la duplicación del espacio de nombres duplicado en los nodos de nivel superior para consultas anidadas que usan FOR XML, he tropezado con algunas soluciones en línea, pero no estoy totalmente convencido ...

He aquí un ejemplo completo

/* 
drop table t1 
drop table t2 
*/ 
create table t1 (c1 int, c2 varchar(50)) 
create table t2 (c1 int, c2 int, c3 varchar(50)) 
insert t1 values 
(1, 'Mouse'), 
(2, 'Chicken'), 
(3, 'Snake'); 
insert t2 values 
(1, 1, 'Front Right'), 
(2, 1, 'Front Left'), 
(3, 1, 'Back Right'), 
(4, 1, 'Back Left'), 
(5, 2, 'Right'), 
(6, 2, 'Left') 



;with XmlNamespaces(default 'uri:animal') 
select 
    a.c2 as "@species" 
    , (select l.c3 as "text()" 
     from t2 l where l.c2 = a.c1 
     for xml path('leg'), type) as "legs" 
from t1 a 
for xml path('animal'), root('zoo') 

Cuál es la mejor solución?

+0

¿Nos puede mostrar su consulta FOR XML PATH, y el XML resultante con los espacios de nombres extra? Ayuda a ver este tipo de cosas en la pantalla para diagnosticar/sugerir soluciones ... –

+0

He agregado un ejemplo completo de trabajo. –

Respuesta

10

Si he entendido bien, se está refiriendo a la conducta que se pueden ver en una consulta como esta:

DECLARE @Order TABLE (
    OrderID INT, 
    OrderDate DATETIME) 

DECLARE @OrderDetail TABLE (
    OrderID INT, 
    ItemID VARCHAR(1), 
    ItemName VARCHAR(50), 
    Qty INT) 

INSERT @Order 
VALUES 
(1, '2010-01-01'), 
(2, '2010-01-02') 

INSERT @OrderDetail 
VALUES 
(1, 'A', 'Drink', 5), 
(1, 'B', 'Cup', 2), 
(2, 'A', 'Drink', 2), 
(2, 'C', 'Straw', 1), 
(2, 'D', 'Napkin', 1) 

;WITH XMLNAMESPACES('http://test.com/order' AS od) 
SELECT 
    OrderID AS "@OrderID", 
    (SELECT 
    ItemID AS "@od:ItemID", 
    ItemName AS "data()" 
    FROM @OrderDetail 
    WHERE OrderID = o.OrderID 
    FOR XML PATH ('od.Item'), TYPE) 
FROM @Order o 
FOR XML PATH ('od.Order'), TYPE, ROOT('xml') 

cual da los siguientes resultados:

<xml xmlns:od="http://test.com/order"> 
    <od.Order OrderID="1"> 
    <od.Item xmlns:od="http://test.com/order" od:ItemID="A">Drink</od.Item> 
    <od.Item xmlns:od="http://test.com/order" od:ItemID="B">Cup</od.Item> 
    </od.Order> 
    <od.Order OrderID="2"> 
    <od.Item xmlns:od="http://test.com/order" od:ItemID="A">Drink</od.Item> 
    <od.Item xmlns:od="http://test.com/order" od:ItemID="C">Straw</od.Item> 
    <od.Item xmlns:od="http://test.com/order" od:ItemID="D">Napkin</od.Item> 
    </od.Order> 
</xml> 

Como usted ha dicho, el espacio de nombres se repite en los resultados de las subconsultas.

Este comportamiento es una característica según una conversación en devnetnewsgroup (sitio web ahora desaparecido) aunque existe la opción de vote al cambiarlo.

Mi solución propuesta es volver a FOR XML EXPLICIT:

SELECT 
    1 AS Tag, 
    NULL AS Parent, 
    'http://test.com/order' AS [xml!1!xmlns:od], 
    NULL AS [od:Order!2], 
    NULL AS [od:Order!2!OrderID], 
    NULL AS [od:Item!3], 
    NULL AS [od:Item!3!ItemID] 
UNION ALL 
SELECT 
    2 AS Tag, 
    1 AS Parent, 
    'http://test.com/order' AS [xml!1!xmlns:od], 
    NULL AS [od:Order!2], 
    OrderID AS [od:Order!2!OrderID], 
    NULL AS [od:Item!3], 
    NULL [od:Item!3!ItemID] 
FROM @Order 
UNION ALL 
SELECT 
    3 AS Tag, 
    2 AS Parent, 
    'http://test.com/order' AS [xml!1!xmlns:od], 
    NULL AS [od:Order!2], 
    o.OrderID AS [od:Order!2!OrderID], 
    d.ItemName AS [od:Item!3], 
    d.ItemID AS [od:Item!3!ItemID] 
FROM @Order o INNER JOIN @OrderDetail d ON o.OrderID = d.OrderID 
ORDER BY [od:Order!2!OrderID], [od:Item!3!ItemID] 
FOR XML EXPLICIT 

Y ver estos resultados:

<xml xmlns:od="http://test.com/order"> 
    <od:Order OrderID="1"> 
    <od:Item ItemID="A">Drink</od:Item> 
    <od:Item ItemID="B">Cup</od:Item> 
    </od:Order> 
    <od:Order OrderID="2"> 
    <od:Item ItemID="A">Drink</od:Item> 
    <od:Item ItemID="C">Straw</od:Item> 
    <od:Item ItemID="D">Napkin</od:Item> 
    </od:Order> 
</xml> 
+1

+1 ¡Saludos por tu respuesta, cualquier idea sobre cómo se compara con la alternativa? (vea mi respuesta a mi pregunta) –

3

Una solución alternativa que he visto es añadir la declaración XMLNAMESPACES después de construir el xml en una variable temporal:

declare @xml as xml; 
select @xml = (
select 
    a.c2 as "@species" 
    , (select l.c3 as "text()" 
     from t2 l where l.c2 = a.c1 
     for xml path('leg'), type) as "legs" 
from t1 a 
for xml path('animal')) 

;with XmlNamespaces('uri:animal' as an) 
select @xml for xml path('') , root('zoo'); 
+1

Para filas de 1 m, su solución corre el doble de rápido. = (Aunque tiene un xmlns = "" en cada fila de "especies". ¿Eso importa? Lo interesante del método FOR XML EXPLICIT es que te permite hacer múltiples espacios de nombres. No estoy seguro de cómo lo harías eso con una solución alternativa (aunque si no tienes la necesidad de hacerlo, probablemente no importe). Enlace a las pruebas de rendimiento aquí si tienes curiosidad: http://tinyurl.com/3yejtyv – 8kb

+1

Tengo Cambie el resultado para eliminar el espacio de nombres en blanco, pero en última instancia, de cualquier manera, supongo que este enfoque genera un aspecto agradable xml, pero no es realmente válido, así que acepté la respuesta de 8kb como el mejor enfoque. –

1

I ' m poco confuso acerca de todas estas explicaciones, mientras declarando un "xmlns: animales" manualmente está haciendo el trabajo: Aquí un ejemplo que escribí para generar Open Graph de los metadatos

DECLARE @l_xml as XML; 
SELECT @l_xml = 
(
SELECT 'http://ogp.me/ns# fb: http://ogp.me/ns/fb# scanilike: http://ogp.me/ns/fb/scanilike#' as 'xmlns:og', 
    (SELECT 
     (SELECT 'og:title' as 'property', title as 'content' for xml raw('meta'), TYPE), 
     (SELECT 'og:type' as 'property', OpenGraphWebMetadataTypes.name as 'content' for xml raw('meta'), TYPE), 
     (SELECT 'og:image' as 'property', image as 'content' for xml raw('meta'), TYPE), 
     (SELECT 'og:url' as 'property', url as 'content' for xml raw('meta'), TYPE), 
     (SELECT 'og:description' as 'property', description as 'content' for xml raw('meta'), TYPE), 
     (SELECT 'og:site_name' as 'property', siteName as 'content' for xml raw('meta'), TYPE), 
     (SELECT 'og:appId' as 'property', appId as 'content' for xml raw('meta'), TYPE) 
    FROM OpenGraphWebMetaDatas INNER JOIN OpenGraphWebMetadataTypes ON OpenGraphWebMetaDatas.type = OpenGraphWebMetadataTypes.id WHERE THING_KEY = @p_index 
    for xml path('header'), TYPE), 
    (SELECT '' as 'body' for xml path(''), TYPE) 
    for xml raw('html'), TYPE 
) 

RETURN @l_xml 

devolver el resultado esperado

<html xmlns:og="http://ogp.me/ns# fb: http://ogp.me/ns/fb# scanilike: http://ogp.me/ns/fb/scanilike#"> 
<header> 
<meta property="og:title" content="The First object"/> 
<meta property="og:type" content="scanilike:tag"/> 
<meta property="og:image" content="http://www.mygeolive.com/images/facebook/facebook-logo.jpg"/> 
<meta property="og:url" content="http://www.scanilike.com/opengraph?id=1"/> 
<meta property="og:description" content="This is the very first object created using the IOThing &amp; ScanILike software. We keep it in file for history purpose. "/> 
<meta property="og:site_name" content="http://www.scanilike.com"/> 
<meta property="og:appId" content="200270673369521"/> 
</header> 
<body/> 
</html> 

espero que esto ayude a las personas a buscar problemas similares en la web. ;-)

+0

¡Genial! Entonces, el truco no obvio es que el elemento * root * debe seleccionarse usando 'para xml raw' pero los internos aún pueden usar' para xml path'. –

+0

En realidad, solo funciona si configura el 'xmlns: og' no predeterminado, si desea establecer el' xmlns' predeterminado, entonces todavía obtendrá declaraciones de espacio de nombres repetidas. –

2

El problema aquí se ve agravado por el hecho de que no se pueden declarar directamente los espacios de nombres cuando se utiliza XML PATH. SQL Server rechazará cualquier nombre de atributo que comience por 'xmlns' y cualquier nombre de etiqueta con dos puntos en ellos.

En lugar de tener que recurrir al uso de XML EXPLICIT relativamente hostil, solucioné el problema generando primero XML con definiciones y referencias de espacios de nombres 'ocultos', y luego reemplazo cadenas de la siguiente manera ...

DECLARE @Order TABLE (
    OrderID INT, 
    OrderDate DATETIME) 

DECLARE @OrderDetail TABLE (
    OrderID INT, 
    ItemID VARCHAR(1), 
    ItemName VARCHAR(50), 
    Qty INT) 

INSERT @Order 
VALUES 
(1, '2010-01-01'), 
(2, '2010-01-02') 

INSERT @OrderDetail 
VALUES 
(1, 'A', 'Drink', 5), 
(1, 'B', 'Cup', 2), 
(2, 'A', 'Drink', 2), 
(2, 'C', 'Straw', 1), 
(2, 'D', 'Napkin', 1) 

declare @xml xml 

set @xml = (SELECT 
    'http://test.com/order' as "@xxmlns..od", -- 'Cloaked' namespace def 
    (SELECT OrderID AS "@OrderID", 
    (SELECT 
     ItemID AS "@od..ItemID", 
     ItemName AS "data()" 
    FROM @OrderDetail 
    WHERE OrderID = o.OrderID 
    FOR XML PATH ('od..Item'), TYPE) 
    FROM @Order o 
    FOR XML PATH ('od..Order'), TYPE) 
    FOR XML PATH('xml')) 

set @xml = cast(replace(replace(cast(@xml as nvarchar(max)), 'xxmlns', 'xmlns'),'..',':') as xml) 

select @xml 

Algunas cosas a señalar:

  1. que estoy usando '' xxmlns como mi versión envuelta de 'xmlns' y '..' para sustituir a ':'. Es posible que esto no funcione para usted si es probable que tenga '..' como parte de los valores de texto; puede sustituirlo por otra cosa, siempre que elija algo que sea un identificador XML válido.

  2. Como queremos la definición de xmlns en el nivel superior, no podemos usar la opción 'ROOT' para XML PATH, sino que necesitaba agregar otro nivel externo a la estructura de subselección para lograr esto.

+0

Esto definitivamente parece ser una respuesta bien pensada a la pregunta. Sin embargo, la pregunta en sí misma fue hecha hace 6 años **, y la respuesta proporcionada aquí debe ser tomada por nuevos lectores como una solución reciente a un problema potencialmente de larga data. – Claies

+0

@Claies - sí, absolutamente - ¡Ciertamente no espero que el OP esté interesado en mi respuesta después de todo este tiempo! - Definitivamente estaba dirigido a otros lectores que buscaban soluciones para este problema (como yo lo estaba), que hasta donde puedo llegar aún estará presente en SQL Server 2016. –

+0

Olvidé lo que XML es ahora, ¡Lo siento! (+1 de todos modos para una respuesta de calidad!) –

5

Después de horas de desesperación y cientos de ensayos & errores, yo he llegado con la solución a continuación.

que tenían el mismo problema, cuando quería sólo unaxmlns atributo, en la raíz nodo única. Pero también tuve una consulta muy difícil con un montón de subconsultas y el método FOR XML EXPLICIT solo era demasiado engorroso. Así que sí, quería la comodidad de FOR XML PATH en las subconsultas y también para establecer mi propio xmlns.

Le pedí prestado el código de 8kb's respuesta, porque era muy agradable. Lo pellizqué un poco para una mejor comprensión. Aquí está el código:

DECLARE @Order TABLE (OrderID INT, OrderDate DATETIME)  
DECLARE @OrderDetail TABLE (OrderID INT, ItemID VARCHAR(1), Name VARCHAR(50), Qty INT)  
INSERT @Order VALUES (1, '2010-01-01'), (2, '2010-01-02')  
INSERT @OrderDetail VALUES (1, 'A', 'Drink', 5), 
          (1, 'B', 'Cup', 2), 
          (2, 'A', 'Drink', 2), 
          (2, 'C', 'Straw', 1), 
          (2, 'D', 'Napkin', 1) 

-- Your ordinary FOR XML PATH query 
DECLARE @xml XML = (SELECT OrderID AS "@OrderID", 
         (SELECT ItemID AS "@ItemID", 
           Name AS "data()" 
         FROM @OrderDetail 
         WHERE OrderID = o.OrderID 
         FOR XML PATH ('Item'), TYPE) 
        FROM @Order o 
        FOR XML PATH ('Order'), ROOT('dummyTag'), TYPE) 

-- Magic happens here!  
SELECT 1 AS Tag 
     ,NULL AS Parent 
     ,@xml AS [xml!1!!xmltext] 
     ,'http://test.com/order' AS [xml!1!xmlns] 
FOR XML EXPLICIT 

Resultado:

<xml xmlns="http://test.com/order"> 
    <Order OrderID="1"> 
    <Item ItemID="A">Drink</Item> 
    <Item ItemID="B">Cup</Item> 
    </Order> 
    <Order OrderID="2"> 
    <Item ItemID="A">Drink</Item> 
    <Item ItemID="C">Straw</Item> 
    <Item ItemID="D">Napkin</Item> 
    </Order> 
</xml> 

Si ha seleccionado @xml solo, que se vería que contiene nodo raíz dummyTag. Nosotros no lo necesitamos, así que quitarlo mediante directivexmltext en FOR XML EXPLICIT consulta:

,@xml AS [xml!1!!xmltext] 

Aunque la explicación en MSDN suena más sofisticada, pero en la práctica se le dice al analizador para seleccionar los contenidos de XML raíz nodo.

No está seguro de lo rápido que la consulta es, sin embargo, actualmente estoy relajarse y beber whisky como un caballero, mientras tranquilamente mirando el código ...

Cuestiones relacionadas