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...
Related
I have data in XML in column in table
SELECT ObjectXML
FROM DispOps_Events
[ObjectXML] [nvarchar](max) NOT NULL
A sample of the XML data:
<Document>
<DocumentId>3352597</DocumentId>
<DocumentFullPath>xxx</DocumentFullPath>
<Category>xxx</Category>
<ClientId>xxx</ClientId>
<ApplicationNumber>xxx</ApplicationNumber>
<ContractNumber>xxx</ContractNumber>
<Created>xxx</Created>
<Creator>xxx</Creator>
</Document>
And I need get data from DocumentId>XXXX/DocumentId> and insert into #tmpTable.
So 1. I cast varchar(max) to xml
select CAST(ObjectXML as XML) as fileXML
INTO #tmpXML
FROM DispOps_Events T WHERE MetastormMapName = 'DocumentsMap'
I tried
select
m.c.value('#DocumentId', 'varchar(max)') as DocumentId
--into #tmpTable
from #tmpXML as s
outer apply s.fileXML.nodes('Document/DocumentId') as m(c)
Error:
null data in the table
You don't need #temp tables to do this, you can just cast the nvarchar(max) data to the xml data type in a single query, e.g.:
/*
* Setup test data...
*/
drop table if exists dbo.DispOps_Events;
create table dbo.DispOps_Events (
ID int not null identity(1,1),
ObjectXML nvarchar(max)
);
insert dbo.DispOps_Events (ObjectXML) values
(N'<Document><DocumentId>2554742</DocumentId><!--...--></Document>'),
(N'<Document><DocumentId>2576868</DocumentId><!--...--></Document>'),
(N'<Document><DocumentId>2576869</DocumentId><!--...--></Document>'),
(N'<Document><DocumentId>2576870</DocumentId><!--...--></Document>');
/*
* Query XML...
*/
select ID, [DocumentId] = Document.DocumentId.value('text()[1]', 'nvarchar(50)')
from dbo.DispOps_Events
cross apply ( select try_cast(ObjectXML as xml) ) Transformers(RoolyTroolyXml)
cross apply RoolyTroolyXml.nodes('/Document/DocumentId') as Document(DocumentId);
ID
DocumentId
1
2554742
2
2576868
3
2576869
4
2576870
I'm struggling with querying a SQL table and returning each row and also splitting out all the "Expressions" xml records into separate rows. I'm tried a few approaches.
This is some test data. I'm hoping to get 2 rows back both id an ID column of 1 and then some columns showing all the attributes of each expression.
CREATE TABLE #MyData (ID INT, MyXML xml)
INSERT INTO #MyData SELECT
1 AS ID
,'<ArrayOfExpressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Expressions>
<ExpressionID>1</ExpressionID>
<FieldName>AlternateDescription</FieldName>
<Operator>is not filled out</Operator>
<Condition>777</Condition>
<AndOr>OR</AndOr>
</Expressions>
<Expressions>
<ExpressionID>2</ExpressionID>
<FieldName>InputMask</FieldName>
<Operator>does not contain</Operator>
<Condition>hello</Condition>
<AndOr>AND</AndOr>
</Expressions>
</ArrayOfExpressions>' AS MyXML
And based on some examples this is as far as I got but I'm a bit lost now....
SELECT * FROM
(select
ID
,MyXMLRows.ExpressionID.value('(text())[1]', 'varchar(32)') as ExpressionID
from
#MyData CROSS APPLY
MyXML.nodes('/ArrayOfExpressions/Expressions') AS Roles(MyXMLRows)
) as MyResults
Try something like this:
SELECT
ID,
ExpressionId = XC.value('(ExpressionID)[1]', 'int'),
FieldName = XC.value('(FieldName)[1]', 'varchar(50)'),
Operator = XC.value('(Operator)[1]', 'varchar(50)'),
Condition = XC.value('(Condition)[1]', 'varchar(50)'),
AndOr = XC.value('(AndOr)[1]', 'varchar(25)')
FROM
#MyData
CROSS APPLY
MyXML.nodes('/ArrayOfExpressions/Expressions') AS XT(XC)
That should return something like this a a result set:
ID
ExpressionId
FieldName
Operator
Condition
AndOr
1
1
AlternateDescription
is not filled out
777
OR
1
2
InputMask
does not contain
hello
AND
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...
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
I am trying to create a structure xml document from my temp table .The temp table is in the following format .
CREATE TABLE #Temp1 ( Name Char( 30 ), seqid integer, salary int );
INSERT INTO #Temp1 VALUES('DEAL' ,123,6)
INSERT INTO #Temp1 VALUES('DEAL' ,56,6)
INSERT INTO #Temp1 VALUES('TRACNHE' ,1253,56)
INSERT INTO #Temp1 VALUES('TRACNHE' ,5,65)
INSERT INTO #Temp1 VALUES('ASSET' ,56,23)
I am trying to create an xml format in the following form :
<Response>
<Deal>
<seqid="123" salary="6" />
<seqid="56" salary="6" />
<Deal>
<TRACNHE>
<seqid="1253" salary="56"/>
<seqid="5" salary="65"/>
</TRACNHE>
<ASSET>
<seqid="56" salary="23"/>
</ASSET>
</Response>
SELECT Name, (SELECT SEQID FROM #TEMP1 T WHERE T.Name = T1.Name)
FROM (SELECT DISTINCT NAME FROM #TEMP1 ) T1
FOR XML PATH('rEPONSE')
DROP TABLE #Temp1
DROP TABLE #Temp1
I tried the above query but says that subquery returned more than 1 value
Could you let me know as to what i am missing in this query .
Is there a better way to handle this scenario.
Thanks in advance
based on your requirement, i'm seeing there are 2 types of complexities
You are trying to get the xml with grouped items.
For each group trying to create an xml element with two attributes
without any proper name
<seqid="1253" salary="56"/>
instead of
<ss seqid="1253" salary="56"/>
just look into this below query, it may help
SELECT
(SELECT
seqid 'ss/#seqid'
, salary 'ss/#salary'
FROM Temp1 as t where t.Name = 'Deal'
FOR XML PATH('Deal') , TYPE
) ,
(SELECT
seqid 'ss/#seqid'
, salary 'ss/#salary'
FROM Temp1 as t where t.Name = 'TRACNHE'
FOR XML PATH('TRACNHE') , TYPE
) ,
(SELECT
seqid 'ss/#seqid'
, salary 'ss/#salary'
FROM Temp1 as t where t.Name = 'ASSET'
FOR XML PATH('ASSET') , TYPE
)
FOR XML PATH(''), ROOT('Response');