Trying to transfer xml data into SQL as elements (not attribute) - sql-server

I am new at this so please bear with me. I am attempting to transfer some XML data into Microsoft SQL Server. I am assuming that this data needs to be transferred as elements and not attributes because the contents of the columns will not be static.
However for some reason when I attempt to transfer the data as elements I get NULL values. But when I try to transfer this same data as attributes it works and looks the way it is supposed to. I am tempted to shrug and just move on but I'm worried that things might go awry for me if I do that later on down the road.
I already have some attributes from this XML that I managed to transfer as attributes which I plan to combine with these elements that are masquerading as attributes into a single table. Will it work? And if it does will there be problems down the road?
Here is my SQL code when I attempt to transfer the elements as elements:
SELECT *
FROM OPENXML (#hdoc, '/roll/voter', 2)
WITH (
id int,
[value] char(50),
[state] char(2))
Here is my SQL code when I attempt to transfer the elements as attributes:
SELECT *
FROM OPENXML (#hdoc, '/roll/voter', 1)
WITH (
id int,
[value] char(50),
[state] char(2))
Here is a miniaturized version of the XML document:
<roll>
<voter id="400048" value="Yea" state="FL" />
<voter id="412516" value="Yea" state="CA" />
</roll>
Here is a link to the xml document via google drive (very small XML): https://drive.google.com/open?id=0B5VgOwWcGeLHaWctRU56Qlk3UWM
A screenshot of my SQL query, the table results, and the XML

FROM OPENXML is outdated and should not be used anymore (rare exceptions exist)...
Try with the real XML methods:
DECLARE #xml XML=
N'<roll>
<voter id="400048" value="Yea" state="FL" />
<voter id="412516" value="Yea" state="CA" />
</roll>';
SELECT #xml.value(N'(/roll/voter/#id)[1]',N'int') AS voter_id
,#xml.value(N'(/roll/voter/#value)[1]',N'nvarchar(max)') AS voter_value
,#xml.value(N'(/roll/voter/#state)[1]',N'nvarchar(max)') AS voter_state
The result
voter_id voter_value voter_state
400048 Yea FL

Related

How to store XML data list in a table

Im working on a script that will allow me to save the xml data in a table. This will be used to compare with other tables which also contains an xml data.
I've been successful so far for simple node tags, but have encountered an issue when trying to store the data from a list.
The XML data looks like this:Sample XML Data
And my query is this:
XML Query display
I am able to get the 'TypeCode' as the main node but for the value, it's always showing blank. I'm not sure how to handle the list in XML.
I'm thinking as long as I can save the data
''
'' as text in the Value column, then I can find another way to parse it and display it in a nicer way as another query.
Any help is appreciated :D Thanks!
For your next question: Please do not post pictures. I had to type this in... Please provide consumable data, best as a stand-alone example to reproduce your issue.
DECLARE #xml XML=
'<Codes>
<TypeCodes type="list">
<item key="A" text="A1"/>
<item key="C" text="C1"/>
</TypeCodes>
</Codes>';
--the Xml "as is"
SELECT #xml;
--fetch one value with XQuery
SELECT #xml.value('(/Codes/TypeCodes/item[#key="A"]/#text)[1]','varchar(10)');
--fetch all items as list
SELECT #xml.value('(/Codes/TypeCodes/#type)[1]','varchar(10)') AS TypeCode_type
,i.value('#key','varchar(10)') AS item_key
,i.value('#text','varchar(10)') AS item_text
FROM #xml.nodes('/Codes/TypeCodes/item') A(i);
check it out
Good day,
SQL Server include special data type XML which is what you should use in order to store your XML data.
declare #MyXML XML = '
<codes>
<Type>
<Item key="1" />
</Type>
</codes>
'
select #MyXML
Here is example of using table:
DROP TABLE IF eXISTS T;
CREATE TABLE T(MyXML XML)
GO
INSERT T(MyXML) values ('
<codes>
<Type>
<Item key="1" />
</Type>
</codes>
')
SELECT * FROM T
GO
For more information check this documentation:
XML Data Type and Columns (SQL Server)

Can't figure out how to search XML column in my table

I have a table called v_EpisodeAudit, with a column called EventData that contains XML data. The XML data differs from row to row, so one record could have XML data in this column that looks like this:
<AddMDMDocument>
<EpisodeMDMId>282521</EpisodeMDMId>
<OncologyReferral>0</OncologyReferral>
<SpecialPalliativeReferral>0</SpecialPalliativeReferral>
<SurgeonReferral>0</SurgeonReferral>
<MDMReport>0</MDMReport>
<GPReferral>0</GPReferral>
<GPReferralApproval>0</GPReferralApproval>
<GeneralPalliativeCare>0</GeneralPalliativeCare>
<AuditLogin>mkell010</AuditLogin>
<AuditTrust>4</AuditTrust>
<Error />
</AddMDMDocument>
while another row might contain the following XML data:
<CloseEpisode>
<EpisodeId>652503</EpisodeId>
<TrackingStatusId>9</TrackingStatusId>
<TrackingClosureReason>100</TrackingClosureReason>
<DateOfTrackingClosure>Sep 25 2017 12:37PM</DateOfTrackingClosure>
<AuditLogin>ccass001</AuditLogin>
<AuditTrust>1</AuditTrust>
<Error />
</CloseEpisode>
And there are further differing types/configurations of XML data. I've read about 20 different sources this morning trying to work out how to search against the XML data in this column to get a specific EpisodeId in the CloseEpisode XMLs, and I can't for the life of me figure it out. Can anyone help me with a query that will find a specified EpisodeId in this column?
XML can be queried very generically. Some approaches:
DECLARE #v_EpisodeAudit TABLE(ID INT IDENTITY, [EventData] XML);
INSERT INTO #v_EpisodeAudit VALUES
(N'<AddMDMDocument>
<EpisodeMDMId>282521</EpisodeMDMId>
<OncologyReferral>0</OncologyReferral>
<SpecialPalliativeReferral>0</SpecialPalliativeReferral>
<SurgeonReferral>0</SurgeonReferral>
<MDMReport>0</MDMReport>
<GPReferral>0</GPReferral>
<GPReferralApproval>0</GPReferralApproval>
<GeneralPalliativeCare>0</GeneralPalliativeCare>
<AuditLogin>mkell010</AuditLogin>
<AuditTrust>4</AuditTrust>
<Error />
</AddMDMDocument>')
,(N'<CloseEpisode>
<EpisodeId>652503</EpisodeId>
<TrackingStatusId>9</TrackingStatusId>
<TrackingClosureReason>100</TrackingClosureReason>
<DateOfTrackingClosure>Sep 25 2017 12:37PM</DateOfTrackingClosure>
<AuditLogin>ccass001</AuditLogin>
<AuditTrust>1</AuditTrust>
<Error />
</CloseEpisode>');00
--This will return the very first node on the second level
SELECT ID
,vEA.[EventData].value(N'local-name(/*[1]/*[1])',N'nvarchar(max)') AS NodeName
,vEA.[EventData].value(N'/*[1]/*[1]/text()[1]',N'nvarchar(max)') AS NodeValue
FROM #v_EpisodeAudit AS vEA
--This will return all nodes of the sevond level and use WHERE with LIKE to find the Episode..Id elements
SELECT ID
,SecondLevelNode.Nd.value(N'local-name(.)',N'nvarchar(max)') AS NodeName
,SecondLevelNode.Nd.value(N'text()[1]',N'nvarchar(max)') AS NodeValue
FROM #v_EpisodeAudit AS vEA
OUTER APPLY vEA.[EventData].nodes(N'/*/*') AS SecondLevelNode(Nd)
WHERE SecondLevelNode.Nd.value(N'local-name(.)',N'nvarchar(max)') LIKE 'Episode%' --or LIKE 'Episode%Id'
--Similar but filtering on XQuery level
SELECT ID
,SecondLevelNode.Nd.value(N'local-name(.)',N'nvarchar(max)') AS NodeName
,SecondLevelNode.Nd.value(N'text()[1]',N'nvarchar(max)') AS NodeValue
FROM #v_EpisodeAudit AS vEA
OUTER APPLY vEA.[EventData].nodes(N'/*/*[substring(local-name(),1,7)="Episode"]') AS SecondLevelNode(Nd)
Use the xml querying functions
select EventData.value('(/CloseEpisode/EpisodeId)[1]','int')
from v_EpisodeAudit
where EventData.value('local-name(/*[1])','varchar(100)')='CloseEpisode'
or perhaps
select EventData
from #v_EpisodeAudit
where EventData.value('(/CloseEpisode/EpisodeId)[1]','int')=652503
depending on what you're trying to do.
If you don't know the root node name, you could use
select EventData.value('(//EpisodeId)[1]','int')
from v_EpisodeAudit
where EventData.exist('//EpisodeId')=1
See https://learn.microsoft.com/en-us/sql/t-sql/xml/value-method-xml-data-type

Extracting XML in a column from a SQL Server database

I have read dozens of posts and have tried numerous SQL queries to try and get this figured out. Sadly, I'm not a SQL expert (not even a novice) nor am I an XML expert. I understand basic queries from SQL, and understand XML tags, mostly.
I'm trying to query a database table, and have the data show a list of values from a column that contains XML. I'll give you an example of the data. I won't burden you with everything I have tried.
Here is an example of field inside of the column I need. So this is just one row, I would need to query the whole table to get all of the data I need.
When I select * from [table name] it returns hundreds of rows and when I double click in the column name of 'Document' on one row, I get the information I need.
It looks like this:
<code_set xmlns="">
<name>ExampleCodeTable</name>
<last_updated>2010-08-30T17:49:58.7919453Z</last_updated>
<code id="1" last_updated="2010-01-20T17:46:35.1658253-07:00"
start_date="1998-12-31T17:00:00-07:00"
end_date="9999-12-31T16:59:59.9999999-07:00">
<entry locale="en-US" name="T" description="Test1" />
</code>
<code id="2" last_updated="2010-01-20T17:46:35.1658253-07:00"
start_date="1998-12-31T17:00:00-07:00"
end_date="9999-12-31T16:59:59.9999999-07:00">
<entry locale="en-US" name="Z" description="Test2" />
</code>
<displayExpression>[Code] + ' - ' + [Description]</displayExpression>
<sortColumn>[Description]</sortColumn>
</code_set>
Ideally I would write it so it runs the query on the table and produces results like this:
Code Description
--------------------
(Data) (Data)
Any ideas? Is it even possible? The dozens of things I have tried that are always posted in stack, either return Nulls or fail.
Thanks for your help
Try something like this:
SELECT
CodeSetId = xc.value('#id', 'int'),
Description = xc.value('(entry/#description)[1]', 'varchar(50)')
FROM
dbo.YourTableNameHere
CROSS APPLY
YourXmlColumn.nodes('/code_set/code') AS XT(XC)
This basically uses the built-in XQuery to get an "in-memory" table (XT) with a single column (XC), each containing an XML fragment that represents each <code> node inside your <code_set> root node.
Once you have each of these XML fragments, you can use the .value() XQuery operator to "reach in" and grab some pieces of information from it, e.g. it's #id (attribute by the name of id), or the #description attribute on the contained <entry> subelement.
The following query will read the xml field in every row, then shred certain values into a tabular result set.
SELECT
-- get attribute [attribute name] from the parent node
parent.value('./#attribute name','varchar(max)') as ParentAttributeValue,
-- get the text value of the first child node
child.value('./text()', 'varchar(max)') as ChildNodeValueFromFirstChild,
-- get attribute attribute [attribute name] from the first child node
child.value('./#attribute name', 'varchar(max)') as ChildAttributeValueFromFirstChild
FROM
[table name]
CROSS APPLY
-- create a handle named parent that references that <parent node> in each row
[xml field name].nodes('//xpath to parent name') AS ParentName(parent)
CROSS APPLY
-- create a handle named child that references first <child node> in each row
parent.nodes('(xpath from parent/to child)[0]') AS FirstChildNode(child)
GO
Please provide the exact values you want to shred from the XML for a more precise answer.

Creating XML Schema for Bulk Load to SQL Server - Child Element Describes Parent

I have an XML document that I'm working to build a schema for in order to bulk load these documents into a SQL Server table. The XML I'm focusing on looks like this:
<Coverage>
<CoverageCd>BI</CoverageCd>
<CoverageDesc>BI</CoverageDesc>
<Limit>
<FormatCurrencyAmt>
<Amt>30000.00</Amt>
</FormatCurrencyAmt>
<LimitAppliesToCd>PerPerson</LimitAppliesToCd>
</Limit>
<Limit>
<FormatCurrencyAmt>
<Amt>85000.00</Amt>
</FormatCurrencyAmt>
<LimitAppliesToCd>PerAcc</LimitAppliesToCd>
</Limit>
</Coverage>
<Coverage>
<CoverageCd>PD</CoverageCd>
<CoverageDesc>PD</CoverageDesc>
<Limit>
<FormatCurrencyAmt>
<Amt>50000.00</Amt>
</FormatCurrencyAmt>
<LimitAppliesToCd>Coverage</LimitAppliesToCd>
</Limit>
</Coverage>
Inside the Limit element, there's a child LimitAppliesToCd that I need to use to determine where the Amt element's value actually gets stored inside my table. Is this possible to do using the standard XML Bulk Load feature of SQL Server? Normally in XML I'd expect that the element would have an attribute containing the "PerPerson" or "PerAcc" information, but this standard we're using does not call for that.
If anyone has worked with the ACORD standard before, you might know what I'm working with here. Any help is greatly appreciated.
Don't know exactly what you are talking about, but this is a solution to get the information out of your XML.
Assumption: Your XML is already bulk-loaded into a declared variable #xml of type XML:
A CTE will pull the information out of your XML. The final query will then use PIVOT to put your data into the right column.
With a fitting table's structure the actual insert should be simple...
WITH DerivedTable AS
(
SELECT cov.value('CoverageCd[1]','varchar(max)') AS CoverageCd
,cov.value('CoverageDesc[1]','varchar(max)') AS CoverageDesc
,lim.value('(FormatCurrencyAmt/Amt)[1]','decimal(14,4)') AS Amt
,lim.value('LimitAppliesToCd[1]','varchar(max)') AS LimitAppliesToCd
FROM #xml.nodes('/root/Coverage') AS A(cov)
CROSS APPLY cov.nodes('Limit') AS B(lim)
)
SELECT p.*
FROM
(SELECT * FROM DerivedTable) AS tbl
PIVOT
(
MIN(Amt) FOR LimitAppliesToCD IN(PerPerson,PerAcc,Coverage)
) AS p

SQL Server XQuery - Selecting a Subset

Take for example the following XML:
Initial Data
<computer_book>
<title>Selecting XML Nodes the Fun and Easy Way</title>
<isbn>9999999999999</isbn>
<pages>500</pages>
<backing>paperback</backing>
</computer_book>
and:
<cooking_book>
<title>50 Quick and Easy XML Dishes</title>
<isbn>5555555555555</isbn>
<pages>275</pages>
<backing>paperback</backing>
</cooking_book>
I have something similar in a single xml-typed column of a SQL Server 2008 database. Using SQL Server XQuery, would it be possible to get results such as this:
Resulting Data
<computer_book>
<title>Selecting XML Nodes the Fun and Easy Way</title>
<pages>500</pages>
</computer_book>
and:
<cooking_book>
<title>50 Quick and Easy XML Dishes</title>
<isbn>5555555555555</isbn>
</cooking_book>
Please note that I am not referring to selecting both examples in one query; rather I am selecting each via its primary key (which is in another column). In each case, I am essentially trying to select the root and an arbitrary subset of children. The roots can be different, as seen above, so I do not believe I can hard-code the root node name into a "for xml" clause.
I have a feeling SQL Server's XQuery capabilities will not allow this, and that is fine if it is the case. If I can accomplish this, however, I would greatly appreciate an example.
Here is the test data I used in the queries below:
declare #T table (XMLCol xml)
insert into #T values
('<computer_book>
<title>Selecting XML Nodes the Fun and Easy Way</title>
<isbn>9999999999999</isbn>
<pages>500</pages>
<backing>paperback</backing>
</computer_book>'),
('<cooking_book>
<title>50 Quick and Easy XML Dishes</title>
<isbn>5555555555555</isbn>
<pages>275</pages>
<backing>paperback</backing>
</cooking_book>')
You can filter the nodes under to root node like this using local-name() and a list of the node names you want:
select XMLCol.query('/*/*[local-name()=("isbn","pages")]')
from #T
Result:
<isbn>9999999999999</isbn><pages>500</pages>
<isbn>5555555555555</isbn><pages>275</pages>
If I understand you correctly the problem with this is that you don't get the root node back.
This query will give you an empty root node:
select cast('<'+XMLCol.value('local-name(/*[1])', 'varchar(100)')+'/>' as xml)
from #T
Result:
<computer_book />
<cooking_book />
From this I have found two solutions for you.
Solution 1
Get the nodes from your table to a table variable and then modify the XML to look like you want.
-- Table variable to hold the node(s) you want
declare #T2 table (RootNode xml, ChildNodes xml)
-- Fetch the xml from your table
insert into #T2
select cast('<'+XMLCol.value('local-name(/*[1])', 'varchar(100)')+'/>' as xml),
XMLCol.query('/*/*[local-name()=("isbn","pages")]')
from #T
-- Add the child nodes to the root node
update #T2 set
RootNode.modify('insert sql:column("ChildNodes") into (/*)[1]')
-- Fetch the modified XML
select RootNode
from #T2
Result:
RootNode
<computer_book><isbn>9999999999999</isbn><pages>500</pages></computer_book>
<cooking_book><isbn>5555555555555</isbn><pages>275</pages></cooking_book>
The sad part with this solution is that it does not work with SQL Server 2005.
Solution 2
Get the parts, build the XML as a string and cast it back to XML.
select cast('<'+XMLCol.value('local-name(/*[1])', 'varchar(100)')+'>'+
cast(XMLCol.query('/*/*[local-name()=("isbn","pages")]') as varchar(max))+
'</'+XMLCol.value('local-name(/*[1])', 'varchar(100)')+'>' as xml)
from #T
Result:
<computer_book><isbn>9999999999999</isbn><pages>500</pages></computer_book>
<cooking_book><isbn>5555555555555</isbn><pages>275</pages></cooking_book>
Making the nodes parameterized
In the queries above the nodes you get as child nodes is hard coded in the query. You can use sql:varaible() to do this instead. I have not found a way of making the number of nodes dynamic but you can add as many as you think you need and have null as value for the nodes you don't need.
declare #N1 varchar(10)
declare #N2 varchar(10)
declare #N3 varchar(10)
declare #N4 varchar(10)
set #N1 = 'isbn'
set #N2 = 'pages'
set #N3 = 'backing'
set #N4 = null
select cast('<'+XMLCol.value('local-name(/*[1])', 'varchar(100)')+'>'+
cast(XMLCol.query('/*/*[local-name()=(sql:variable("#N1"),
sql:variable("#N2"),
sql:variable("#N3"),
sql:variable("#N4"))]') as varchar(max))+
'</'+XMLCol.value('local-name(/*[1])', 'varchar(100)')+'>' as xml)
from #T
Result:
<computer_book><isbn>9999999999999</isbn><pages>500</pages><backing>paperback</backing></computer_book>
<cooking_book><isbn>5555555555555</isbn><pages>275</pages><backing>paperback</backing></cooking_book>

Resources