2012-09-13 129 views
5

He leído a través de SO: XML data type method “value” must be a string literal pero mi problema es un poco diferente. Tengo un poco de xml en una variable que quiero separar y me dan un camino. originalmente probé esto:El argumento 1 del método de tipo de datos XML "valor" debe ser una cadena literal

declare @x xml 
select @x = '....' 
select @x.value('(' + @path + ')[1]', 'varchar(max)') 

pero, por supuesto, eso falla. entonces encontré el SQL: variable e intenté esto:

select @x.value('(sql:variable("@path"))[1]', 'varchar(max)') 

pero que curiosamente devuelve el valor de @path (¿por qué?). He estado jugando con eso pero no puedo hacer que haga lo correcto.

Pensamientos a alguien?

+0

Utilice las URL de SO actuales aquí. Puede hacer clic con el botón derecho en el enlace 'compartir' en la parte inferior de cualquier pregunta o respuesta y elegir 'Copiar', y se vinculará correctamente por título (como hice con su URL anterior). Gracias. –

Respuesta

2

con la ayuda de wBob en el sitio de Microsoft, ahora tengo una solución limpia. El rendimiento está, por supuesto, una preocupación ya que todo el documento conseguirá asignada por el bien de un solo camino pero las mejoras se dejan como posibilidades de sugerencia para el lector :)

if object_id('VMConfigVal') is not null 
drop function VMConfigVal 
go 
create function VMConfigVal(@x xml, @path varchar(max)) 
returns nvarchar(max) 
as 
begin 
    declare @ret nvarchar(max) 

    ;with cte as 
    (
    select value = x.c.value('.', 'varchar(50)') 
    ,  path = cast (null as varchar(max)) 
    ,  node = x.c.query('.') 
    from @x.nodes('/*') x(c) 
    union all 
    select n.c.value('.', 'varchar(50)') 
    ,  isnull(c.path + '/', '/') 
     +  n.c.value('local-name(.)', 'varchar(max)') 
    ,  n.c.query('*') 
    from cte c 
    cross apply c.node.nodes('*') n(c) 
    ) 
    select @ret = value from cte where path = @path 
    return @ret 
    end 
go 

así que ahora puede hacer algo como:

select dbo.VMConfigVal(MyXMLConfig, '/hardware/devices/IDE/ChannelCount') 
from someTable 

sweet!

3

Su selección devuelve el valor de @path porque sql:variable() devuelve un valor literal, por lo que en realidad le pide al servidor SQL que seleccione el valor literal @path del documento, lo que hace. La única forma que conozco de hacer lo que quiere se utiliza SQL dinámico, así:

declare @xml xml = ' 
<root> 
    <element attr="test">blah</element> 
</root>'; 

declare @p nvarchar(max) = '(//element/text())[1]'; 
declare @sql nvarchar(max) 
    = 'select @x.value(''' + @p + ''', ''nvarchar(max)'')'; 

exec sp_executesql @sql, @parameters = N'@x xml', @x = @xml; 

Pero te advierto que esto no es muy buena práctica (piensa en las inyecciones SQL, entrada de validación, etc.)

+0

No tengo suerte porque estoy tratando de hacer esto dentro de un UDF. grrr ... (gracias) – ekkis

+0

hmm ... alternativamente, puedo hacer algo como '/ my/path/mytag [@ someattr = sql: variable (" @ myattr ")] ... así que ¿cómo podría crear un consulta que busca una ruta/etiqueta determinada en lugar de buscar un atributo en la etiqueta? – ekkis

+0

Puede hacer esto: seleccione '@ xml.value ('(// * [local-name() = sql: variable (" @ elementname ")]) [1]', 'nvarchar (max)')', con '@elementname = 'element''. Tal vez con algunos hackeos puedes hacer que busque por ruta, pero no puedo decirte con seguridad sin saber más detalles. –

1

Si sólo necesita encontrar un elemento de niño por su nombre y quiere abstraer el nombre de la XPath literal aquí hay algunas opciones:

// Returns the /root/node/element/@Value with @Name contained in @AttributeName SQL variable. 
SELECT @Xml.value('(/root/node/element[@Name=sql:variable("@AttributeName")]/@Value)[1]', 'varchar(100)') 

// Returns the text of the child element of /root/node with the name contained in @ElementName SQL variable. 
SELECT @Xml.value('(/root/node/*[name(.)=sql:variable("@ElementName")]/text())[1]', 'varchar(100)') 

// Searching the xml hierarchy for elements with the name contained in @ElementName and returning the text(). 
SELECT @Xml.value('(//*[name(.)=sql:variable("@ElementName")]/text())[1]', 'varchar(100)') 

tiene que declarar @ElementName o variable @AttributeName SQL para ejecutar estas. Probé la primera declaración, pero no he probado explícitamente las otras 2 afirmaciones, FYI.

+0

pero el punto era que no quería codificar la ruta al elemento – ekkis

+0

. Tuve mejor suerte con el nombre local() que con el nombre (.) Usando el formato sugerido en la segunda declaración. Este formato es mucho más agradable y ordenado que usar cross-apply, eso es seguro. – edhubbell

Cuestiones relacionadas