SQL Server output as XML all on one line - sql-server

I'm using SQL Server 2014 and I'm new to XML. I'm trying to export 3 tables into XML. The 3 tables are:
Property (Parent)
PropertyArea (Child)
PropertyNotes (Child)
In SQL world, there is a left join from:
Property > PropertyArea
Property > PropertyNotes
The primary and foreign key is [property_id]
I have the below SQL code. However, when I open it in Notepad ++ it just creates the XML all on one line, and I was expecting to see it formatted.
DECLARE #XMLOutput XML
DECLARE #XMLOutputChar nvarchar(max)
;WITH XMLNAMESPACES('MyNameSpace' as ns)
SELECT #XMLOutput =
(
SELECT ISNULL(T1.[propertyusercode],'')AS propertyusercode,
ISNULL(T1.[NAME],'')AS [NAME],
(
SELECT
(
SELECT
[AreaCode]
,[AreaDesc]
FROM [PropertyArea]
WHERE [Property_Id] = T1.property_id
FOR XML PATH('Area'), TYPE
)
FOR XML PATH('Area'), TYPE
),
(
SELECT
(
SELECT
[NotesModuleNumber]
,[NotesPageNumber]
,[NotesText]
FROM [PropertyNotes]
WHERE [Property_Id] = T1.property_id
FOR XML PATH('Notes'), TYPE
)
FOR XML PATH('Notes'), TYPE
)
FROM Property T1
FOR XML PATH('Property'),TYPE, ROOT('Loader'),ELEMENTS XSINIL
)
SET #XMLOutputChar = '<?xml version="1.0" encoding="UTF-8"?>' + CONVERT(nvarchar(max),#XMLOutput)
SELECT #XMLOutputChar AS XMLOutput
Am I missing something? Is my code correct?

If you just want to see it on Notepad++, use plugin XML Tools to "pretty print" the XML into an easier to read format.
Copy paste from SQL Server:
After formatting:

Related

SQL Server parse XML to table - multiple node with the same name

