问题
I need help with passing an XML path through a variable to the nodes() method. I have looked at several different posts and found that a node can be passed by using local-name
and sql:variable
. The example below works as expected:
DECLARE @XML_Path VARCHAR(MAX)
, @XML_In XML
SET @XML_Path = 'GetData'
SET @XML_In =
'
<GetData>
<test>234</test>
</GetData>
'
--THIS WORKS
SELECT Item_Idx = Nodes.value('(test)[1]' ,'INT')
FROM @XML_In.nodes('/*[local-name()=sql:variable("@XML_Path")]') Results(Nodes)
However, it does not work when I try to pass a path instead of a single node:
SET @XML_Path = 'GetData/Hello'
SET @XML_In =
'
<GetData>
<Hello>
<test>234</test>
</Hello>
</GetData>
'
--THIS DOES NOT WORK
SELECT Item_Idx = Nodes.value('(test)[1]' ,'INT')
FROM @XML_In.nodes('/*[local-name()=sql:variable("@XML_Path")]') Results(Nodes)
I need to be able to pass a path preceding another part of the path that is always constant. So, in my example, the "test" part would always be the same but the path above it would vary.
I suspect that local-name
is not the way to do this. But is there a different way I can do this?
Any help would be appreciated.
I've thought about using dynamic SQL, but this code would be used inside a UDF returning a table of results.
Edit:
I believe I was unclear about what I am trying to do. I need to be able to pass different XML paths into a function that uses them to shred XML.
Example 1
DECLARE @XML_In XML
SELECT @XML_In =
'
<RootNode1>
<ExtraNode1>
<Items>
<ExampleItem>
<SomeNode>100</SomeNode>
<Attributes>
<ID>1</ID>
<Name>a</Name>
<Value>123a</Value>
</Attributes>
</ExampleItem>
<ExampleItem>
<SomeNode>200</SomeNode>
<Attributes>
<ID>2</ID>
<Name>b</Name>
<Value>234</Value>
</Attributes>
</ExampleItem>
<ExampleItem>
<SomeNode>300</SomeNode>
<Attributes>
<ID>3</ID>
<Name>c</Name>
<Value>345</Value>
</Attributes>
</ExampleItem>
</Items>
</ExtraNode1>
</RootNode1>
'
SELECT SomeNode = Nodes.value('(../SomeNode)[1]' ,'INT')
, ID = Nodes.value('(ID)[1]' ,'INT')
, Name = Nodes.value('(Name)[1]' ,'VARCHAR(100)')
, Value = Nodes.value('(Value)[1]' ,'NVARCHAR(MAX)')
FROM @XML_In.nodes('/RootNode1/ExtraNode1/Items/ExampleItem/Attributes') Results(Nodes)
Example 2
DECLARE @XML_In XML
SELECT @XML_In =
'
<Example2Root>
<Entries>
<Entry>
<SomeNode>100</SomeNode>
<Fields>
<ID>1</ID>
<Name>a</Name>
<Value>123a</Value>
</Fields>
</Entry>
<Entry>
<SomeNode>200</SomeNode>
<Fields>
<ID>2</ID>
<Name>b</Name>
<Value>234</Value>
</Fields>
</Entry>
<Entry>
<SomeNode>300</SomeNode>
<Fields>
<ID>3</ID>
<Name>c</Name>
<Value>345</Value>
</Fields>
</Entry>
</Entries>
</Example2Root>
'
SELECT SomeNode = Nodes.value('(../SomeNode)[1]' ,'INT')
, ID = Nodes.value('(ID)[1]' ,'INT')
, Name = Nodes.value('(Name)[1]' ,'VARCHAR(100)')
, Value = Nodes.value('(Value)[1]' ,'NVARCHAR(MAX)')
FROM @XML_In.nodes('/Example2Root/Entries/Entry/Fields') Results(Nodes)
I want to be able to pass the path to the function so it can do this and the return it as a table:
SELECT SomeNode = Nodes.value('(../SomeNode)[1]' ,'INT')
, ID = Nodes.value('(ID)[1]' ,'INT')
, Name = Nodes.value('(Name)[1]' ,'VARCHAR(100)')
, Value = Nodes.value('(Value)[1]' ,'NVARCHAR(MAX)')
FROM @XML_In.nodes('SOME_PATH') Results(Nodes)
But I don't know how to go about using a parameter for the path. The path will be different most times.
回答1:
You do not want to change the whole function, just the path in XQuery. This is - as you know - not possible.
But: It seems that you are finding the same data in differing structures. At least you seem to know, that you will find nodes named ID
, Name
and Value
below a node SomeNode
...
So this approach might solve your issue in a completely different way... It works - at least - with your two given examples...
SELECT p.*
FROM
(
SELECT Nd.value('.','int') AS SomeNode
,Deeper.value('local-name(.)','nvarchar(max)') AS NodeName
,Deeper.value('.','nvarchar(max)') AS NodeValue
FROM @XML_In.nodes('//SomeNode') AS Sm(Nd)
OUTER APPLY Nd.nodes('parent::*/*[local-name(.)!="SomeNode"]/*') AS TwoLevels(Deeper)
) AS tbl
PIVOT
(
MIN(NodeValue) FOR NodeName IN(ID,Name,Value)
) AS p
In this solution your first working example shows, how you might use SomeNode
as variable...
回答2:
Add in a second forward slash to signify a deep search and only look for the parent node of the value you are after in your @XML_Path
variable:
DECLARE @XML_Path VARCHAR(MAX)
, @XML_In XML
SET @XML_Path = 'Hello'
SET @XML_In =
'
<GetData>
<Hello>
<test>234</test>
<test>567</test>
</Hello>
</GetData>
'
SELECT Item_Idx = Nodes.value('(test)[1]' ,'INT')
FROM @XML_In.nodes('//*[local-name()=sql:variable("@XML_Path")]') Results(Nodes)
SELECT Item_Idx = Nodes.value('(test)[2]' ,'INT')
FROM @XML_In.nodes('//*[local-name()=sql:variable("@XML_Path")]') Results(Nodes)
Alternatively, you can specify several levels and return all values:
declare @Node varchar(max)
declare @Attribute varchar(max)
set @Attribute = 'World'
set @Node = 'Hello'
select @XML_In.value('(/GetData
/*[local-name() = sql:variable("@Node")]
/*[local-name() = sql:variable("@Attribute")])[1]', 'int')
来源:https://stackoverflow.com/questions/39164962/sql-variable-containing-path-in-xml-node