问题
I have the following XML data, which I have zero control over. Note that it's basically a collection of property groups. I need to select the value of one property where the value of another property is 'true'. The problem is, there's nothing to group on, and I cannot figure out how to associate things correctly.
Here's the XML Data, and the query I came up with so far:
DECLARE @xml xml = '
<Container>
<Collection>
<ItemName>SomeItem</ItemName>
<IsDeletable>true</IsDeletable>
<IsPersisted>false</IsPersisted>
</Collection>
<Collection>
<ItemName>AnotherItem</ItemName>
<IsDeletable>true</IsDeletable>
<IsPersisted>true</IsPersisted>
<ExistsInDB>true</ExistsInDB>
</Collection>
<Collection>
<ItemName>ItemFoo</ItemName>
<IsDeletable>true</IsDeletable>
<IsPersisted>true</IsPersisted>
<ExistsInDB>true</ExistsInDB>
</Collection>
<Collection>
<ItemName>BarBazItem</ItemName>
<IsDeletable>true</IsDeletable>
<IsPersisted>true</IsPersisted>
<ExistsInDB>false</ExistsInDB>
</Collection>
</Container>
'
;WITH XmlStuff AS (
SELECT CAST(xmlShredded.colXmlItem.query('local-name(.)') AS nvarchar(4000)) as XmlNodeName,
xmlShredded.colXmlItem.value('.', 'nvarchar(4000)') AS XmlNodeValue
FROM @xml.nodes('/*/Collection/child::node()') as xmlShredded(colXmlItem)
)
SELECT *
FROM XmlStuff
Now, what I need to do, is get the "ItemName" value for each grouping where "ExistsInDB" is 'true'. Note that the "ExistsInDB" property doesn't exist in the first property collection (i.e. it should be considered NULL/false).
So in this case, I need to query this xml data, and get back the following set:
AnotherItem ItemFoo
I should NOT get "SomeItem" or "BarBazItem".
I've been beating my head against the desk trying to figure out how to formulate the query for "Get all ItemName values where the associated ExistsInDB value is both present, and true".
Is this even possible?
回答1:
Hi perhaps try a sibling match
/Container/Collection/ItemName[../ExistsInDB='true']
That gets ItemName elements whose parents contain an ExistsInDb child equal to "true".
DECLARE @xml xml = '
<Container>
<Collection>
<ItemName>SomeItem</ItemName>
<IsDeletable>true</IsDeletable>
<IsPersisted>false</IsPersisted>
</Collection>
<Collection>
<ItemName>AnotherItem</ItemName>
<IsDeletable>true</IsDeletable>
<IsPersisted>true</IsPersisted>
<ExistsInDB>true</ExistsInDB>
</Collection>
<Collection>
<ItemName>ItemFoo</ItemName>
<IsDeletable>true</IsDeletable>
<IsPersisted>true</IsPersisted>
<ExistsInDB>true</ExistsInDB>
</Collection>
<Collection>
<ItemName>BarBazItem</ItemName>
<IsDeletable>true</IsDeletable>
<IsPersisted>true</IsPersisted>
<ExistsInDB>false</ExistsInDB>
</Collection>
</Container>
';
SELECT node.value('.', 'nvarchar(100)')
FROM @xml.nodes('/Container/Collection/ItemName[../ExistsInDB="true"]') AS x(node)
回答2:
Your expected output is a bit irritating, as you mention BarBazItem twice
So in this case, I need to query this xml data, and get back the following set:
AnotherItem ItemFoo BarBazItem
I should NOT get "SomeItem" or "BarBazItem".
Both existing answers are using backward-navigation (ther parent-axis via ../
). This is very bad performing (find details here) and not the best approach for you. Try this:
SELECT nd.value('(ItemName/text())[1]','nvarchar(100)') AS ItemName
FROM @xml.nodes('/Container/Collection[ExistsInDB/text()="true"]') A(nd);
The idea is, to place the predicate one level up behind <Collection>
. You can read this as
- Dive into
<Container>
- Dive deeper into
<Collection>
, but we want only nodes, where there is a sub-node<ExistsInDB>
with atext()
of "true". - The list returned by
A
will include one row per<Collection>
fullfilling the predicate (=filter) - the
.value()
-method will now take at the first level below<Collection>
the<ItemName>
and return thetext()
-node.
My query returns AnotherItem and ItemFoo
SomeItem is not returned as there is no <ExistsInDB>
and BarBazItem is not returned as there is "false" in <ExistsInDB>
.
UPDATE
Lukasz Szoda added a fiddle with some enhancements. I think the easiest to read the whole everything was this:
SELECT nd.value('(ItemName/text())[1]','nvarchar(100)') AS ItemName
,nd.value('(IsDeletable/text())[1]','bit') AS IsDeletable
,nd.value('(IsPersisted/text())[1]','bit') AS IsPersisted
FROM @xml.nodes('/Container/Collection[ExistsInDB/text()="true"]') A(nd);
回答3:
You could use:
;WITH XmlStuff AS (
SELECT CAST(xmlShredded.colXmlItem.query('local-name(.)') AS nvarchar(4000)) as XmlNodeName,
xmlShredded.colXmlItem.value('.', 'nvarchar(4000)') AS XmlNodeValue,
xmlShredded.colXmlItem.value('(../ExistsInDB)[1]', 'nvarchar(4000)') AS ExistsInDB
FROM @xml.nodes('/*/Collection/child::node()') as xmlShredded(colXmlItem)
)
SELECT *
FROM XmlStuff
WHERE ExistsInDB = 'true';
db<>fiddle demo
来源:https://stackoverflow.com/questions/55977004/query-xml-data-for-information-based-on-a-sibling-value