I would like to parse XML into a table in SQL Server 2012 where my XML has nodes with the same name.
My SQL query which return only the first row:
SELECT
[date] = Node.Data.value('(date)[1]', 'NVARCHAR(MAX)'),
name = Node.Data.value('(name)[1]', 'VARCHAR(MAX)')
FROM
#xml.nodes('result/subject') Node(Data)
XML sample
<result>
<subject>
<date>2019-06-03</date>
<name>AZGREX</name>
<name>ABGDFC</name>
<name>WWGDFW</name>
<name>FDSFSD</name>
<name>FSDWEW</name>
<name>CXZCXZ</name>
<name>GWGRE</name>
</subject>
</result>
You need to use nodes in the FROM:
DECLARE #XML xml = '<result>
<subject>
<date>2019-06-03</date>
<name>AZGREX</name>
<name>ABGDFC</name>
<name>WWGDFW</name>
<name>FDSFSD</name>
<name>FSDWEW</name>
<name>CXZCXZ</name>
<name>GWGRE</name>
</subject>
</result>';
SELECT r.[subject].value('(date/text())[1]','date') AS [date],
s.[name].value('(./text())[1]','varchar(6)') AS [name] --obviously, you'll likely need a larger length
FROM (VALUES(#XML))V(X)
CROSS APPLY V.X.nodes('result/subject') r([subject])
CROSS APPLY r.[subject].nodes('name') s([name]);

SSRS: How to find which reports have subreports?

I have a lot of SQL Server Reporting Services (SSRS) reports (*.rdl). I want to know which of these reports are using subreports. How can I do that? Looking at an easier way instead of opening each report and figuring out if a subreport is being used.
Thanks
I think this should provide you with what you need (With thanks to Bret Stateham ):
--The first CTE gets the content as a varbinary(max)
--as well as the other important columns for all reports,
--data sources and shared datasets.
WITH ItemContentBinaries AS
(
SELECT
ItemID,Name,[Type]
,CASE Type
WHEN 2 THEN 'Report'
WHEN 5 THEN 'Data Source'
WHEN 7 THEN 'Report Part'
WHEN 8 THEN 'Shared Dataset'
ELSE 'Other'
END AS TypeDescription
,CONVERT(varbinary(max),Content) AS Content
FROM ReportServer.dbo.Catalog
WHERE Type IN (2,5,7,8)
),
--The second CTE strips off the BOM if it exists...
ItemContentNoBOM AS
(
SELECT
ItemID,Name,[Type],TypeDescription
,CASE
WHEN LEFT(Content,3) = 0xEFBBBF
THEN CONVERT(varbinary(max),SUBSTRING(Content,4,LEN(Content)))
ELSE
Content
END AS Content
FROM ItemContentBinaries
)
--The outer query gets the content in its varbinary, varchar and xml representations...
,VarcharContent as
(
SELECT
ItemID,Name,[Type],TypeDescription
,Content --varbinary
,CONVERT(varchar(max),Content) AS ContentVarchar --varchar
,CONVERT(xml,Content) AS ContentXML --xml
FROM ItemContentNoBOM
)
SELECT * FROM VarcharContent where ContentVarchar like '%<subreport%'
The following query below will return a list of deployed reports that have subreports. Here's the Microsoft reference and a link for referencing older versions of SSRS. It looks like the only difference is changing the version of SSRS in XMLNAMESPACES part of the CTE.
Query to return all subreports
WITH
XMLNAMESPACES
(
'http://schemas.microsoft.com/sqlserver/reporting/2016/01/reportdefinition' AS rdl
)
,
report_list
AS
(
SELECT
[ReportID] = cat.[ItemID]
, [ReportName] = cat.[Name]
, [ReportPath] = cat.[Path]
, [xmlColumn] = CAST(CAST(cat.[Content] AS VARBINARY(MAX)) AS XML)
FROM
[ReportServer].[dbo].[Catalog] AS cat
WHERE
1=1
AND cat.[Content] IS NOT NULL
AND cat.[Type] = 2
)
SELECT
rpt.[ReportID]
, rpt.[ReportName]
, rpt.[ReportPath]
, [SubReportName] = srpt.x.value('(//rdl:ReportName)[1]', 'NVARCHAR(256)')
FROM
report_list AS rpt
CROSS APPLY xmlColumn.nodes('//rdl:Subreport') AS srpt(x);

SQL REPLACE function inside Xml.modify 'replace value of'

I'm trying to update some XML data in SQL Server. The XML contains data that looks like this:
<root>
<id>1</id>
<timestamp>16-10-2017 19:24:55</timestamp>
</root>
Let's say this XML exists in a column called Data in a table called TestTable.
I would like to be able to change the hyphens in the timestamp to forward slashes.
I was hoping I might be able to do something like:
update TestTable
set Data.modify('replace value of
(/root/timestamp/text())[1] with REPLACE((/root/timestamp/text())[1], "-", "/")')
I get the following error:
XQuery [TestTable]: There is no function '{http://www.w3.org/2004/07/xpath-functions}:REPLACE()'
When I think about it, this makes sense. But I wonder, is there a way to do this in a single update statement? Or do I first need to query the timestamp value and save it as a variable, and then update the XML with the variable?
You can also do this with a join to an inline view and use the SQL REPLACE function:
CREATE TABLE TestTable
(
Id INT IDENTITY(1,1) NOT NULL,
Data XML NOT NULL
)
INSERT TestTable (Data) VALUES ('<root>
<id>1</id>
<timestamp>16-10-2017 19:24:55</timestamp>
</root>')
UPDATE TestTable
SET Data.modify('replace value of
(/root/timestamp/text())[1] with sql:column("T2.NewData")')
FROM TestTable T1
INNER JOIN (
SELECT Id
, REPLACE( Data.value('(/root/timestamp/text())[1]', 'nvarchar(max)'), '-', '/') AS NewData
FROM TestTable
) T2
ON T1.Id = T2.Id
SELECT * FROM TestTable
Note: this answer assumes you want to have this formatted for the purpose of displaying this as a string, and not parsing the content as a xs:dateTime. If you want the latter, Shungo's answer will format it as such.
It seems that replace is not a supported XQuery function in SQL Server at the time of this writing. You can use the substring function along with the concat function in a "replace value of (XML DML)" though.
CREATE TABLE #t(x XML);
INSERT INTO #t(x)VALUES(N'<root><id>1</id><timestamp>16-10-2017 19:24:55</timestamp></root>');
UPDATE
#t
SET
x.modify('replace value of (/root/timestamp/text())[1]
with concat(substring((/root/timestamp/text())[1],1,2),
"/",
substring((/root/timestamp/text())[1],4,2),
"/",
substring((/root/timestamp/text())[1],7)
) ')
SELECT*FROM #t;
Giving as a result:
<root><id>1</id><timestamp>16/10/2017 19:24:55</timestamp></root>
If there's no external need you have to fullfill, you should use ISO8601 date/time strings within XML.
Your dateTime-string is culture related. Reading this on different systems with differing language or dateformat settings will lead to errors or - even worse!!! - to wrong results.
A date like "08-10-2017" can be the 8th of October or the 10th of August...
The worst point is, that this might pass all your tests successfully, but will break on a customer's machine with strange error messages or bad results down to real data dammage!
Switching the hyphens to slashes is just cosmetic! An XML is a strictly defined data container. Any non-string data must be represented as a secure convertible string.
This is what you should do:
DECLARE #tbl TABLE(ID INT IDENTITY,YourXML XML);
INSERT INTO #tbl VALUES
(N'<root>
<id>1</id>
<timestamp>16-10-2017 19:24:55</timestamp>
</root>');
UPDATE #tbl SET YourXml.modify(N'replace value of (/root/timestamp/text())[1]
with concat( substring((/root/timestamp/text())[1],7,4), "-"
,substring((/root/timestamp/text())[1],4,2), "-"
,substring((/root/timestamp/text())[1],1,2), "T"
,substring((/root/timestamp/text())[1],12,8)
) cast as xs:dateTime?');
SELECT * FROM #tbl;
The result
<root>
<id>1</id>
<timestamp>2017-10-16T19:24:55</timestamp>
</root>
you can try string replacement like below
update testtable
set data= cast(
concat(
left(cast(data as varchar(max)),charindex('<timestamp>',cast(data as varchar(max)))+len('<timestamp>')-1),
replace(
substring(
cast(data as varchar(max)),
len('<timestamp>') +
charindex( '<timestamp>', cast(data as varchar(max))) ,
charindex('</timestamp>',cast(data as varchar(max)))
-charindex('<timestamp>',cast(data as varchar(max)))
-len('<timestamp>')
),
'-','/'),
right(cast(data as varchar(max)),len(cast(data as varchar(max)))-charindex('</timestamp>',cast(data as varchar(max)))+1)
) as xml)
select *
from testtable
working demo

SQL Server FOR XML PATH: Set xml-declaration or processing instruction "xml-stylesheet" on top

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.

Delete empty XML nodes using T-SQL FOR XML PATH

I'm using FOR XML PATH to construct XML out of a table in SQL Server 2008R2. The XML has to be constructed as follows:
<Root>
<OuterElement>
<NumberNode>1</NumberNode>
<FormattedNumberNode>0001</KFormattedNumberNode>
<InnerContainerElement>
<InnerNodeOne>0240</InnerNodeOne>
<InnerNodeStartDate>201201</InnerNodeStartDate>
</InnerContainerElement>
</OuterElement>
</Root>
According to the schema files, the InnerContainerElement is optional, while the InnerNodeOne is required. The schema files aren't set up by me, are quite complex, referring each other and not having explicit XSD-namespaces, so I can't easily load them into the database.
The XML has to be created from a table, which is filled using the following query:
SELECT
1 AS NumberNode
, '0001' AS [FormattedNumberNode]
, '0240' AS [InnerNodeOne]
, '201201' AS [InnerNodeStartDate]
INTO #temporaryXMLStore
UNION
SELECT
2 AS NumberNode
, '0001' AS [FormattedNumberNode]
, NULL AS [InnerNodeOne]
, NULL AS [InnerNodeStartDate]
I can think of two ways to construct this XML with FOR XML PATH.
1) Using 'InnerContainerElement' as named result from an XML subquery:
SELECT
NumberNode
, [FormattedNumberNode]
, (
SELECT
[InnerNodeOne]
, [InnerNodeStartDate]
FOR XML PATH(''), TYPE
) AS [InnerContainerElement]
FROM #temporaryXMLStore
FOR XML PATH('OuterElement'), ROOT('Root') TYPE
2) Using 'InnerContainerElement' as an output element from an XML subquery, but without naming it:
SELECT
NumberNode
, [FormattedNumberNode]
, (
SELECT
[InnerNodeOne]
, [InnerNodeStartDate]
FOR XML PATH('InnerContainerElement'), TYPE
)
FROM #temporaryXMLStore
FOR XML PATH('OuterElement'), ROOT('Root'), TYPE
However, none of them gives the desired result: in both cases, the result looks like
<Root>
<OuterElement>
<NumberNode>1</NumberNode>
<FormattedNumberNode>0001</FormattedNumberNode>
<InnerContainerElement>
<InnerNodeOne>0240</InnerNodeOne>
<InnerNodeStartDate>201201</InnerNodeStartDate>
</InnerContainerElement>
</OuterElement>
<OuterElement>
<NumberNode>2</NumberNode>
<FormattedNumberNode>0001</FormattedNumberNode>
<InnerContainerElement></InnerContainerElement>
<!-- Or, when using the second codeblock: <InnerContainerElement /> -->
</OuterElement>
</Root>
Whenever InnerContainerElement is empty, it is still displayed as an empty element. This is invalid according to the schema: whenever the element InnerContainerElement is in the XML, InnerNodeOne is required too.
How do I construct my FOR XML PATH query in such a way that the InnerContainerElement is left out whenever it's empty?
You need to make sure that the InnerContainerElement has zero rows for the case when there is no content.
select T.NumberNode,
T.FormattedNumberNode,
(
select T.InnerNodeOne,
T.InnerNodeStartDate
where T.InnerNodeOne is not null or
T.InnerNodeStartDate is not null
for xml path('InnerContainerElement'), type
)
from #temporaryXMLStore as T
for xml path('OuterElement'), root('Root')
Or you could specify the element InnerContainerElement as a part of a column alias.
select T.NumberNode,
T.FormattedNumberNode,
T.InnerNodeOne as 'InnerContainerElement/InnerNodeOne',
T.InnerNodeStartDate as 'InnerContainerElement/InnerNodeStartDate'
from #temporaryXMLStore as T
for xml path('OuterElement'), root('Root')

Resources