SQL to populate values in xml list - sql-server

In an SQL Server sproc I need to generate xml using data originating from two different tables. In my example below, the patient number for type EPI comes from one table and the patient number for type MRN comes from another table. To create the xml I am using a UNION to combine the records from two distinct select statements and then using 'FOR XML PATH'. Is there a different way - such as using two select sub-queries without using UNION?
<Patients>
<Patient>
<Number>1234</Number>
<NumberType>EPI</NumberType>
</Patient>
<Patient>
<Number>5678</Number>
<NumberType>MRN</NumberType>
</Patient>
</Patients>
Thanks in advance.

If I understood your answer to my question, you are not really joining the tables on PatientId, you are just creating a list of all the data from both tables, and you don't need to group the records by patient.
Yes, UNION is the easiest way to accomplish a single list.
However, since you want to output xml, there is an alternate that can be done without UNION, per your question:
Assuming you have two tables that might look something like this:
CREATE TABLE SrcA (PatientId int, NumberA int, TypeA varchar(16));
CREATE TABLE SrcB (PatientId int, NumberB int, TypeB varchar(16));
with sample values like this (note how each table has one record not in the other):
INSERT INTO SrcA VALUES(100, 1234, 'EPI'), (200, 2222, 'EPI'), (400, 4444, 'EPI');
INSERT INTO SrcB VALUES(100, 5678, 'MRN'), (200, 2121, 'MRN'), (300, 3131, 'MRN');
Then the following query:
SELECT
(SELECT SA.NumberA AS Number, SA.TypeA AS NumberType WHERE SA.NumberA IS NOT NULL FOR XML PATH('Patient'), TYPE),
(SELECT SB.NumberB AS Number, SB.TypeB AS NumberType WHERE SB.NumberB IS NOT NULL FOR XML PATH('Patient'), TYPE)
FROM SrcA SA
FULL OUTER JOIN SrcB SB ON SA.PatientId = SB.PatientId
FOR XML PATH(''), ROOT('Patients')
will produce:
<Patients>
<Patient>
<Number>1234</Number>
<NumberType>EPI</NumberType>
</Patient>
<Patient>
<Number>5678</Number>
<NumberType>MRN</NumberType>
</Patient>
<Patient>
<Number>2222</Number>
<NumberType>EPI</NumberType>
</Patient>
<Patient>
<Number>2121</Number>
<NumberType>MRN</NumberType>
</Patient>
<Patient>
<Number>4444</Number>
<NumberType>EPI</NumberType>
</Patient>
<Patient>
<Number>3131</Number>
<NumberType>MRN</NumberType>
</Patient>
</Patients>

Related

best way to add an xml element around xml or a row-by-row basis

On SQL Server 2012 (SP1), what's the best way to take data that looks like this:
declare #t table (
id int,
data xml
)
insert into #t values (1, '<node/>'), (2, '<node/>')
id data
1 <node />
2 <node />
and turn it into
id data
1 <root><node /></root>
2 <root><node /></root>
?
Is there a way that's more efficient than just converting it to character data, adding the node start and end tag and then converting it back to xml? I'm dealing with this issue on larger data sets, so this is just a simple example.
One more approach (but my favorite was har07's answer)
declare #t table (
id int,
data xml
)
insert into #t values (1, '<node/>'), (2, '<node/>');
SELECT id
,(SELECT data AS [*] FOR XML PATH('root'),TYPE)
FROM #t;
You can use simple XQuery to add <root> parent in your query result. No back-and-forth conversion between XML and VARCHAR data types involved :
SELECT id, data.query('<root>{.}</root>') AS data
FROM #t
Quick test : http://rextester.com/RLZ30365
Brief explanation :
{} : indicates that texts in between should be treated as XQuery expression instead of literals
. : reference to current context node, which in this case is <node />

Convert 1-to-n XML column to tabular data

