Root element with header and detail rows - sql-server

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>

Related

Is that valid XML and how to replicate with SQL Server

I do have to replicate an XML file with SQL Server and I am now stumbling over the following structure inside the XML file and I don't know how to replicate that.
The structure looks like this at the moment for certain tags:
<ART_TAG1>
<UNMLIMITED/>
</ART_TAG1>
<ART_TAG2>
<ART_TAG3>
<Data_Entry/>
</ART_TAG3>
</ART_TAG2>
I am wondering if this is proper XML that the data inside (unlimited and Data_Entry) is enclosed with a closing XML tag. The XML validator https://www.w3schools.com/xml/xml_validator.asp is telling me this is correct. But now I am struggling with replicating that with Transact-SQL.
If I try to replicate that I can only come up with the following TSQL script, which obviously does not fully look like the original.
SELECT 'UNLIMITED' as 'ART_TAG1'
, 'Data_Entry' as 'ART_TAG2/ART_TAG3'
FOR XML PATH(''), ROOT('root')
<root>
<ART_TAG1>UNLIMITED</ART_TAG1>
<ART_TAG2>
<ART_TAG3>Data_Entry</ART_TAG3>
</ART_TAG2>
</root>
If I get this correctly, your question is:
How can I put my query to create those <SomeElement /> tags?
Look at this:
--This will create filled nodes
SELECT 'outer' AS [OuterNode/#attr]
,'inner' AS [OuterNode/InnerNode]
FOR XML PATH('row');
--The empty string is some kind of content
SELECT 'outer' AS [OuterNode/#attr]
,'' AS [OuterNode/InnerNode]
FOR XML PATH('row');
--the missing value (NULL) is omited by default
SELECT 'outer' AS [OuterNode/#attr]
,NULL AS [OuterNode/InnerNode]
FOR XML PATH('row');
--Now check what happens here:
--First XML has an empty element, while the second uses the self-closing element
DECLARE #xml1 XML=
N'<row>
<OuterNode attr="outer">
<InnerNode></InnerNode>
</OuterNode>
</row>';
DECLARE #xml2 XML=
N'<row>
<OuterNode attr="outer">
<InnerNode/>
</OuterNode>
</row>';
SELECT #xml1,#xml2;
The result is the same for both...
Some background: Semantically the empty element <element></element> is exactly the same as the self-closing element <element />. It should not make any difference, whether you use the one or the other. If your consumer cannot deal with this, it is a problem in the reading part.
Yes, you can force any content into XML on string level, but - as the example shows above - this is just a (dangerous) hack.
XML within T-SQL returns - by default - a missing node as NULL and an empty element as empty (depending on the datatype, and beware of the difference between an element and its text() node).
In short: This is nothing you should have to think about...

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)

SQL Query values out of XML using

I can't figure out what I'm doing wrong. I have XML stored in a table column as text. I'm taking the ID, and the XML text and querying it into a temp table that stores the XML as XML type.
Each Order in the XML has multiple licenses in it that I need to pull out and create a new table with OrderID and License ID. But I can't tell what I'm doing wrong.
So, I'm trying to start basic but I can't seem to even just get the Account Info from the first Node.
The XML looks like this:
<ns1:OrderFromCRM xmlns:ns1="http://company.com/licensing/neworder/v2">
<ns1:AccountInfo Name="Company Name" AccountId="A012345" />
<ns1:OrderInfo CRMOrderId="S147360" Date="2/23/2017 12:00:00 AM" ffEmail="emailaddress.#gmail.com" >
<ns1:Licensing>
<ns1:Foundations>
<ns1:Foundation LicenseId="L012345678" Action="Create" Environment="Production" Type="Enterprise">
<Metadata>
<AllowedInstances>999</AllowedInstances>
</Metadata>
</ns1:Foundation>
<ns1:Foundation LicenseId="L012345698" Action="Create" Environment="Production" Type="Enterprise">
<Metadata>
<AllowedInstances>999</AllowedInstances>
</Metadata>
</ns1:Foundation>
</ns1:Foundations>
<ns1:Licenses Type="Create">
<ns1:License LicenseId="L0123451234" ProductFamily="Fam1" Product="EStudio" LicenseType="Perpetual" StartDate="2017-02-23" ExpiryDate="2017-12-18" MaintenanceExpiryDate="2017-12-18">
<ns1:Capabilities>
<ns1:Capability Name="T1" />
<ns1:Capability Name="Q1" />
<ns1:Capability Name="B1" />
</ns1:Capabilities>
</ns1:License>
<ns1:License LicenseId="L333356675" ProductFamily="Fam1" Product="EStudio" LicenseType="Perpetual" StartDate="2017-02-23" ExpiryDate="2017-12-18" MaintenanceExpiryDate="2017-12-18">
<ns1:Capabilities>
<ns1:Capability Name="T1" />
<ns1:Capability Name="Q1" />
<ns1:Capability Name="B1" />
</ns1:Capabilities>
</ns1:License>
The SQL I wrote is:
CREATE TABLE #demoData
(
ActivationCode NVARCHAR(100) NOT NULL,
OrderXMLText XML
)
SELECT OrderId, OrderXMLText.value('(/OrderFromCRM/AccountInfo)[1]', 'varchar(30)')
FROM #DEMODATA
I mentioned I need the OrderID and the LicenseId but even with this, I can't get anything. Am I on right track? First, what am I missing? Second, once this is formatted correctly, how do I get the nested LicenseIds in the XML?
Thanks so much for any help. I've been trying to make this work for a couple days
You’re missing the namespace so the query isn’t matching the xml, therefore it doesn’t find the elements you’re querying.
Add
;WITH XMLNAMESPACES
https://learn.microsoft.com/en-us/sql/t-sql/xml/with-xmlnamespaces
Assuming you have a complete, valid XML document in your table (the one you're showing is lacking several closing tags), then you could try something like this:
;WITH XMLNAMESPACES('http://company.com/licensing/neworder/v2' AS ns1)
SELECT
ActivationCode,
CRMOrderId = XC.value('#CRMOrderId', 'varchar(100)'),
FoundationsLicenseId = xcf.value('#LicenseId', 'varchar(50)'),
LicensesLicenseId = xcl.value('#LicenseId', 'varchar(50)')
FROM
#demoData
CROSS APPLY
OrderXMLText.nodes('/ns1:OrderFromCRM/ns1:OrderInfo') AS XT(XC)
CROSS APPLY
XC.nodes('ns1:Licensing/ns1:Foundations/ns1:Foundation') AS XTF(XCF)
CROSS APPLY
XC.nodes('ns1:Licensing/ns1:Licenses/ns1:License') AS XTL(XCL)
First of all, you need to include and respect the XML namespace in your XQuery - that's what I do with the WITH XMLNAMESPACES() directive.
Next, you need to use .nodes() to get a list of XML fragments for each <OrderInfo> node, which is located below the root node (<OrderFromCRM>). This is the first CROSS APPLY. This returns a list of XML fragments, one for each <OrderInfo> node.
Then you need to reach into these XML fragments, and again use CROSS APPLY with the .nodes() function to get a list of the <Foundation> elements (contained in the <Licensing>/<Foundations> subtree to get the license Id's from those nodes. In addition, you need a second CROSS APPLY to get all the <License> subnodes under <Licensing>/<Licenses> to get those LicenseId attributes.
That should return an output something like:
Hope this helps you some!

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.

Using attribute more than once in FOR XML Path T-SQL query with same element name

I am trying to create an xml output in SQL 2008 using FOR XML Path. This is working fine:
<Taxonomy>
<Category Level="1">Clothing</Category>
<SubCategory Level="2">Jeans</SubCategory>
</Taxonomy>
But I would like the output to be:
<Taxonomy>
<Category Level="1">Clothing</Category>
<Category Level="2">Jeans</Category>
</Taxonomy>
Of course you can code as following:
1 as 'Taxonomy/Category/#Level',
2 as 'Taxonomy/Category/#Level',
t.MainCat as 'Taxonomy/Category',
t.SubCat as 'Taxonomy/Category',
But this gives an error message:
Attribute-centric column 'Column name is repeated. The same attribute cannot be generated more than once on the same XML tag.
What can be done to get the desired output?
Would a subselect work or some kind of cross apply? Or perhaps a union? But how?
---- EDIT - after several answers came up with following solution:
SELECT
1 as 'Category/#Level',
t.Cat as 'Category'
FROM table t
UNION
SELECT
2 as 'Category/#Level',
t.SubCat as 'Category'
FROM table t
FOR XML PATH (''), ROOT('Taxonomy')
gives this output:
<Taxonomy>
<Category Level="1">Clothing</Category>
<Category Level="2">Jeans</Category>
</Taxonomy>
Still have to figure out how to put this partial coding in a much larger code with several 'nested' FOR XML's already
The shortcut methods may not cut it for this. AUTO and PATH don't like multiple elements with the same name. Looks like you would have to use the FOR XML EXPLICIT command.
It works, but is cumbersome.
Sample:
--Generate Sample Data
--FOR XML EXPLICIT requires two meta fields: Tag and Parent
--Tag is the ID of the current element.
--Parent is the ID of the parent element, or NULL for root element.
DECLARE #DataTable as table
(Tag int NOT NULL
, Parent int
, TaxonomyValue nvarchar(max)
, CategoryValue nvarchar(max)
, CategoryLevel int)
--Fill with sample data: Category Element (2), under Taxonomy(1), with no Taxonomy value.
INSERT INTO #DataTable
VALUES (2, 1, NULL, 1, 'Clothing')
, (2, 1, NULL, 2, 'Jeans')
--First part of query: Define the XML structure
SELECT
1 as Tag --root element
, NULL as Parent
, NULL as [Taxonomy!1] --Assign Taxonomy Element to the first element, aka root.
, NULL as [Category!2] --Assign Category Element as a child to Taxonomy.
, NULL as [Category!2!Level] --Give Category an Attribute 'Level'
--The actual data to fill the XML
UNION
SELECT
Data.Tag
, Data.Parent
, Data.TaxonomyValue
, Data.CategoryValue
, Data.CategoryLevel
FROM
#DataTable as Data
FOR XML EXPLICIT
Generates XML
<Taxonomy>
<Category Level="1">Clothing</Category>
<Category Level="2">Jeans</Category>
</Taxonomy>
Edit: Had columns reversed. No more Jeans level.
I know this is an old post, but I want to share one solution that avoids that FOR XML EXPLICIT command complexity for big xmls.
It's enough to add null as a child of Taxonomy, and error will disappear:
select 1 as 'Taxonomy/Category/#Level',
t.MainCat as 'Taxonomy/Category',
NULL AS 'Taxonomy/*',
2 as 'Taxonomy/Category/#Level',
t.SubCat as 'Taxonomy/Category',
from t
I hope it helps.
I have another way. It seemed a tad bit easy to me.
Say, for example I have an xml like
DECLARE #xml xml='<parameters></parameters>'
DECLARE #multiplenodes XML = '<test1><test2 Level="1">This is a test node 2</test2><test2 Level="2">This is another test node</test2></test1>'
SET #xml.modify('insert sql:variable("#multiplenodes") into (/parameters)[1]')
SELECT #xml
Do tell me if this helps.

Resources