SQL XML Attribute value - sql-server

I'm trying to figure out how to get an XML attributes value when the element name changes. The attribute will be the same regardless of the element.
<obj1 id="1" name="sally" />
<obj2 id="15" date="yesterday" />
I've been trying various forms of this, but it's not working:
SELECT
[OriginalRecordXml].value('(/./id)[1]', 'varchar(MAX)')
FROM [AuditRecords]
Is this possible?

Something like this does the trick:
declare #t table (
Id int identity(1,1) primary key,
XMLData xml not null
);
insert into #t (XMLData)
values (N'<obj1 id="1" name="sally" />
<obj2 id="15" date="yesterday" />'),
(N'<objM />');
select t.Id, x.c.value('./#id', 'varchar(max)')
from #t t
cross apply t.XMLData.nodes('//*[#id]') x(c);
Or, you can save a little bit if you only need value from first / single node:
select t.Id, t.XMLData.value('/*[#id][1]/#id', 'varchar(max)')
from #t t;

Related

Iterate values on xml nodes SQL Server

I'm trying to extract all the values ​​from each XML node. For example for the <Cash> node, I would like to recover the value (0,1) but I can not get it
CREATE TABLE #TableXML (Col1 INT PRIMARY KEY, Col2 XML)
INSERT INTO #TableXML
VALUES (1,
'<CookedUP>
<Evenement Calcul="16">
<Cookie xmlns="http://services.ariel.morneausobeco.com/2007/02" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<AlternateOptionTypeCodes />
<Cash>0</Cash>
<CashInterest>0</CashInterest>
<CashSource>Undefined</CashSource>
<Code>A</Code>
<SmallAmount>0</SmallAmount>
<SmallAmountType>Undefined</SmallAmountType>
</Cookie>
<Cookie xmlns="http://services.ariel.morneausobeco.com/2007/02" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<AlternateOptionTypeCodes />
<Cash>1</Cash>
<CashInterest>2</CashInterest>
<CashSource>Undefined</CashSource>
<Code>B</Code>
<SmallAmount>1</SmallAmount>
<SmallAmountType>1</SmallAmountType>
</Cookie>
</Evenement>
</CookedUP> '
)
WITH XMLNAMESPACES ('http://services.ariel.morneausobeco.com/2007/02' AS ns)
SELECT
b.Col1,
x.XmlCol.value('(ns:Cookie/ns:SmallAmount/text())[2]', 'int') AS SmallAmount,
x.XmlCol.value('(ns:Cookie/ns:Cash/text())[2]', 'int') AS Cash
FROM
#TableXML b
CROSS APPLY
b.Col2.nodes('CookedUP/Evenement') x(XmlCol);
Small tweak to what you already have and you got it.
The namespaces doesn't come into play until the Cookie node.
I update the example to a table variable, give this a try:
DECLARE #TableXML TABLE(Col1 int primary key, Col2 xml)
Insert into #TableXML values ( 1,
'<CookedUP>
<Evenement Calcul="16">
<Cookie xmlns="http://services.ariel.morneausobeco.com/2007/02" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<AlternateOptionTypeCodes />
<Cash>0</Cash>
<CashInterest>0</CashInterest>
<CashSource>Undefined</CashSource>
<Code>A</Code>
<SmallAmount>0</SmallAmount>
<SmallAmountType>Undefined</SmallAmountType>
</Cookie>
<Cookie xmlns="http://services.ariel.morneausobeco.com/2007/02" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<AlternateOptionTypeCodes />
<Cash>1</Cash>
<CashInterest>2</CashInterest>
<CashSource>Undefined</CashSource>
<Code>B</Code>
<SmallAmount>1</SmallAmount>
<SmallAmountType>1</SmallAmountType>
</Cookie>
</Evenement>
</CookedUP> '
)
;WITH XMLNAMESPACES ('http://services.ariel.morneausobeco.com/2007/02' AS ns)
SELECT b.Col1
--Also include the namespaces here with ns: for each "field" you are after contained within Cookie
,XmlCol.value('(./ns:SmallAmount)[1]', 'int') AS SmallAmount
,XmlCol.value('(./ns:Cash)[1]', 'int') AS Cash
,XmlCol.value('(./ns:Code)[1]', 'nvarchar(10)') AS Code
,XmlCol.value('(./ns:CashSource)[1]', 'nvarchar(10)') AS CashSource
FROM #TableXML b
CROSS APPLY b.Col2.nodes('/CookedUP/Evenement/ns:Cookie') AS x(XmlCol) --ns, the namespace, only at Cookie node.