I have a table on MS SQL server that holds information about reports in XML format. The table consists of two fields: the first has the business key, the second the entire report in XML format.
These reports include several pictures each. The XML holds information about these pictures, such as their filename, taken date, etc. I want to extract this information into a table, where every record holds information about exactly one photo. I've found ways to do this that come very close, but the problem I keep running into is that I need to create several records in this table for every record in my source table. How can I make this work?
The business key needs to be in the final table as well. This business key can be found in the XML data, but there is also a separate field in the source table (as mentioned before) where it can be found. The content of the XML column could look similar to this:
<Report>
<ReportKey>0000001</ReportKey>
[...]
<Photos>
<Photo>
<Filename>1.jpg</Filename>
<Date>01-01-2015</Date>
</Photo>
<Photo>
<Filename>2.jpg</Filename>
<Date>01-01-2016</Date>
</Photo>
[...]
</Photos>
[...]
</Report>
I want the final table to look like this:
+---------+----------+------------+
| Key | Filename | Date |
+---------+----------+------------+
| 0000001 | 1.jpg | 01-01-2015 |
| 0000001 | 2.jpg | 01-01-2016 |
+---------+----------+------------+
This is not an answer, but important enough not to end up in a comment:
Be very careful with date formats. I do not know how your XML is generated, but the date within an XML should be ISO 8601 (yyyy-mm-dd or yyyy-mm-ddThh:mm:ss).
Your format is culture dependant !!!
Try this:
set language french;
declare #xml as xml ='<x><Date>08-03-2015</Date></x>';
select #xml.value('(/x/Date)[1]','datetime');
set language english;
select #xml.value('(/x/Date)[1]','datetime');
You see, that the results differ?
Now try to set the date to the 13th of March. There's even a conversion exception!
According to comments the OP needs an approach to get this from table row data and the existing answer is not solution enough.
You might try this:
CREATE TABLE #YourTable(BusinessKey VARCHAR(10),ReportData XML);
INSERT INTO #YourTable VALUES
('0000001','<Report>
<ReportKey>0000001</ReportKey>
<Photos>
<Photo>
<Filename>1.jpg</Filename>
<Date>2015-01-01</Date>
</Photo>
<Photo>
<Filename>2.jpg</Filename>
<Date>2016-05-13</Date>
</Photo>
</Photos>
</Report>')
,('0000002','<Report>
<ReportKey>0000002</ReportKey>
<Photos>
<Photo>
<Filename>3.jpg</Filename>
<Date>2015-04-19</Date>
</Photo>
<Photo>
<Filename>4.jpg</Filename>
<Date>2016-12-10</Date>
</Photo>
</Photos>
</Report>');
SELECT BusinessKey AS Table_Key
,ReportData.value('(/Report/ReportKey)[1]','varchar(10)') AS XML_Key
,Photo.value('Filename[1]','varchar(max)') AS Photo_Filename
,Photo.value('Date[1]','date') AS Photo_Date
FROM #YourTable
CROSS APPLY ReportData.nodes('/Report/Photos/Photo') AS A(Photo);
GO
DROP TABLE #YourTable;
Maybe I misunderstood the question. However, try this.
create table t (
[Key] int,
[Filename] nvarchar(max),
[Date] date
)
declare #xml as xml = '<Report>
<ReportKey>0000001</ReportKey>
<Photos>
<Photo>
<Filename>1.jpg</Filename>
<Date>01-01-2015</Date>
</Photo>
<Photo>
<Filename>2.jpg</Filename>
<Date>01-01-2016</Date>
</Photo>
</Photos>
</Report>'
insert into t ([Key], [Filename], [Date])
select n.value('ReportKey[1]', 'int')
, x.value('Filename[1]', 'nvarchar(max)')
, x.value('Date[1]', 'date')
from #xml.nodes('Report') as r(n)
cross apply r.n.nodes('Photos/Photo') as t(x)
select * from t

SSIS Export table with different types of columns into flat file

I'm working on a SSIS Package.
I have a table as below:
Table Name: Employee_table
EmployeID EmployeeName EmployeeDataXML
==============================================
1 Mark <Age>32</Age><Role>Manager</Role>
2 Albert <Age>31</Age><Role>Staff</Role>
==============================================
This table has to be exported into a flat file with name: Employeedata.dat
Content in the file should look like this:
<EmployeeID>1</EmployeeID><EmployeeName>Mark</EmplyeeName><EmployeeDataXML><Age>32</Age><Role>Manager</Role></EmployeeDataXML>
<EmployeeID>2</EmployeeID><EmployeeName>Albert</EmplyeeName><EmployeeDataXML><Age>31</Age><Role>Staff</Role></EmployeeDataXML>
Basically, the employeeid and employeename columns are not in xml format but still when the export happens they should be wrapped up in xml too.
Can someone guide me which is the best way to do it?
Do i need to use any transformation here?
Is there any control/task which is readily available?
Can writing a SQL Select Statement which could simply solves this?
Please guide.
Yes, a simple SELECT using FOR XML PATH should take care of this:
DECLARE #TestData TABLE
(
EmployeID INT NOT NULL,
EmployeeName NVARCHAR(50) NOT NULL,
EmployeeDataXML XML
);
INSERT INTO #TestData (EmployeID, EmployeeName, EmployeeDataXML)
VALUES (1, N'Mark', N'<Age>32</Age><Role>Manager</Role>');
INSERT INTO #TestData (EmployeID, EmployeeName, EmployeeDataXML)
VALUES (2, N'Albert', N'<Age>31</Age><Role>Staff</Role>');
SELECT EmployeID, EmployeeName, EmployeeDataXML
FROM #TestData
FOR XML PATH(N'Employee');
produces the following:
<Employee>
<EmployeID>1</EmployeID>
<EmployeeName>Mark</EmployeeName>
<EmployeeDataXML>
<Age>32</Age>
<Role>Manager</Role>
</EmployeeDataXML>
</Employee>
<Employee>
<EmployeID>2</EmployeID>
<EmployeeName>Albert</EmployeeName>
<EmployeeDataXML>
<Age>31</Age>
<Role>Staff</Role>
</EmployeeDataXML>
</Employee>
You didn't have the parent <Employee> element shown in the sample output, but I don't think the file would be usable without some element wrapping the field elements into a "row".

FOR XML SQL Server - Variable Element name in output XML

I'm quite new to FOR XML in SQL Server, I've searched considerable and I can't find an answer to this.
Can I have a variable element name using 'for xml' where the element name is not hard-coded and is instead take from a cell in each row? Take the following example...
Table ORDERS:
ID STATUS TIME AMOUNT
------------------------------------
1 COMPLETE 02:31 2355
2 ACCEPTED 02:39 6653
3 ACCEPTED 04:21 4102
4 RECEIVED 05:03 4225
FOR XML query:
select ID,
TIME as STATUS_TIME,
AMOUNT as CURRENT_AMOUNT
from ORDERS
for xml raw(' **STATUS NAME HERE** '),root('ORDERS'), elements
Required output:
<ORDERS>
<COMPLETE> <<<<--- Variable element name from STATUS in ORDERS
<ID>1</ID>
<STATUS_TIME>02:31</STATUS_TIME>
<CURRENT_AMOUNT>2355</CURRENT_AMOUNT>
</COMPLETE>
<ACCEPTED> <<<<--- Variable element name from STATUS in ORDERS
<ID>2</ID>
<STATUS_TIME>02:39</STATUS_TIME>
<CURRENT_AMOUNT>6653</CURRENT_AMOUNT>
</ACCEPTED>
<ACCEPTED> <<<<--- Variable element name from STATUS in ORDERS
<ID>3</ID>
<STATUS_TIME>04:21</STATUS_TIME>
<CURRENT_AMOUNT>4102</CURRENT_AMOUNT>
</ACCEPTED>
<RECEIVED> <<<<--- Variable element name from STATUS in ORDERS
<ID>4</ID>
<STATUS_TIME>05:03</STATUS_TIME>
<CURRENT_AMOUNT>4225</CURRENT_AMOUNT>
</RECEIVED>
</ORDERS>
I know I'm able to give attributes to the element names, and that I could give the individual ORDER in ORDERS and attribute of STATUS like below but unfortunately that's not what the people that will receive the XML document are looking for :(
select ID,
STATUS as '#STATUS'
TIME as STATUS_TIME,
AMOUNT as CURRENT_AMOUNT
from ORDERS
for xml raw('ORDER'),root('ORDERS'), elements
Output:
<ORDERS>
<ORDER STATUS='COMPLETE'> <<<<--- Attribute for STATUS but not what I want
<ID>1</ID>
<STATUS_TIME>02:31</STATUS_TIME>
<CURRENT_AMOUNT>2355</CURRENT_AMOUNT>
</ORDER>
<ORDER STATUS='ACCEPTED'> <<<<--- Attribute for STATUS but not what I want
<ID>2</ID>
<STATUS_TIME>02:39</STATUS_TIME>
<CURRENT_AMOUNT>6653</CURRENT_AMOUNT>
</ORDER>
....
I'd like to be able to do all this within SQL Server if possible. Many, many thanks if you can help me at all on this.
You can't specify column value in XML Raw(). So what you have to do is select required column from select query and cast result into XML, like this -
Schema
DECLARE #temp table (ID int, [STATUS] [varchar](100) NOT NULL, [TIME] [varchar](100), AMOUNT int);
INSERT #temp (ID, [STATUS], [TIME], AMOUNT) VALUES (1, 'COMPLETE', '02:31', 2355),(2, 'ACCEPTED', '02:41', 6653),(3, 'ACCEPTED', '02:31', 4102),(4, 'ACCEPTED', '02:31', 4225)
Query
SELECT
CAST('<' + STATUS + '>' +
'<ID>' + CAST(ID AS varchar) + '</ID>' +
'<TIME>' + TIME + '</TIME>' +
'<AMOUNT>' + CAST(AMOUNT AS varchar) + '</AMOUNT>' +
'</' + STATUS + '>' AS XML) from #temp
FOR XML PATH(''),root('ORDERS')
Output
<ORDERS>
<COMPLETE>
<ID>1</ID>
<TIME>02:31</TIME>
<AMOUNT>2355</AMOUNT>
</COMPLETE>
<ACCEPTED>
<ID>2</ID>
<TIME>02:41</TIME>
<AMOUNT>6653</AMOUNT>
</ACCEPTED>
<ACCEPTED>
<ID>3</ID>
<TIME>02:31</TIME>
<AMOUNT>4102</AMOUNT>
</ACCEPTED>
<ACCEPTED>
<ID>4</ID>
<TIME>02:31</TIME>
<AMOUNT>4225</AMOUNT>
</ACCEPTED>
</ORDERS>
In SQL Server, XML schema has to be static, so it is impossible to specify a variable element name (be it document or attribute).
If possible options for the STATUS field are limited and stable, you can mention them all explicitly, like in the example below:
select (
select t.ID, t.TIME as [STATUS_TIME], t.AMOUNT as [CURRENT_AMOUNT]
from #temp t
where t.STATUS = 'ACCEPTED'
for xml path('ACCEPTED'), type, elements
), (
select t.ID, t.TIME as [STATUS_TIME], t.AMOUNT as [CURRENT_AMOUNT]
from #temp t
where t.STATUS = 'COMPLETE'
for xml path('COMPLETE'), type, elements
)
for xml path('ORDERS'), type;
I think you have already noticed numerous possibilities for how this code can betray you, but frankly this approach is the only one available which does not include string manipulations (they will be detrimental to performance if the size of the XML output will be at least several Mb).
As a possible workaround, you can generate this query dynamically, including as many sections as there are distinct STATUS values in your table. Very ugly, but it will work.

How to get results in a specific XML format from a TSQL query?

I have the following T-SQL query that I want to convert into XML file. I was trying to use FOR XML Path, but this isn't working the way I need it to.
Here is my T-SQL table definition:
create table TN_DataFeed
(
--Patient uniqueidentifier,
ProviderPatientNo varchar(50) null,
LastName varchar(25),
FirstName varchar(25),
SSN char(9) null,
DOB char(10) null,
Gender tinyint null,
Race tinyint null,
Ethnicity tinyint null,
--PhoneAssessment varchar(50),
ProviderPhoneAssessmentID varchar(50),
CallEndDate char(10),
CallEndTime varchar(8)
)
My data matches this format above and successfully is inserted. But I need my XML to look like:
<Patient>
<ProviderPatientNo>ProviderPatientNo0</ProviderPatientNo>
<LastName>LastName0</LastName>
<FirstName>FirstName0</FirstName>
<SSN>000000000</SSN>
<DOB>2006-05-04</DOB>
<Gender>1</Gender>
<Race>1</Race>
<Ethnicity>1</Ethnicity>
<PhoneAssessment>
<ProviderPhoneAssessmentId>52854541</ProviderPhoneAssessmentId>
<CallEndDate>2006-05-04</CallEndDate>
<CallEndTime>01:01:01.001</CallEndTime>
</PhoneAssessment>
</Patient>
This is my XML Path code for retrieving the above T-SQL query:
select
ProviderPatientNo,
LastName,FirstName,SSN,DOB,Gender,Race,Ethnicity,
(
select distinct
ProviderPhoneAssessmentId, CallEndDate, CallEndTime
from TN_DataFeed
For XML path ('PhoneAssessment'), root('PhoneAssessment2'), type
)
from TN_DataFeed
For XML path ('Patient'), root('Patient_root'), type
Note that I have not yet included all of the columns. Instead, I'm just trying to get the Patient section working. Notice how in the example XML file below, the section that shows how Patient is the parent node of ProviderPatientNo, LastName, FirstName, SSN, DOB, Gender, Race, and Ethnicity.
But instead, my XML output from my above XML Path is:
<Patient_root>
<Patient>
<ProviderPatientNo>00200543</ProviderPatientNo>
<LastName>Ga</LastName>
<FirstName>Ti</FirstName>
<SSN>4108</SSN>
<DOB>1998-08-16</DOB>
<Gender>2</Gender>
<Race>2</Race>
<Ethnicity>3</Ethnicity>
<PhoneAssessment2>
<PhoneAssessment>
<ProviderPhoneAssessmentId>BEA5487B-82E9-4226-B883-BFBFE7EF2B1A</ProviderPhoneAssessmentId>
<CallEndDate>2013-09-16</CallEndDate>
<CallEndTime>22:00:00</CallEndTime>
</PhoneAssessment>
<PhoneAssessment>
<ProviderPhoneAssessmentId>C8F39E2F-BC4A-48AD-BD07-C07EB8384AD7</ProviderPhoneAssessmentId>
<CallEndDate>2013-09-16</CallEndDate>
<CallEndTime>16:24:00</CallEndTime>
</PhoneAssessment>
</PhoneAssessment2>
</Patient>
<Patient>
<ProviderPatientNo>00200543</ProviderPatientNo>
<LastName>Ga</LastName>
<FirstName>Ti</FirstName>
<SSN>4108</SSN>
<DOB>1998-08-16</DOB>
<Gender>2</Gender>
<Race>2</Race>
<Ethnicity>3</Ethnicity>
<PhoneAssessment2>
<PhoneAssessment>
<ProviderPhoneAssessmentId>BEA5487B-82E9-4226-B883-BFBFE7EF2B1A</ProviderPhoneAssessmentId>
<CallEndDate>2013-09-16</CallEndDate>
<CallEndTime>22:00:00</CallEndTime>
</PhoneAssessment>
<PhoneAssessment>
<ProviderPhoneAssessmentId>C8F39E2F-BC4A-48AD-BD07-C07EB8384AD7</ProviderPhoneAssessmentId>
<CallEndDate>2013-09-16</CallEndDate>
<CallEndTime>16:24:00</CallEndTime>
</PhoneAssessment>
</PhoneAssessment2>
</Patient>
So the problems are:
Many of the elements are repeated. I tried using Distinct to limit repetitions, but this caused the error:
The xml data type cannot be selected as DISTINCT because it is not
comparable.
Ok: First thing I notice is that you're querying the information_schema not the actual table, and all this gives you is the table schema metadata. Can you change your query to query the table please? SELECT ... FROM TN_DataFeed.
See this example : http://technet.microsoft.com/en-us/library/bb510462.aspx
Something like this should get you close to what you want:
SELECT ProviderPatientNo,
LastName,
FirstName,
SSN,
DOB,
Gender,
Race,
PhoneAssessment ....
FROM TN_DataFeed
FOR XML PATH ('Patient');
you can replace the distinct with a GROUP BY
select
ProviderPhoneAssessmentId, CallEndDate, CallEndTime
from
TN_DataFeed
group by
ProviderPhoneAssessmentId, CallEndDate, CallEndTime

Resources