declare #x xml =
'<Detials xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Tests>
<Test Name="Test1" TotalMarks="100">95</Test>
<Test Name="Test2" TotalMarks="200">65</Test>
<Test Name="Test3" TotalMarks="150">95</Test>
<Test Name="Test4" TotalMarks="150"></Test>
</Tests>
<Tests>
<Test Name="Test1" TotalMarks="100">95</Test>
<Test Name="Test2" TotalMarks="200">65</Test>
<Test Name="Test3" TotalMarks="150">95</Test>
<Test Name="Test4" TotalMarks="150"></Test>
</Tests>
</Detials>'
When i queried like this
SELECT STUFF(
#x.query('for $a in (*:Detials/Tests/Test/#Name)
return <a>{concat(",", $a)}</a>')
.value('.', 'NVARCHAR(MAX)'),
1, 1, '') AS ListOfName
I get like this
ListofName
Test1,Test2,Test3,Test4,Test1,Test2,Test3,Test4
But Want to shred the xml based on the <Test> Which can give the result like this below
ListofName
Test1,Test2,Test3,Test4
Test1,Test2,Test3,Test4
Please help me here to shred original XML into separate rows before string concatenation with query() and value()
Thanks in Advance ,Jayendran
Try this solution:
declare #x xml =
'<Detials xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Tests>
<Test Name="Test1" TotalMarks="100">95</Test>
<Test Name="Test2" TotalMarks="200">65</Test>
<Test Name="Test3" TotalMarks="150">95</Test>
<Test Name="Test4" TotalMarks="150"></Test>
</Tests>
<Tests>
<Test Name="Test1" TotalMarks="100">95</Test>
<Test Name="Test2" TotalMarks="200">65</Test>
<Test Name="Test3" TotalMarks="150">95</Test>
<Test Name="Test4" TotalMarks="150"></Test>
</Tests>
</Detials>'
-- Edit 1
SELECT XmlTable.OriginalID, ROW_NUMBER() OVER(ORDER BY x.XmlCol) AS RowNum, y.ListOfName
FROM (SELECT 1 AS OriginalID, #x AS XmlCol) AS XmlTable -- Edit 2
CROSS APPLY XMLTable.XmlCol.nodes('*:Detials/Tests') AS x(XmlCol)
CROSS APPLY(
SELECT STUFF(x.XmlCol.query('for $a in (Test/#Name) return <a>{concat(",", $a)}</a>').value('.', 'NVARCHAR(MAX)'), 1, 1, '')
) AS y(ListOfName)
-- End of Edit 1
Demo
Related
Issue:
I have a single .xml file containing 13.5k of data sets and need to import it into SSMS. Unfortunatly, it contain a style I never met before; first a column declaration part, then the data part without any specific column names. With thus, I have issues to catch the needed fields. On top of that the .xml may even be corrupt (incorrect hierarchical structure).
Xml:
<?xml version="1.0" encoding="UTF-8"?>
<Root>
<DMSContent format="LOL"/>
<Archive name="Adressdossier" id="52" osguid="43AAEC21AC6C40F1BEDB34D92512ED84"/>
<ObjectType name="Dokument" internal_name="CitizenFileDocument" id="262216" osguid="F287C984EB9E48BEA280BA46C305567C" type="DOCUMENT" modul="MULTIDOC"/>
<Rowset>
<Columns>
<Column name="Salutation" type="TEXT" ostype="X" size="50" otype="FOLDER"/>
<Column name="Name" type="TEXT" ostype="X" size="200" otype="FOLDER"/>
<Column name="FirstName" type="TEXT" ostype="X" size="100" otype="FOLDER"/>
<Column name="StreetNo" type="TEXT" ostype="X" size="100" otype="FOLDER"/>
<Column name="City" type="TEXT" ostype="X" size="150" otype="FOLDER"/>
<Column name="ZIP" type="TEXT" ostype="X" size="50" otype="FOLDER"/>
<Column name="Country" type="TEXT" ostype="X" size="50" otype="FOLDER"/>
<Column name="Birthday" type="DATE" ostype="D" size="50" otype="FOLDER"/>
<Column name="Filename" type="INTEGER" ostype="9" size="100" otype="FOLDER"/>
</Columns>
</Rowset>
<Rows>
<Row id="2538">
<Value>Mrs</Value>
<Value>Doe</Value>
<Value>Jane</Value>
<Value>Main Street 5</Value>
<Value>Ghost Town</Value>
<Value>5315</Value>
<Value>Switzerland</Value>
<Value>12.12.2017</Value>
<Value>jp4_B025DF7DBAFC49879103ECB8AE59C3A2.docx</Value>
</Row>
<Row id="2579">
<Value>Mr</Value>
<Value>Ding</Value>
<Value>Chavez</Value>
<Value>Sun Boulevard 3a</Value>
<Value>Alien City</Value>
<Value>4586</Value>
<Value>Germany</Value>
<Value>01.01.1980</Value>
<Value>jp4_DCA9345C93E84F1697668E6ACDC596C9.docx</Value>
</Row>
<Row id="2580">
<Value>Mr</Value>
<Value>Dale</Value>
<Value>Dick</Value>
<Value>Beach Avenue 13</Value>
<Value>Zombie Village</Value>
<Value>9513</Value>
<Value>Italy</Value>
<Value>09.11.1911</Value>
<Value>jp4_5DDBF2A05BD0421A8C53B0CC4EB64232.doc</Value>
</Row>
</Rows>
</Root>
The usually used MS-Sql code snippet, of course not working for this type of .xml-structure:
set ansi_nulls on;
declare #xmlfile xml;
select #xmlfile = bulkcolumn
from openrowset(bulk 'C:\Meta.xml', single_blob) x;
select
id = c.value('#id', 'int'),
Salutation = c.value('(Column[#k="Salutation"]/#v)[1]', 'varchar(60)'),
[Name] = c.value('(Column[#k="name"]/#v)[1]', 'varchar(100)'),
Birthday = c.value('(Column[#k="Birthday"]/#v)[1]', 'date'),
[Filename] = c.value('(Column[#k="Filename"]/#v)[1]', 'varchar(100)')
into #Meta --
from #xmlfile.nodes('/root/rows') as T(c);
set ansi_nulls off;
Thank you in advance for any help!
SQL Server doesn't support fn::position() or preceding-sibling:: syntaxes. But you can use a hack involving << to get the position of each node.
So we calculate the position of each Column node, then push those values into the Value lookups
SELECT
id = x2.Row.value('#id', 'int'),
Salutation = x2.Row.value('(Value[sql:column("ColIndex.Salutation")]/text())[1]', 'varchar(60)'),
[Name] = x2.Row.value('(Value[sql:column("ColIndex.Name" )]/text())[1]', 'varchar(100)'),
Birthday = x2.Row.value('(Value[sql:column("ColIndex.Birthday" )]/text())[1]', 'date'),
[Filename] = x2.Row.value('(Value[sql:column("ColIndex.Filename" )]/text())[1]', 'varchar(100)')
FROM #xml.nodes('/Root/Rowset/Columns') x1(Col)
CROSS APPLY (
SELECT
Salutation = x1.Col.value('let $c:= Column[#name="Salutation"][1] return count(Column[. << $c]) + 1', 'int'),
[Name] = x1.Col.value('let $c:= Column[#name="Name"] [1] return count(Column[. << $c]) + 1', 'int'),
Birthday = x1.Col.value('let $c:= Column[#name="Birthday"] [1] return count(Column[. << $c]) + 1', 'int'),
[Filename] = x1.Col.value('let $c:= Column[#name="Filename"] [1] return count(Column[. << $c]) + 1', 'int')
) ColIndex
CROSS APPLY #xml.nodes('/Root/Rows/Row') x2(Row);
db<>fiddle
If you want to keep your current approach of importing the file, you can, with the following changes:
set ansi_nulls on;
declare #xmlfile xml;
select #xmlfile = bulkcolumn
from openrowset(bulk 'C:\Meta.xml', single_blob) x;
select
id = c.value('#id', 'int'),
Salutation = c.value('(Value[count(/Root/Rowset/Columns/Column[#name="Salutation"]/preceding-sibling::*) + 1]/text())[1]', 'varchar(60)'),
[Name] = c.value('(Value[count(/Root/Rowset/Columns/Column[#name="Name"]/preceding-sibling::*) + 1]/text())[1]', 'varchar(100)'),
Birthday = c.value('(Value[count(/Root/Rowset/Columns/Column[#name="Birthday"]/preceding-sibling::*) + 1]/text())[1]', 'date'),
[Filename] = c.value('(Value[count(/Root/Rowset/Columns/Column[#name="Filename"]/preceding-sibling::*) + 1]/text())[1]', 'varchar(100)')
into #Meta --
from #xmlfile.nodes('/Root/Rows/Row') as T(c);
set ansi_nulls off;
This finds the right <Value> position by looking up the <Column> of the given name and figuring out how many columns precede it. Not pretty, but effective.
If this is a one-off and/or you're certain of the column order, you can of course access the values directly.
Birthday = c.value('(Value[8]/text())[1]', 'varchar(60)'),
I have a store procedure that returns the below XML:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header />
<SOAP-ENV:Body>
<ns2:ReportResponse>
<ns2:responseTitle />
<ns2:responseBody>
<ns2:resultRow>
<ns2:result Name="country" Value="United Kingdom" />
<ns2:result Name="code" Value="7360" />
</ns2:resultRow>
<ns2:resultRow>
<ns2:result Name="country" Value="France" />
<ns2:result Name="code" Value="7340" />
</ns2:resultRow>
</ns2:responseBody>
</ns2:ReportResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
I want to be able to save the 2 records in the table, how can I get a loop to get the data?
Record 1:
Country=United Kingdom
Code=7360
Record 2:
Country=France
Code=7340
I tried to use this select but it's not returning anything.
SELECT
Record.value('#Name','VARCHAR')
FROM #XmlResponse.nodes('/Envelope/Body/ReportResponse/responseBody/resultRow')AS TEMPTABLE(Record)
Thanks.
Like this:
declare #doc xml = '
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header />
<SOAP-ENV:Body>
<ns2:ReportResponse xmlns:ns2="http://whatever">
<ns2:responseTitle />
<ns2:responseBody>
<ns2:resultRow>
<ns2:result Name="country" Value="United Kingdom" />
<ns2:result Name="code" Value="7360" />
</ns2:resultRow>
<ns2:resultRow>
<ns2:result Name="country" Value="France" />
<ns2:result Name="code" Value="7340" />
</ns2:resultRow>
</ns2:responseBody>
</ns2:ReportResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
';
WITH XMLNAMESPACES ('http://schemas.xmlsoap.org/soap/envelope/' as soap ,
'http://whatever' as ns2)
SELECT
Record.value('(ns2:result[#Name="country"])[1]/#Value','VARCHAR(20)') Country,
Record.value('(ns2:result[#Name="code"])[1]/#Value','int') Code
FROM #doc.nodes('/soap:Envelope/soap:Body/ns2:ReportResponse/ns2:responseBody/ns2:resultRow')AS TEMPTABLE(Record)
outputs
Country Code
-------------------- -----------
United Kingdom 7360
France 7340
(2 rows affected)
I am having following XML
DECLARE #ruleXML XML
SET #RuleXML = '<questionnaire xmlns:xsi="http://schema1" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://schem2" title="Sample">
<sections>
<section id="24" title="Section Title" help="" url="">
<questions />
</section>
<section id="23" title="Information" help="" url="">
<questions />
</section>
<section id="25" title="Section Title1" help="" url="">
<questions>
<question id="3" title="Question Text">
<display-rules />
<questions />
</question>
<question id="4" title="Question Text" >
<response-set type="inline" />
<display-rules />
<questions />
</question>
</questions>
</section>
</sections>
</questionnaire>'
How to get a table with question id and title from all the question nodes regardless of their level using XQUERY in SQL server?
; with xmlnamespaces (default 'http://schem2')
select tbl.col1.value('#id', 'int')
, tbl.col1.value('#title', 'varchar(100)')
from #RuleXML.nodes('//question') tbl(col1)
Working example at SQL FIddle.
I need to query Xml data that has multiple REPORT tag elements. It needs to be filtered to return only the rows where the REPORTID is equal to a given name. I've tried to perform this filter with no luck. Could someone point me in the right direction here using the Sql Server Xml functions?
Basically, I'm looking for my result set to return as a table and look like:
ID
------
1
2
3
Given the following, how would I select out on the rows for the REPORT where the REPORTID (/TEST/REPORT/TITLE[#ReportId = "Report One"]) is equal to 'Report One'?
DECLARE #Xml XML, #ReportId VARCHAR(200);
SET #ReportId = 'Report One';
SET #Xml = '
<TEST>
<REPORT ReportType="Type One">
<TITLE ReportId="Report One">
<TITLE1>Title One</TITLE1>
</TITLE>
<HEADER>
<Run_Date OrigName="Run Date">4/10/2012</Run_Date>
</HEADER>
<BODY>
<TABLE1>
<DATA />
<ROW>
<ID>1</ID>
</ROW>
<ROW>
<ID>2</ID>
</ROW>
<ROW>
<ID>3</ID>
</ROW>
</TABLE1>
</BODY>
</REPORT>
<REPORT ReportType="Type Two">
<TITLE ReportId="Report Two">
<TITLE1>Title Two</TITLE1>
</TITLE>
<HEADER>
<Run_Date OrigName="Run Date">4/10/2012</Run_Date>
</HEADER>
<BODY>
<TABLE1>
<DATA />
<ROW>
<ID>4</ID>
</ROW>
<ROW>
<ID>5</ID>
</ROW>
<ROW>
<ID>6</ID>
</ROW>
</TABLE1>
</BODY>
</REPORT>
</TEST>';
select I.N.value('.', 'int') as ID
from #Xml.nodes('TEST/REPORT') as R(N)
cross apply R.N.nodes('BODY/TABLE1/ROW/ID') as I(N)
where R.N.exist('TITLE[#ReportId = sql:variable("#ReportId")]') = 1
I realize this is almost three years old, but I couldn't resist. The CROSS APPLY is unnecessary if you expand the XPATH expression in nodes().
SELECT ID = c.value('.', 'int')
FROM #Xml.nodes('/TEST/REPORT[TITLE/#ReportId=sql:variable("#ReportId")]/BODY/TABLE1/ROW/ID') x(c)
In my table I have col 1 ,col 2 ,col 3. The col 3 has the XML stored. I want update the Name,Signedby,userid,title,status,lastmodified nodes based on "Name" node.
XML File:
<SignatureSummary>
<SectionList>
<Section>
<Name>A</Name>
<SignedBy></SignedBy>
<UserId></UserId>
<Title></Title>
<Status></Status>
<LastModifiedOn></LastModifiedOn>
</Section>
<Section>
<Name>B</Name>
<SignedBy />
<UserId />
<Title />
<Status />
<LastModifiedOn />
</Section>
</SectionList>
</SignatureSummary>
Try something like this:
SELECT
Col1, Col2,
Section.value('(Name)[1]', 'VARCHAR(50)') AS 'Name',
Section.value('(SignedBy)[1]', 'VARCHAR(50)') AS 'SignedBy',
Section.value('(UserId)[1]', 'VARCHAR(50)') AS 'UserId',
Section.value('(Title)[1]', 'VARCHAR(50)') AS 'Title',
Section.value('(Status)[1]', 'VARCHAR(50)') AS 'Status',
Section.value('(LastModifiedOn)[1]', 'DATETIME') AS 'Last Modified On'
FROM
dbo.YourTable
CROSS APPLY
Col3.nodes('/SignatureSummary/SectionList/Section') AS Sig(Section)