SQL Cross Apply no results

I have ugly XML that looks like this:
<?xml version="1.0"?>
<MainTag xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" RecordID="201801026543210">
<Field Name="TheFieldName">
<FieldValue>Field Contents</FieldValue>
<ListTag>
<ListItem>
<Value>This List Value</Value>
<Source>source.txt</Source>
<ListType>text</ListType>
<ItemNumber>6912</ItemNumber>
<MoreData>some text here</MoreData>
<Address>address data</Address>
<Ranking>102</Ranking>
</ListItem>
<ListItem>
<Value>Another List Value</Value>
<Source>other.txt</Source>
<ListType>text</ListType>
<ItemNumber>7919</ItemNumber>
<MoreData>more text here</MoreData>
<Address>address data</Address>
<Ranking>41</Ranking>
</ListItem>
</ListTag>
</Field>
</MainTag>
What I want is query results that gives me a spreadsheet, essentially:
RecordID FieldName FieldValue ListValue ListSource ListType ListItemNumber …
201801026543210 TheFieldName Field Contents This List Value source.txt text 6912
201801026543210 TheFieldName Field Contents Another List Value other.txt text 7919
To aid in the fun, the XML is stored in a varchar field, not an XML field.
As example data and queries I've tried:
DECLARE #Tbl TABLE (
TblID varchar(15)
, Fld varchar(max)
)
INSERT INTO #Tbl SELECT '201801026543210', '<?xml version="1.0"?><MainTag xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" RecordID="201801026543210"><Field Name="TheFieldName"><FieldValue>Field Contents</FieldValue><ListTag><ListItem><Value>This List Value</Value><Source>source.txt</Source><ListType>text</ListType><ItemNumber>6912</ItemNumber><MoreData>some text here</MoreData><Address>address data</Address><Ranking>102</Ranking></ListItem><ListItem><Value>Another List Value</Value><Source>other.txt</Source><ListType>text</ListType><ItemNumber>7919</ItemNumber><MoreData>more text here</MoreData><Address>address data</Address><Ranking>41</Ranking></ListItem></ListTag></Field></MainTag>'
-- this shows that i am reading the main tag
SELECT t.r.value('#RecordID','varchar(15)') AS RecordID
, t.r.query('.') as fullvalue
FROM (
SELECT top 10 CONVERT(xml, Fld) AS Fld, TblID
FROM #Tbl
) AS s
CROSS APPLY Fld.nodes('/MainTag') AS t(r)
-- and this works to read the attribute from the first field
SELECT t.r.value('#RecordID', 'varchar(18)') AS RecordID
, f.r.value('#Name', 'varchar(100)') AS Field
FROM (
SELECT top 10 CONVERT(xml, Fld) AS Fld, TblID
FROM #Tbl
) AS s
CROSS APPLY Fld.nodes('/MainTag') AS t(r)
CROSS APPLY Fld.nodes('/MainTag/Field') AS f(r)
-- this does NOT work to read the second field
SELECT t.r.value('#RecordID','varchar(18)') AS RecordID
, f.r.value('.', 'varchar(100)') AS ValueF
, p.r.query('.') AS QueryP
, p.r.value('.', 'varchar(100)') AS ValueP
FROM (
SELECT top 10 CONVERT(xml, Fld) AS Fld, TblID
FROM #Tbl
) AS s
CROSS APPLY Fld.nodes('/MainTag') AS t(r)
CROSS APPLY Fld.nodes('/MainTag/Field') AS f(r)
CROSS APPLY Fld.nodes('/MainTag/FieldValue') AS p(r)
-- i honestly feel like this should be using nodes off of the parent nodes method
-- , but this is NOT working either
SELECT t.r.value('#RecordID','varchar(18)') AS RecordID
, p.r.query('.') AS FieldValue
FROM (
SELECT top 10 CONVERT(xml, Fld) AS Fld, TblID
FROM #Tbl
) AS s
CROSS APPLY s.Fld.nodes('/MainTag') AS t(r)
CROSS APPLY t.r.nodes('/MainTag/FieldValue') AS p(r)
I've seen some other questions/samples that grab just the first entry. I do not want that. I want a big blob of data. I want the unique fields to repeat for each "ListItem". To say that another way: I know this is akin to a one-to-many join where the fields in the "one" table will repeat for each row in the "many" table.
Technically, in my data there may be more than one "Field" as well.
There is only one FieldValue for that field.
There is one or more ListItems.
Try it with this query:
SELECT s.Fld.value('(/MainTag/#RecordID)[1]','bigint') AS RecordID
,A.f.value('#Name','nvarchar(max)') as FieldName
,A.f.value('(FieldValue/text())[1]','nvarchar(max)') as FieldValue
,B.li.value('(Value/text())[1]','nvarchar(max)') as ListValue
,B.li.value('(Source/text())[1]','nvarchar(max)') as ListSource
--and so on
FROM (
SELECT top 10 CONVERT(xml, Fld) AS Fld, TblID
FROM #Tbl
) AS s
CROSS APPLY Fld.nodes('/MainTag/Field') AS A(f)
CROSS APPLY A.f.nodes('ListTag/ListItem') AS B(li)
Hint
If there is any chance to change storage from VARCHAR to XML it's worth it...

