This problem keeps messing around with my Friday afternoon:
I have this XML:
declare #xml as XML
set #xml =
'<fields>
<field>
<id>1</id>
<items>
<item>
<name>name1_1</name>
<value>value1_1</value>
</item>
<item>
<name>name1_2</name>
<value>value1_2</value>
</item>
</items>
</field>
<field>
<id>2</id>
<items>
<item>
<name>name2_1</name>
<value>value2_1</value>
</item>
<item>
<name>name2_2</name>
<value>value2_2</value>
</item>
</items>
</field>
</fields>'
Using T-SQL and XPath, I need a query to get this result:
id name value
1 name1_1 value1_1
1 name1_2 value1_2
2 name2_1 value2_1
2 name2_2 value2_2
I'm getting name and value with:
SELECT c.value('name[1]', 'nvarchar(255)') name,
c.value('value[1]', 'nvarchar(255)') value
FROM #xml.nodes('fields/field/items/item') t(c)
...but how to insert the parent column "id"?
Your own code uses .nodes() to get a derived table from repeating elements. In your case there are two levels of repeating elements:
many fields and within each field
many items
You have to use .nodes() twice:
SELECT fld.value(N'(id/text())[1]',N'int') AS FieldID
,itm.value(N'(name/text())[1]',N'nvarchar(max)') AS ItemName
,itm.value(N'(value/text())[1]',N'nvarchar(max)') AS ItemValue
FROM #xml.nodes(N'/fields/field') AS A(fld)
OUTER APPLY A.fld.nodes(N'items/item') AS B(itm);
The first .nodes() comes back with XML fragments, one for each field, the second node is called for each of these field-fragments to pick their items.
Use OUTER APPLY if there might be fields without <item> nodes and CROSS APPLY when you do not want to see fields without <item> nodes (similar to LEFT JOIN vs INNER JOIN)
Assumption: there is only one id element per field.
SELECT c.value('../../id[1]', 'int') id,
c.value('name[1]', 'nvarchar(255)') name,
c.value('value[1]', 'nvarchar(255)') value
FROM #xml.nodes('fields/field/items/item') t(c)
The .. operator means "select parent of node" in XPATH. So the query will select the parent of item, then the parent of items, then the first child node id
Related
I have a table which contains a XML column and I need to get a value from the XML.
<ArrayOfItem>
<Item>
<Key>Member_Claim_Id</Key>
<Value>1802538</Value>
</Item>
<Item>
<Key>Reverify</Key>
<Value>0</Value>
</Item>
<Item>
<Key>RequestNumber</Key>
<Value>First Request</Value>
</Item>
</ArrayOfItem>
Sometimes Reverify key will be present in the XML document, and other times it won't. The document can contain other key / value pairs as well.
But RequestNumber key / value pair will always be present, but it might be the second or third key / value item in the document. So I could have:
<ArrayOfItem>
<Item>
<Key>Member_Claim_Id</Key>
<Value>1802538</Value>
</Item>
<Item>
<Key>RequestNumber</Key>
<Value>First Request</Value>
</Item>
</ArrayOfItem>
Currently I am using this:
SELECT TOP 10 *
FROM dbo.myTable
WHERE Parameters.value('(/ArrayOfItem/Item/Value)[2]', 'varchar(max)') LIKE '%revision%'
ORDER BY Id DESC
But I was assuming that RequestNumber was always the 2nd Key/Value Item in the document, but I just learned that that is not always the case.
Let's say the table looks like:
CREATE TABLE dbo.myTable
(
Parameters XML NOT NULL,
Field1 VARCHAR(50) NULL
)
INSERT INTO dbo.myTable (Parameters, Field1)
VALUES
( '<ArrayOfItem>
<Item>
<Key>Member_Claim_Id</Key>
<Value>1802538</Value>
</Item>
<Item>
<Key>Reverify</Key>
<Value>0</Value>
</Item>
<Item>
<Key>RequestNumber</Key>
<Value>First Request</Value>
</Item>
</ArrayOfItem>', -- XMLParameters - xml
'myText' -- Field1 - varchar(50)
)
and I want the value of /ArrayOfItem/Item/Value where the key is RequestNumber.
Thank you for your help.
Please try the following solution.
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (ID INT IDENTITY PRIMARY KEY, Parameters XML NOT NULL);
INSERT INTO #tbl (Parameters) VALUES
(N'<ArrayOfItem>
<Item>
<Key>Member_Claim_Id</Key>
<Value>1802538</Value>
</Item>
<Item>
<Key>Reverify</Key>
<Value>0</Value>
</Item>
<Item>
<Key>RequestNumber</Key>
<Value>First Request</Value>
</Item>
</ArrayOfItem>');
-- DDL and sample data population, end
DECLARE #param VARCHAR(30) = 'RequestNumber';
SELECT ID
, c.value('(Key/text())[1]', 'VARCHAR(30)') AS [Key]
, c.value('(Value/text())[1]', 'VARCHAR(30)') AS [Value]
FROM #tbl
CROSS APPLY Parameters.nodes('/ArrayOfItem/Item[Key[text()=sql:variable("#param")]]') AS t(c);
Output
+----+---------------+---------------+
| ID | Key | Value |
+----+---------------+---------------+
| 1 | RequestNumber | First Request |
+----+---------------+---------------+
I have a table with 70K records with a column XMLMetadata - that column holds all the 70k xml data.
I need a way to extra a item from the xml columns for all 70K records. The item name that I need to pull from all 70k is <Item Name="DocTitle" Type="String">.
Is there a way I can easily pull this?
<Metadata>
<Item Name="ID" Type="String">1364416</Item>
<Item Name="Name" Type="String">website</Item>
<Item Name="Type" Type="String">WebContent</Item>
<Item Name="Title" Type="String">Close Out Letter 11/1/17</Item>
<Item Name="Author" Type="String">Seba</Item>
....
</Metadata>
Try this query
SELECT
XMLMetadata.value('(/Metadata/node())[1]', 'nvarchar(max)') as ID,
XMLMetadata.value('(/Metadata/node())[2]', 'nvarchar(max)') as Name,
XMLMetadata.value('(/Metadata/node())[3]', 'nvarchar(max)') as Type,
XMLMetadata.value('(/Metadata/node())[4]', 'nvarchar(max)') as Title,
XMLMetadata.value('(/Metadata/node())[5]', 'nvarchar(max)') as Author
FROM [myTable]
If you want to get all items with the name, type and value, you could use something like this:
SELECT
ItemName = XC.value('(#Name)', 'varchar(20)'),
ItemType = XC.value('(#Type)', 'varchar(20)'),
ItemValue = XC.value('(.)', 'varchar(50)')
FROM
dbo.YourTableNameHere
CROSS APPLY
XmlMetadata.nodes('/Metadata/Item') AS XT(XC)
and if you want to get just a single value, based on the Name attribute, you could use this code here:
SELECT
ItemValue = XmlMetadata.value('(/Metadata/Item[#Name="Title"]/text())[1]', 'varchar(50)')
FROM
dbo.YourTableNameHere
I'm trying to build some query to export data in XML and I build this query:
select
[invoice].*,
[rows].*,
[payment].payerID,
[items].picture
from InvoicesHeader [invoice]
join InvoicesRows [rows] on [rows].invoiceID=[invoice].invoiceID
join Payments [payments] on [payments].paymentID=[invoice].paymentID
join Items [items] on [items].itemID=[rows].itemID
FOR XML Auto, ROOT ('invoices'), ELEMENTS
and I got something like this as result
<invoices>
<invoice>
<ID>82</ID>
<DocType>R</DocType>
<DocYear>2017</DocYear>
<DocNumber>71</DocNumber>
<IssueDate>2017-07-17T15:17:30.237</IssueDate>
<OrderID>235489738019</OrderID>
...
<payments>
<payerID>3234423f33</payerID>
<rows>
<ID>163</ID>
<ItemID>235489738019</ItemID>
<Quantity>2</Quantity>
<Price>1</Price>
<VATCode>22</VATCode>
<Color>-</Color>
<Size></Size>
<SerialNumber></SerialNumber>
<items>
<picture>http://nl.imgbb.com/AAOSwOdpXyB4I.JPG</picture>
</items>
</rows>
....
</payments>
</invoice>
</invoices>
while I would like to have something like this where
[rows] is childnode of invoice and not of payments
<invoices>
<invoice>
<ID>82</ID>
<DocType>R</DocType>
<DocYear>2017</DocYear>
<DocNumber>71</DocNumber>
<IssueDate>2017-07-17T15:17:30.237</IssueDate>
<OrderID>235489738019</OrderID>
...
<payments>
<payerID>3234423f33</payerID>
</payments>
<rows>
<ID>163</ID>
<ItemID>235489738019</ItemID>
<Quantity>2</Quantity>
<Price>1</Price>
<VATCode>22</VATCode>
<Color>-</Color>
<Size></Size>
<SerialNumber></SerialNumber>
<items>
<picture>http://nl.imgbb.com/AAOSwOdpXyB4I.JPG</picture>
</items>
</rows>
....
</invoice>
</invoices>
seen some solution where there are many
FOR XML AUTO
put all together, but the data here comes from connected table, would be a pity to re-query 2-3 times same values
how can achieve it?
Thanks
Try changing the select order around to this;
select
[invoice].*,
[payment].payerID,
[items].picture,
[rows].*
from InvoicesHeader [invoice]
join InvoicesRows [rows] on [rows].invoiceID=[invoice].invoiceID
join Payments [payments] on [payments].paymentID=[invoice].paymentID
join Items [items] on [items].itemID=[rows].itemID
FOR XML Auto, ROOT ('invoices'), ELEMENTS
well, found that have to use FOR XML PATH instead and add the other table as subquery with each FOR XML PATH as follows:
select
[invoice].*,
p.payerID,
(select r.* from InvoiceRows r where r.invoiceID=i.invoiceID for XML PATH ('rows'), type)
from InvoicesHeader i
join payment p on i.paymentID=p.paymentID
FOR XML PATH('invoice'), ROOT ('invoices'), ELEMENTS
I need to pull values from an XML column. The table contains 3 fields with one being an XML column like below:
TransID int,
Place varchar(20),
Custom XML
The XML column is structured as following:
<Fields>
<Field>
<Id>9346-00155D1C204E</Id>
<TransactionCode>0710</TransactionCode>
<Amount>5.0000</Amount>
</Field>
<Field>
<Id>A6F0-BA07EF3A7D43</Id>
<TransactionCode>0885</TransactionCode>
<Amount>57.9000</Amount>
</Field>
<Field>
<Id>9BDA-7858FD182Z3C</Id>
<TransactionCode>0935</TransactionCode>
<Amount>25.85000</Amount>
</Field>
</Fields>
I need to be able to query the xml column and return only the value for the <Amount> if there is a <Transaction code> = 0935. Note: there are records where this transaction code isn’t present, but it won't exist in the same record twice.
This is probably simple, but I’m having a problem returning just the <amount> value where the <transaction code> = 0935.
You can try this way :
DECLARE #transCode VARCHAR(10) = '0935'
SELECT field.value('Amount[1]', 'decimal(18,5)') as Amount
FROM yourTable t
OUTER APPLY t.Custom.nodes('/Fields/Field[TransactionCode=sql:variable("#transCode)"]') as x(field)
Alternatively, you can put logic for filtering Field by TransactionCode in SQL WHERE clause instead of in XPath expression, like so :
DECLARE #transCode VARCHAR(10) = '0935'
SELECT field.value('Amount[1]', 'decimal(18,5)') as Amount
FROM yourTable t
OUTER APPLY t.Custom.nodes('/Fields/Field') as x(field)
WHERE field.value('TransactionCode[1]', 'varchar(10)') = #transCode
SQL Fiddle Demo
You can use an XPath like this in your TSQL:
SELECT
*,
Custom.value('(/Fields/Field[#Name="Id"]/#Value)[1]', 'varchar(50)')
FROM YourTable
WHERE Custom.value('(/Fields/Field[#Name="Id"]/#Value)[1]', 'varchar(50)') = '0655'
In my SQL Server DB I have table with an XML column. The XML that goes in it is like the sample below:
<Rows>
<Row>
<Name>John</Name>
</Row>
<Row>
<Name>Debbie</Name>
</Row>
<Row>
<Name>Annie</Name>
</Row>
<Row>
<Name>John</Name>
</Row>
</Rows>
I have a requirement that I need to find the occurrence of all rows where the XML data has duplicate entries of <Name>. For example, above we have 'John' twice in the XML.
I can use the exist XML statement to find 1 occurrence, but how can I find if it's more than 1? Thanks.
To identify any table row that has duplicate <Name> values in its XML, you can use exist as well:
exist('//Name[. = preceding::Name]')
To identify which names are duplicates, respectively, you need nodes and CROSS APPLY
SELECT
t.id,
x.Name.value('.', 'varchar(100)') AS DuplicateName
FROM
MyTable t
CROSS APPLY t.MyXmlColumn.nodes('//Name[. = preceding::Name]') AS x(Name)
WHERE
t.MyXmlColumn.exist('//Name[. = preceding::Name]')
Try this:
;with cte as
(SELECT tbl.col.value('.[1]', 'varchar(100)') as name
FROM yourtable
CROSS APPLY xmlcol.nodes('/Rows/Row/Name') as tbl(col))
select name
from cte
group by name
having count(name) > 1
We first use the nodes function to convert from XML to relational data, then use value to get the text inside the Name node. We then put the result of the previous step into a CTE, and use a simple group by to get the value with multiple occurences.
Demo