Background: I am generating pieces of a much larger XML document (HL7 CDA documents) using SQL FOR XML queries. Following convention, we need to include section comments before this XML node so that when the nodes are reassembled into the larger document, they are easier to read.
Here is a sample of the expected output:
<!--
********************************************************
Past Medical History section
********************************************************
-->
<component>
<section>
<code code="10153-2" codeSystem="2.16.840.1.113883.6.1" codeSystemName="LOINC"/>
<title>Past Medical History</title>
<text>
<list>
<item>COPD - 1998</item>
<item>Dehydration - 2001</item>
<item>Myocardial infarction - 2003</item>
</list>
</text>
</section>
</component>
Here is the SQL FOR XML statement that I have constructed to render the above XML:
SELECT '10153-2' AS [section/code/#code], '2.16.840.1.113883.6.1' AS [section/code/#codeSystem], 'LOINC' AS [section/code/#codeSystemName],
'Past Medical History' AS [section/title],
(SELECT [Incident] + ' - ' + [IncidentYear] as "item"
FROM [tblSummaryPastMedicalHistory] AS PMH
WHERE ([PMH].[Incident] IS NOT NULL) AND ([PMH].[PatientUnitNumber] = [PatientEncounter].[PatientUnitNumber])
FOR XML PATH('list'), TYPE
) as "section/text"
FROM tblPatientEncounter AS PatientEncounter
WHERE (PatientEncounterNumber = 6)
FOR XML PATH('component'), TYPE
While I can insert the comments from the controlling function that reassembles these XML snippets into the main document, our goal is to have the comments be generated with the output to avoid document construction errors.
I've tried a few things, but am having trouble producing the comments with the SELECT statement. I've tried a simple string, but have not been able to get the syntax for the line breaks. Any suggestions?
Example:
SELECT [EmployeeKey]
,[ParentEmployeeKey]
,[FirstName]
,[LastName]
,[MiddleName]
,[DepartmentName] AS "comment()"
FROM [AdventureWorksDW2008].[dbo].[DimEmployee]
FOR XML PATH('Employee'),ROOT('Employees')
produces:
<Employees>
<Employee>
<EmployeeKey>1</EmployeeKey>
<ParentEmployeeKey>18</ParentEmployeeKey>
<FirstName>Guy</FirstName>
<LastName>Gilbert</LastName>
<MiddleName>R</MiddleName>
<!--Production-->
</Employee>
<Employee>
<EmployeeKey>2</EmployeeKey>
<ParentEmployeeKey>7</ParentEmployeeKey>
<FirstName>Kevin</FirstName>
<LastName>Brown</LastName>
<MiddleName>F</MiddleName>
<!--Marketing-->
</Employee>
</Employees>
Just an alternative that also works:
select cast('<!-- comment -->' as xml)`
This may be the only viable approach if you're using FOR XML EXPLICIT, which doesn't support the [comment()] column alias notation of the answer by John Saunders. For example:
select
1 [tag],
null [parent],
(select cast('<!-- test -->' as xml)) [x!1],
2 [x!1!b]
for xml explicit, type
The above produces:
<x b="2"><!-- test --></x>
If the comment is dynamic, just concatenate it like this:
select cast('<!--' + column_name + '-->' as xml)` from table_name
Related
I use a for xml select to produce an xml file.
I want to have two root nodes that contain some header tags and then detail rows that are a result from a query on a table.
Example:
<Root>
<FileHeader>
HEADER ROWS
</FileHeader>
<Jobs>
<Message xmlns="url">
<Header Destination="1" xmlns="url"/>
<Body>
<ListItem xmlns="url">
DETAIL ROWS FROM SELECT
</ListItem>
</Body>
</Message>
</Jobs>
</Root>
The query I am trying to produce this is this one:
WITH XMLNAMESPACES('url')
SELECT(
SELECT
HEADER ROWS
FOR XML PATH('FileHeader'),
TYPE),
(SELECT
'1' AS 'Message/Header/#Destination',
'url' AS 'Message/Header/#xmlns'
FOR XML PATH(''),
TYPE),
(SELECT
DETAIL ROWS FROM SELECT
FROM MY_TABLE
FOR XML PATH('Jobs'),ROOT('Body'),
TYPE )
FOR XML PATH ('Root')
MY_table and its data are irrelevant as all tags inside the final select are correct are validated against the xsd schema.
The FileHeader and Header tags are populated with values given from variables, so no tables are used there.
I am missing something on the middle part of the query (the second select). With my way, I can't have the Header tag inside the Jobs/Body path.
What is more, I cannot fill in the with xmlns value. I even used the following as I found on some forums and still can't manage to produce a well formatted tag with the xmlns attribute.
;WITH XMLNAMESPACES ('url' as xmlns)
Thank you!
Some things to state first:
It is not allowed to add the namespaces xmlns like any other attribute
It is possible to add this default namespace with WITH XMLNAMESPACES. But - in cases of sub-queries - this namespace will be inserte repeatedly. This is not wrong yet annoying and it can blow up your XML and make it fairly hard to read...
You can create a nested XML ad-hoc (inlined), or prepare it first and insert it to the final query. This allows you to add default namespace to a deeper level only.
It's not absolutely clear to me, what you are really looking for, but this might point you in the right direction:
DECLARE #HeaderData TABLE(SomeValue INT,SomeText VARCHAR(100));
DECLARE #DetailData TABLE(DetailsID INT,DetailText VARCHAR(100));
INSERT INTO #HeaderData VALUES
(100,'Value 100')
,(200,'Value 200');
INSERT INTO #DetailData VALUES
(1,'Detail 1')
,(2,'Detail 2');
DECLARE #BodyWithNamespace XML;
WITH XMLNAMESPACES(DEFAULT 'SomeURL_for_Body_default_ns')
SELECT #BodyWithNamespace=
(
SELECT *
FROM #DetailData AS dd
FOR XML PATH('DetailRow'),ROOT('ListItem'),TYPE
);
SELECT(
SELECT *
FROM #HeaderData AS hd
FOR XML PATH('HeaderRow'),ROOT('FileHeader'),TYPE
)
,
(
SELECT 1 AS [Header/#Destination]
,#BodyWithNamespace
FOR XML PATH('Message'),ROOT('Jobs'),TYPE
)
FOR XML PATH ('Root')
The result
<Root>
<FileHeader>
<HeaderRow>
<SomeValue>100</SomeValue>
<SomeText>Value 100</SomeText>
</HeaderRow>
<HeaderRow>
<SomeValue>200</SomeValue>
<SomeText>Value 200</SomeText>
</HeaderRow>
</FileHeader>
<Jobs>
<Message>
<Header Destination="1" />
<ListItem xmlns="SomeURL_for_Body_default_ns">
<DetailRow>
<DetailsID>1</DetailsID>
<DetailText>Detail 1</DetailText>
</DetailRow>
<DetailRow>
<DetailsID>2</DetailsID>
<DetailText>Detail 2</DetailText>
</DetailRow>
</ListItem>
</Message>
</Jobs>
</Root>
I have this xml file that all the rest of the nodes depends on the first one. I created a tree diagram to help illustrate it.
The problem I am having is that each node depends on the first one but they do not have any columns that link each other.
Here is the Link to the
XML Document
Here is my sql so far
SELECT
B.RD.query('racedata/todays_cls').value('.','varchar(max)') AS todays_cls,
B.RD.query('racedata/horsedata/horse_name').value('.', 'varchar(max)') AS horse_name,
B.RD.query('racedata/horsedata/jockey/jock_disp').value('.', 'varchar(max)') AS jockeyname
FROM #xmlData.nodes('data') AS B(RD)
Do the second CROSS APPLY on the result of the first one, so that you get <horsedata> elements that corresponds to current <racedata>. See a working demo example below.
sample data :
declare #xml XML = '
<data xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.trackmaster.com/xmlSchema/ppXMLData.xsd">
<racedata>
<race>1</race>
<todays_cls>59</todays_cls>
<distance>800.0</distance>
<country>USA</country>
<horsedata>
<horse_name>BROADWAY KATE</horse_name>
<owner_name>C. Steve Larue</owner_name>
<program>1</program>
<pp>1</pp>
<weight>121</weight>
</horsedata>
<horsedata>
<horse_name>UNFAITHFUL</horse_name>
<owner_name>Melson, James L., Thomson, Ramona and Connell</owner_name>
<program>2</program>
<pp>2</pp>
<weight>121</weight>
</horsedata>
</racedata>
<racedata>
<race>2</race>
<todays_cls>87</todays_cls>
<distance>800.0</distance>
<country>USA</country>
<horsedata>
<horse_name>MAGNETIC START</horse_name>
<owner_name>Vernon D. Coyle</owner_name>
<program>1</program>
<pp>1</pp>
<weight>121</weight>
</horsedata>
<horsedata>
<horse_name>SKI POLE</horse_name>
<owner_name>Downunder Cable, LLC</owner_name>
<program>2</program>
<pp>2</pp>
<weight>121</weight>
</horsedata>
</racedata>
</data>
'
query :
SELECT
rd.value('race[1]', 'int') AS race,
rd.value('todays_cls[1]','int') AS todays_cls,
hd.value('pp[1]','int') AS pp,
hd.value('weight[1]','int') AS weight
FROM #xml.nodes('data/racedata') AS B(RD)
CROSS APPLY RD.nodes('horsedata') AS C(HD)
output :
UPDATE
In response to the updated query, you should've shredded on horsedata element level as suggested in the above query, something like this :
SELECT
B.RD.query('todays_cls').value('.','varchar(max)') AS todays_cls,
C.HD.query('horse_name').value('.', 'varchar(max)') AS horse_name,
C.HD.query('jockey/jock_disp').value('.', 'varchar(max)') AS jockeyname
FROM #xml.nodes('data/racedata') AS B(RD)
CROSS APPLY RD.nodes('horsedata') AS C(HD)
I want to set a processing instruction to include a stylesheet on top of an XML:
The same issue was with the xml-declaration (e.g. <?xml version="1.0" encoding="utf-8"?>)
Desired result:
<?xml-stylesheet type="text/xsl" href="stylesheet.xsl"?>
<TestPath>
<Test>Test</Test>
<SomeMore>SomeMore</SomeMore>
</TestPath>
My research brought me to node test syntax and processing-instruction().
This
SELECT 'type="text/xsl" href="stylesheet.xsl"' AS [processing-instruction(xml-stylesheet)]
,'Test' AS Test
,'SomeMore' AS SomeMore
FOR XML PATH('TestPath')
produces this:
<TestPath>
<?xml-stylesheet type="text/xsl" href="stylesheet.xsl"?>
<Test>Test</Test>
<SomeMore>SomeMore</SomeMore>
</TestPath>
All hints I found tell me to convert the XML to VARCHAR, concatenate it "manually" and convert it back to XML. But this is - how to say - ugly?
This works obviously:
SELECT CAST(
'<?xml-stylesheet type="text/xsl" href="stylesheet.xsl"?>
<TestPath>
<Test>Test</Test>
<SomeMore>SomeMore</SomeMore>
</TestPath>' AS XML);
Is there a chance to solve this?
There is another way, which will need two steps but don't need you to treat the XML as string anywhere in the process :
declare #result XML =
(
SELECT
'Test' AS Test,
'SomeMore' AS SomeMore
FOR XML PATH('TestPath')
)
set #result.modify('
insert <?xml-stylesheet type="text/xsl" href="stylesheet.xsl"?>
before /*[1]
')
Sqlfiddle Demo
The XQuery expression passed to modify() function tells SQL Server to insert the processing instruction node before the root element of the XML.
UPDATE :
Found another alternative based on the following thread : Merge the two xml fragments into one? . I personally prefer this way :
SELECT CONVERT(XML, '<?xml-stylesheet type="text/xsl" href="stylesheet.xsl"?>'),
(
SELECT
'Test' AS Test,
'SomeMore' AS SomeMore
FOR XML PATH('TestPath')
)
FOR XML PATH('')
Sqlfiddle Demo
As it came out, har07's great answer does not work with an XML-declaration. The only way I could find was this:
DECLARE #ExistingXML XML=
(
SELECT
'Test' AS Test,
'SomeMore' AS SomeMore
FOR XML PATH('TestPath'),TYPE
);
DECLARE #XmlWithDeclaration NVARCHAR(MAX)=
(
SELECT N'<?xml version="1.0" encoding="UTF-8"?>'
+
CAST(#ExistingXml AS NVARCHAR(MAX))
);
SELECT #XmlWithDeclaration;
You must stay in the string line after this step, any conversion to real XML will either give an error (when the encoding is other then UTF-16) or will omit this xml-declaration.
I am new to XML and SQL Server and am trying import an XML file into SQL Server 2010. I have 14 tables that I would like to parse the data into. All 14 table names are listed in the XML as nodes (I think) I found some example code that worked with the simple example XML, but my XML seems a little more complicated and may not be structured optimally; unfortunately, I can't change that. As a basic attempt, I tried to insert the data into just one field of one existing table (SILVX_SN16000), but the Message pane shows "(0 rows(s) affected). Thanks in advance for looking at this.
USE TEST
Declare #xml XML
Select #xml =
CONVERT(XML,bulkcolumn,2) FROM OPENROWSET(BULK 'C:\Users\Kevin_S\Documents \SilvxInSightImport.xml',SINGLE_BLOB) AS X
SET ARITHABORT ON
Insert into [SILVX_SN16000]
(
md_group
)
Select
P.value('MD_GROUP[1]','NVARCHAR(255)') AS md_group
From #xml.nodes('/TableData/Row') PropertyFeed(P)
Here is a much-shortened (rows removed) version of my XML:
<?xml version="1.0" ?>
<SilvxInSightImport Version="1.0" Host="uslsss17" Date="14-09-14_20-40-02">
<Tables Count="14">
<Table Name="SN16000">
<TableSchema>
<Column><COLUMN_NAME>PARENT_HPKEY</COLUMN_NAME><DATA_TYPE>VARCHAR2</DATA_TYPE></Column>
<Column><COLUMN_NAME>MD_GROUP</COLUMN_NAME><DATA_TYPE>VARCHAR2</DATA_TYPE></Column>
<Column><COLUMN_NAME>PKEY</COLUMN_NAME><DATA_TYPE>NUMBER</DATA_TYPE></Column>
<Column><COLUMN_NAME>S_STATE</COLUMN_NAME><DATA_TYPE>VARCHAR2</DATA_TYPE></Column>
<Column><COLUMN_NAME>NAME</COLUMN_NAME><DATA_TYPE>VARCHAR2</DATA_TYPE></Column>
<Column><COLUMN_NAME>ROUTER_ID</COLUMN_NAME><DATA_TYPE>VARCHAR2</DATA_TYPE></Column>
<Column><COLUMN_NAME>IP_ADDR</COLUMN_NAME><DATA_TYPE>VARCHAR2</DATA_TYPE></Column>
</TableSchema>
<TableData>
<Row><MD_GROUP>100.120.25162</MD_GROUP><PARENT_HPKEY>100</PARENT_HPKEY> <PKEY>161888</PKEY><NAME>UODEDTM010</NAME><ROUTER_ID>10.41.32.129</ROUTER_ID> <IP_ADDR>10.41.32.129</IP_ADDR><S_STATE>IS-NR</S_STATE></Row>
<Row><MD_GROUP>100.120.25162</MD_GROUP><PARENT_HPKEY>100</PARENT_HPKEY> <PKEY>278599</PKEY><NAME>UODEETM010</NAME><ROUTER_ID>10.41.4.129</ROUTER_ID> <IP_ADDR>10.41.4.129</IP_ADDR><S_STATE>IS-NR</S_STATE></Row>
<Row><MD_GROUP>100.120.25162</MD_GROUP><PARENT_HPKEY>100</PARENT_HPKEY> <PKEY>183583</PKEY><NAME>UODEGRM010</NAME><ROUTER_ID>10.41.76.129</ROUTER_ID> <IP_ADDR>10.41.76.129</IP_ADDR><S_STATE>IS-NR</S_STATE></Row>
NT_HPKEY>100</PARENT_HPKEY><PKEY>811003</PKEY><NAME>UODWTIN010</NAME> <ROUTER_ID>10.27.36.130</ROUTER_ID><IP_ADDR>10.27.36.130</IP_ADDR><S_STATE>IS-NR</S_STATE> </Row>
</TableData>
</Table>
</Tables>
</SilvxInSightImport>
The xPath in .nodes() must specify the whole path to the Row nodes so you should start with SilvxInSightImport and work your way down to Row.
/SilvxInSightImport/Tables/Table/TableData/Row
In your case you have multiple table nodes, one for each table and I assume you only need one table at a time. You can use a predicate on the table name in the .nodes() xPath expression.
/SilvxInSightImport/Tables/Table[#Name = "SN16000"]/TableData/Row
Your whole query for SN16000 should look something like this.
select T.X.value('(MD_GROUP/text())[1]', 'varchar(20)') as MD_GROUP,
T.X.value('(PARENT_HPKEY/text())[1]', 'int') as PARENT_HPKEY,
T.X.value('(PKEY/text())[1]', 'int') as PKEY,
T.X.value('(NAME/text())[1]', 'varchar(20)') as NAME,
T.X.value('(ROUTER_ID/text())[1]', 'varchar(20)') as ROUTER_ID,
T.X.value('(IP_ADDR/text())[1]', 'varchar(20)') as IP_ADDR,
T.X.value('(S_STATE/text())[1]', 'varchar(20)') as S_STATE
from #XML.nodes('/SilvxInSightImport/Tables/Table[#Name = "SN16000"]/TableData/Row') as T(X)
You have to sort out the data types used for each column.
SQL Fiddle
I'm writing an SQL select statement which returns XML. I wanted to put in some comments and found a post asking how to do this. The answer seemed to be the "comment()" function/keyword. So, my code looks broadly like this:
select ' extracted on tuesday ' as 'comment()',
(select top 5 id from MyTable for xml path(''),type)
for xml path('stuff')
...which returns XML as follows:
<stuff>
<!-- extracted on tuesday -->
<id>0DAD4B42-CED6-4A68-AB7D-0003E4C127CC</id>
<id>24BD0E5F-8B76-43FF-AEEA-0008AA911ADD</id>
<id>AAFF5BB0-BFFB-4584-BACC-0009684A1593</id>
<id>0581AF24-8C30-408C-9A48-000A488133AC</id>
<id>01E2306D-296A-4FF7-9263-000EEFF42230</id>
</stuff>
In the process of trying to find out more about "comment()", I discovered "data()" as well.
select top 5 id as 'data()' from MyTable for xml path('')
Unfortunately, the names make searching for information on these functions very difficult.
Can someone point me at the documentation on their usage, as well as any other similar functions ?
Thanks,
Edit:
Another would appear to be "processing-instruction(blah)".
Example:
select 'type="text/css" href="style.css"' as 'processing-instruction(xml-stylesheet)',
(select top 5 id from MyTable for xml path(''),type)
for xml path('stuff')
Results:
<stuff>
<?xml-stylesheet type="text/css" href="style.css"?>
<id>0DAD4B42-CED6-4A68-AB7D-0003E4C127CC</id>
<id>24BD0E5F-8B76-43FF-AEEA-0008AA911ADD</id>
<id>AAFF5BB0-BFFB-4584-BACC-0009684A1593</id>
<id>0581AF24-8C30-408C-9A48-000A488133AC</id>
<id>01E2306D-296A-4FF7-9263-000EEFF42230</id>
</stuff>
Here is the link to the BOL info: Columns with the Name of an XPath Node Test.
This details the functionality you are interested in. (It can indeed be a pain to find)
Also you can find quick functional examples here