Generate a XML and update it in a table in sql server without using cursor

I'm using a cursor to generate a XML file for each row. I'm selecting a list of IDs from the xml_temp_table. Then I have to update these XML files in another table (LOCATION_TABLE_XML). I've realized this cursor is not efficient if I have more than 20K rows to update. Thanks, I really appreciate it.
DECLARE #xml_var XML;
DECLARE #ID INT;
DECLARE XML_CURSOR CURSOR FOR
SELECT id
FROM xml_temp_table
WHERE id IS NOT NULL;
OPEN XML_CURSOR;
FETCH NEXT
FROM XML_CURSOR
INTO #ID;
WHILE ##FETCH_STATUS = 0
BEGIN
SET #xml_var =
(
SELECT
(
SELECT 'Type' AS ID,
'Initial' AS VALUE,
'' AS TAG,
'true' AS VISIBLE,
Getdate() AS HISTORY,
'' AS DESCRIPTION,
'' AS COMMENT
FROM XML_TABLE d
WHERE D.XML_ID = #ID FOR XML PATH('field'),
TYPE ) AS '[*]',
(
SELECT 'OwnerName' AS ID,
'Testing_XML' AS VALUE,
'' AS TAG,
'true' AS VISIBLE,
Getdate() AS HISTORY,
'' AS DESCRIPTION,
'' AS COMMENT
FROM XML_TABLE d
WHERE D.XML_ID = #ID FOR XML PATH('field'),
TYPE ) AS '[*]'
FROM XML_TABLE p
WHERE P.XML_ID = #ID FOR XML PATH('Material'),
ROOT('FormValue') );
UPDATE S
SET S.XML_COL = #xml_var,
FROM LOCATION_TABLE_XML S
WHERE S.ID = #ID;
FETCH NEXT
FROM XML_CURSOR
INTO #ID;
END;
The Required output
<FormValue>
<Material>
<field>
<id>Type</id>
<value>Initial</value>
<tag />
<visible>true</visible>
<history>2016-11-08</history>
<description />
<comment />
</field>
<field>
<id>OwnerName</id>
<value>Testing_XML</value>
<tag />
<visible>true</visible>
<history>2016-11-08</history>
<description />
<comment />
</field>
</Material>
</FormValue>
One thing for sure: You do not need a CURSOR here...
Cursors are bad and evil... They are invented by the data devil to lead us poor developers away from the light of set-based thinking. It's pulling you down into the dark acres of procedural pain... (Well, there are some cases, where a CURSOR is the right choice, but these are rare...)
I've stolen the test scenario from John Cappeletti (thx John! +d you...) and think this can be simplified:
No need for the CROSS APPLY... As the XML is a scalar value it can be used directly. And there's no need to join the CTE back to #YourTable as the CTE is updateable itself:
Declare #YourTable table (ID int,Active bit,First_Name varchar(50),Last_Name varchar(50),EMail varchar(50),XMLData xml)
Insert into #YourTable values
(1,1,'John','Smith','john.smith#email.com',null),
(2,0,'Jane','Doe' ,'jane.doe#email.com',null)
;with cte as
(
Select A.ID
,A.XMLData
,(
-- This would be your XML generation
-- Notice the reference to A.ID
Select XMLData = (Select * From #YourTable Where ID=A.ID For XML Path('root'))
) AS NewXML
From #YourTable A
)
Update cte Set XMLData = NewXML;
Select * from #YourTable
But this can be put even simpler: I need to create the table physically now, since we'd need a table's alias otherwise...
CREATE TABLE YourTable(ID int,Active bit,First_Name varchar(50),Last_Name varchar(50),EMail varchar(50),XMLData xml);
Insert into YourTable values
(1,1,'John','Smith','john.smith#email.com',null),
(2,0,'Jane','Doe' ,'jane.doe#email.com',null);
UPDATE YourTable SET XMLData= (
-- This would be your XML generation
Select x.* From YourTable AS x Where x.ID=YourTable.ID For XML Path('root')
)
Select * from YourTable
GO
DROP TABLE YourTable;
Attention 1: I understand your sentence I'm selecting a list of IDs from the xml_temp_table that you need to filter this process to a list of IDs. If so, just add
WHERE YourTable.ID IN(SELECT filter.ID FROM SomeWhere AS filter)
Attention 2: The sentence Then I have to update these XML files in another table is not clear to me... If you want to create these rows in the other table, just use
INSERT INTO OtherTable(col1,col2,...) SELECT col1,col2, ... FROM ...
If the other table has got corresponding rows with the IDs given, you can easily change the statements to correspond to this. If you need help, just come back...

Query (SQL Server) to get value from node in XML column stored as varchar(max)?

I hate dealing with XML queries. I don't do it often enough to remember how to format everything, but I'm at my wits' end.
Let's say I have a table called Messages and a column inside it called Payload. Payload contains XML stored as varchar(max). The XML is formatted as such:
<NCOAPACP xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="uri://www.gen.da.hob/VC/Contract/QualityManagement/EMSM">
<VID>3656183</VID>
</NCOAPACP>
How do I query the table to retrieve a list of the values in the VID node?
Try this:
declare #w as xml = cast('
<NCOAPACP xmlns:i=''http://www.w3.org/2001/XMLSchema-instance''
xmlns="uri://www.gen.da.hob/VC/Contract/QualityManagement/EMSM">
<VID>3656183</VID>
</NCOAPACP>' as xml)
SELECT #w.value('declare namespace ns="uri://www.gen.da.hob/VC/Contract/QualityManagement/EMSM";
ns:NCOAPACP[1]/ns:VID[1]','varchar(10)') AS VID
If you have multiple VID node you can try this:
declare #w as xml = cast('
<NCOAPACP xmlns:i=''http://www.w3.org/2001/XMLSchema-instance''
xmlns="uri://www.gen.da.hob/VC/Contract/QualityManagement/EMSM">
<VID>3656183</VID>
<VID>454545</VID>
</NCOAPACP>' as xml)
SELECT VID.value('.','varchar(10)')
from #w.nodes(
'declare namespace ns="uri://www.gen.da.hob/VC/Contract/QualityManagement/EMSM";
ns:NCOAPACP[1]/ns:VID'
)
AS C(VID)
If XML data are in a table column then the solution is a little more tricky. Especially if data type is varchar(max).
declare #tbl table(id int,payload varchar(max))--sample table
--sample data
insert #tbl values
(1,'<NCOAPACP xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="uri://www.gen.da.hob/VC/Contract/QualityManagement/EMSM">
<VID>3656183</VID>
<VID>3656184</VID>
</NCOAPACP>'),
(2,'<NCOAPACP xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="uri://www.gen.da.hob/VC/Contract/QualityManagement/EMSM">
<VID>123456</VID>
<VID>987654</VID>
</NCOAPACP>')
--it is convenient to define namespaces before the query
;with xmlnamespaces('uri://www.gen.da.hob/VC/Contract/QualityManagement/EMSM' as x)--we will use this x:
select id, t.vid.value('.[1]','varchar(20)') vid
-- . means self. [1] ensure single value
from
--convert varchar(max) to xml first
(select id,cast(payload as xml) payload from #tbl) tt
cross apply
--convert xml to a tabular form
tt.payload.nodes('//x:VID') t(vid)
Results:
id vid
1 3656183
1 3656184
2 123456
2 987654
U can try this
DECLARE #Messages TABLE(ID INT, Payload NVARCHAR(MAX))
INSERT INTO #Messages(ID, Payload) VALUES(1, '<NCOAPACP xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="uri://www.gen.da.hob/VC/Contract/QualityManagement/EMSM"> <VID>3656183</VID> </NCOAPACP>')
INSERT INTO #Messages(ID, Payload) VALUES(2, '<NCOAPACP xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="uri://www.gen.da.hob/VC/Contract/QualityManagement/EMSM"> <VID>3656183</VID> <VID>3656184</VID> </NCOAPACP>')
;WITH CTE(ID, Payload) AS
(
SELECT ID, CAST(Payload AS XML)
FROM #Messages
)
SELECT
ID,
x.items.value('.', 'NVARCHAR(10)')
FROM CTE
CROSS APPLY Payload.nodes('declare namespace xx="uri://www.gen.da.hob/VC/Contract/QualityManagement/EMSM"; xx:NCOAPACP/xx:VID') as x(items)
The most simple way (CTE test created just to get output, your xml value converted to varchar(max) as in your table, then SELECT *.value):
;WITH test AS (
SELECT CAST(
'<NCOAPACP xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="uri://www.gen.da.hob/VC/Contract/QualityManagement/EMSM">
<VID>3656183</VID>
</NCOAPACP>' as varchar(max)) as Payload
)
SELECT CAST(Payload as xml).value('(/*/*)[1]', 'int')
FROM test
Output:
3656183

Querying XML column in SQL Server

I'm running SQL Server 2008 R2, and have a table that I audit update statements.
create table STG_Participant_16_Month
(
Serial int,
ID varchar(10),
StartTime datetime,
FinishTime datetime,
ChildID varchar(10),
childIndex int,
Record_State varchar(15),
Duplicate_flag varchar(1)
);
When table X is updated, it inserts a record into an audit table:
select *
into STG_Participant_16_Month_AUDIT
from STG_Participant_16_Month;
alter table STG_Participant_16_Month_AUDIT
add audit_user varchar(30),
audit_action varchar(1),
audit_date datetime,
columns_updated xml;
I create a record and do an update:
insert into STG_Participant_16_Month
( Serial, ID, StartTime, FinishTime, ChildID, childIndex,
Record_State, Duplicate_flag )
values
( 90, 'ID', getdate(), getdate(), 'ChildID', 1,
'LOADED', 'N');
update STG_Participant_16_Month set serial=99, ID='xx', childIndex=99 where serial=90;
I see output as follows:
<Fields>
<Field Name="Serial" />
<Field Name="ID" />
<Field Name="childIndex" />
</Fields>
How do I create a query which just shows the text values?
Serial
ID
childIndex
IF that's your output, then it looks like your trigger is not populating the XML file correctly; I tried to guess what your output might look like, and built an xQuery SQL statement to demonstrate:
DECLARE #t TABLE ( x XML )
INSERT INTO #t
SELECT '<Fields> <Field Name="Serial">99</Field> <Field Name="ID">xx</Field> <Field Name="childIndex">99</Field> </Fields>'
SELECT x
FROM #t
SELECT Serial = x.value('data(for $f in //Field
where $f/#Name="Serial"
return $f)[1]', 'int')
, ID = x.value('data(for $f in //Field
where $f/#Name="ID"
return $f)[1]', 'varchar(2)')
, childIndex = x.value('data(for $f in //Field
where $f/#Name="childIndex"
return $f)[1]', 'int')
FROM #t
You can try something like this:
SELECT
UpdFld.value('(#Name)', 'varchar(20)')
FROM
STG_Participant_16_Month_AUDIT
CROSS APPLY
COLUMNS_UPDATED.nodes('/Fields/Field') AS Tbl(UpdFld)
It grabs a list of all the <Field> nodes inside the root <Fields> node, and extracts the Name attribute from those XML elements.
I get an output something like this:

Resources