Querying XML column in SQL Server - 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:

Related

TSQL: Return elements of a group as XML

I try to aggregate values per group into one XML. I am on SQL Server 2016.
My raw-data looks like this (here strictly stiped down to the problem. My real values are not generic.):
create table #data (group_id int, value1 char(10), value2 char(10), value3 char(10));
insert #data values
(1,'a1', 'b1', 'c1'),
(1,'a2', 'b2', 'c2'),
(1,'a3', 'b3', 'c3'),
(2,'a4', 'b4', 'c4');
I am looking for an aggregate function that would return one XML (per group)
select
group_id,
**my_XML_aggregation** as [XML-values]
from #data
group by group_id
The expected result XML for the first group (group_id = 1) should look like (names of the elements are not relevant to the problem)
<group>
<row>
<value1>a1<value1>
<value2>b1<value1>
<value3>c1<value1>
</row>
<row>
<value1>a2<value1>
<value2>b2<value1>
<value3>c2<value1>
</row>
<row>
<value1>a3<value1>
<value2>b3<value1>
<value3>c3<value1>
</row>
</group>
I know how to aggregate a pattern-separated string. This would not do the job. Even to put the pattern-aggregated string into one XML element is no alternative. I am looking for the structured information within the XML.
Try like this:
drop table if exists #data ;
create table #data (group_id int, value1 varchar(10), value2 varchar(10), value3 varchar(10));
insert #data values
(1,'a1', 'b1', 'c1'),
(1,'a2', 'b2', 'c2'),
(1,'a3', 'b3', 'c3'),
(2,'a4', 'b4', 'c4');
with groups as
(
select distinct group_id from #data
)
select group_id,
(
select value1, value2, value3
from #data
where group_id = groups.group_id
for xml path, root('group'), type
) group_doc
from groups
outputs
group_id group_doc
----------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1 <group><row><value1>a1</value1><value2>b1</value2><value3>c1</value3></row><row><value1>a2</value1><value2>b2</value2><value3>c2</value3></row><row><value1>a3</value1><value2>b3</value2><value3>c3</value3></row></group>
2 <group><row><value1>a4</value1><value2>b4</value2><value3>c4</value3></row></group>

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...

Insert XML in SQL Server

Good afternoon all.
Currently, I have make a small demo with XML in SQl Server.
I have table with name: tb_xml_demo(ID, Name, Descr)
And each time when I insert to this table. ID column like this:
001, 002, 003 ....
This is my procedure
alter proc sp_xml_demo_cud
#p_xml xml
as
begin
declare #dochandle int;
exec sp_xml_preparedocument #dochandle output,#p_xml;
insert into tb_xml_demo(id, name, descr)
select
(select
format(isnull(convert(int, max(id)), 0) + 1, '000')
from tb_xml_demo) id,
name,
descr
from
OPENXML(#dochandle,'/root/item',2)
with
(name nvarchar(50),
descr nvarchar(50),
crud varchar(1)
)
end;
And this is my xml:
exec sp_xml_demo_cud
'<root>
<item>
<name>9876543</name>
<descr>1sdfsd</descr>
</item>
<item>
<name>333</name>
<descr>333</descr>
</item>
</root>';
And this is result after executing the procedure:
id Name Descr
001 9876543 1sdfsd
001 333 333
Please help me.
Thanks a lot.
I would recommend doing this:
create your table with a ID INT IDENTITY(1,1) column to let SQL Server handle the generation of unique ID values
add a computed column (I called it PaddedID) that uses the system-generated, valid ID to display with leading zeroes
parse the XML with the built-in, native XQuery functionality (instead of the legacy OPENXML stuff which is notorious for memory leaks)
This gives me this code:
-- create your table
CREATE TABLE tb_xml_demo
(ID INT IDENTITY(1,1) PRIMARY KEY CLUSTERED,
PaddedID AS RIGHT('0000' + CAST(ID AS VARCHAR(4)), 4) PERSISTED,
Name VARCHAR(50),
Descr VARCHAR(100)
)
-- declare your INPUT XML document
DECLARE #input XML = '<root>
<item>
<name>9876543</name>
<descr>1sdfsd</descr>
</item>
<item>
<name>333</name>
<descr>333</descr>
</item>
</root>'
-- parse the XML using XQuery and insert the results into that newly created table
INSERT INTO dbo.tb_xml_demo
(Name, Descr)
SELECT
ItemName = xc.value('(name)[1]', 'varchar(50)'),
ItemDescr = xc.value('(descr)[1]', 'varchar(100)')
FROM
#input.nodes('/root/item') AS XT(XC)
-- select the values from the table
SELECT * FROM dbo.tb_xml_demo
and this results in an output of:

Get updated row as XML

Is there any way to get the last updated row as XML in sql server?
Consider this snippet
DECLARE #table TABLE (
NAME NVARCHAR(255)
,Col2 INT
)
INSERT INTO #table
VALUES (
'a1'
,1
)
INSERT INTO #table
VALUES (
'a2'
,2
)
UPDATE #table
SET NAME = 'hello'
OUTPUT inserted.*
WHERE Col2 = 2
The above statements outputs the updated row. How can I output the row as XML, as I would do with a SELECT statement?
I tried the SELECT syntax, but was unsuccessful.
UPDATE #table
SET NAME = 'hello'
OUTPUT (inserted.* FOR XML AUTO)
WHERE Col2 = 2
Is there any way to accomplish this other than writing it to a table and selecting from the table?
Is there any way to accomplish this other than writing it to a table
and selecting from the table?
Not that I know of. Create a table variable where you insert the output from the update and then use that to create the XML output you want.
DECLARE #table TABLE
(
NAME NVARCHAR(255),
Col2 INT
);
INSERT INTO #table VALUES ('a1', 1);
INSERT INTO #table VALUES ('a2', 2);
DECLARE #updateresult TABLE
(
NAME NVARCHAR(255),
Col2 INT
);
UPDATE #table
SET NAME = 'hello'
OUTPUT inserted.NAME,
inserted.Col2
INTO #updateresult(Name, Col2)
WHERE Col2 = 2
SELECT NAME, Col2
FROM #updateresult
FOR XML PATH('row');

SQL XML Attribute value

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;

Resources