问题
The requirement is to parse all the 27 values related to MarginRevenue/Cost/Value. As a standard scenario, these elements can come in any order and also can come in any number of time. In short, the XML is completely dynamic. The only point is Under Margins/Margin, there can be any number of MarginRevenue, MarginCost and MarginValue.
declare @xml xml = '<Margins>
<Margin type="type1" currencyCode="currencyCode1">
<MarginRevenue>1.1</MarginRevenue>
<MarginRevenue>1.2</MarginRevenue>
<MarginRevenue>1.3</MarginRevenue>
<MarginCost>2.1</MarginCost>
<MarginCost>2.2</MarginCost>
<MarginCost>2.3</MarginCost>
<MarginValue>3.1</MarginValue>
<MarginValue>3.2</MarginValue>
<MarginValue>3.3</MarginValue> </Margin>
<Margin type="type2" currencyCode="currencyCode2">
<MarginRevenue>1.4</MarginRevenue>
<MarginRevenue>1.5</MarginRevenue>
<MarginRevenue>1.6</MarginRevenue>
<MarginCost>2.4</MarginCost>
<MarginCost>2.5</MarginCost>
<MarginCost>2.6</MarginCost>
<MarginValue>3.4</MarginValue>
<MarginValue>3.5</MarginValue>
<MarginValue>3.6</MarginValue> </Margin>
<Margin type="type3" currencyCode="currencyCode3">
<MarginRevenue>1.7</MarginRevenue>
<MarginRevenue>1.8</MarginRevenue>
<MarginRevenue>1.9</MarginRevenue>
<MarginCost>2.7</MarginCost>
<MarginCost>2.8</MarginCost>
<MarginCost>2.9</MarginCost>
<MarginValue>3.7</MarginValue>
<MarginValue>3.8</MarginValue>
<MarginValue>3.9</MarginValue> </Margin>
</Margins>'
回答1:
Not quite clear what you're trying to achieve / what you're looking to get - but this XQuery will return all 27 values for you:
SELECT
Name = XC.value('local-name(.)', 'varchar(100)'),
Value = XC.value('.', 'varchar(100)')
FROM
@xml.nodes('/Margins/Margin/*') AS XT(XC)
Results in SSMS:
回答2:
It seems as if you want to map your data just on their position in the XML. I think so, because I've read and answered your previous question. In the comments I gave you a bunch of hints and questions you should provide as a new question (to avoid chameleons). This question now is very hard on the edge of - uhm - something with "impolite"...
Nevertheless I provide an answer but ask you clearly to invest some more time into your questions. You expect us to invest our time in good answers.
Your xml
declare @xml xml = '<Margins>
<Margin type="type1" currencyCode="currencyCode1">
<MarginRevenue>1.1</MarginRevenue>
<MarginRevenue>1.2</MarginRevenue>
<MarginRevenue>1.3</MarginRevenue>
<MarginCost>2.1</MarginCost>
<MarginCost>2.2</MarginCost>
<MarginCost>2.3</MarginCost>
<MarginValue>3.1</MarginValue>
<MarginValue>3.2</MarginValue>
<MarginValue>3.3</MarginValue> </Margin>
<Margin type="type2" currencyCode="currencyCode2">
<MarginRevenue>1.4</MarginRevenue>
<MarginRevenue>1.5</MarginRevenue>
<MarginRevenue>1.6</MarginRevenue>
<MarginCost>2.4</MarginCost>
<MarginCost>2.5</MarginCost>
<MarginCost>2.6</MarginCost>
<MarginValue>3.4</MarginValue>
<MarginValue>3.5</MarginValue>
<MarginValue>3.6</MarginValue> </Margin>
<Margin type="type3" currencyCode="currencyCode3">
<MarginRevenue>1.7</MarginRevenue>
<MarginRevenue>1.8</MarginRevenue>
<MarginRevenue>1.9</MarginRevenue>
<MarginCost>2.7</MarginCost>
<MarginCost>2.8</MarginCost>
<MarginCost>2.9</MarginCost>
<MarginValue>3.7</MarginValue>
<MarginValue>3.8</MarginValue>
<MarginValue>3.9</MarginValue> </Margin>
</Margins>';
The first approach uses a numbers table (in this case an on-the-fly-tally) to get a set of numbers. The query then uses this number with sql:column() to fetch the elements by their positions:
DECLARE @maxCount INT=
(
SELECT MAX(marg.value('count(MarginRevenue)','int'))
FROM @xml.nodes('Margins/Margin') A(marg)
);
WITH Tally AS(SELECT TOP(@maxCount) ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS Nmbr FROM master..spt_values) --select * from tally
SELECT Tally.Nmbr
,marg.value('@type','varchar(100)') AS margin_type
,marg.value('(MarginRevenue)[sql:column("Nmbr")][1]','decimal(15,5)') MarginRevenue
,marg.value('(MarginCost)[sql:column("Nmbr")][1]','decimal(15,5)') MarginCost
,marg.value('(MarginValue)[sql:column("Nmbr")][1]','decimal(15,5)') MarginValue
FROM Tally
OUTER APPLY @xml.nodes('Margins/Margin') A(marg)
ORDER BY marg.value('@type','varchar(100)'),Nmbr;
The second approach uses a serie of CTEs to get each "type" of data in a separate but numbered list. The three sets are combined with a JOIN on their positional index:
WITH margins AS
(
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS MarginIndex
,marg.query('MarginRevenue') AS MRevenues
,marg.query('MarginCost') AS MCosts
,marg.query('MarginValue') AS MValues
FROM @xml.nodes('Margins/Margin') A(marg)
)
,MRevs AS
(
SELECT ROW_NUMBER() OVER(PARTITION BY MarginIndex ORDER BY (SELECT NULL)) AS RevIndex
,MarginIndex
,x.value('text()[1]','decimal(15,5)') AS RevValue
FROM margins
CROSS APPLY MRevenues.nodes('./*') A(x)
)
,MCosts AS
(
SELECT ROW_NUMBER() OVER(PARTITION BY MarginIndex ORDER BY (SELECT NULL)) AS CostIndex
,MarginIndex
,x.value('text()[1]','decimal(15,5)') AS CostValue
FROM margins
CROSS APPLY MCosts.nodes('./*') A(x)
)
,MValues AS
(
SELECT ROW_NUMBER() OVER(PARTITION BY MarginIndex ORDER BY (SELECT NULL)) AS ValueIndex
,MarginIndex
,x.value('text()[1]','decimal(15,5)') AS ValueValue
FROM margins
CROSS APPLY MValues.nodes('./*') A(x)
)
SELECT m.MarginIndex
,r.RevIndex
,r.RevValue
,c.CostValue
,v.ValueValue
FROM margins m
INNER JOIN MRevs r ON m.MarginIndex=r.MarginIndex
INNER JOIN MCosts c ON r.MarginIndex=c.MarginIndex AND r.RevIndex=c.CostIndex
INNER JOIN MValues v ON r.MarginIndex=v.MarginIndex AND r.RevIndex=v.ValueIndex;
来源:https://stackoverflow.com/questions/51616198/xml-parsing-in-sql-server