SQL XML Parsing Query for Element Hierarchy - sql

I am attempting to write a SQL Query that will take in an XML object of undefined schema (YAY!) and transform it to a two column table of ElementName, Value columns. I was able to get a simple query down after some time (I am not a SQL person by any means).
DECLARE #strXml XML
SET #strXml = '<xml>
<FirstName>TEST</FirstName>
<LastName>PERSON</LastName>
<DOB>1/1/2000</DOB>
<TestObject>
<SomeProperty>CHECKED</SomeProperty>
<EmbeddedObject>
<SomeOtherProperty>NOT CHECKED</SomeOtherProperty>
</EmbeddedObject>
</TestObject>
</xml>'
DECLARE #XmlMappings TABLE
(
NodeName VARCHAR(64),
Value VARCHAR(128)
)
INSERT INTO #XmlMappings
SELECT doc.col.value('fn:local-name(.)[1]', 'varchar(64)') AS ElementName,
doc.col.value('.', 'varchar(128)') AS Value
FROM #strXml.nodes('/xml/*') doc(Col)
SELECT * FROM #XmlMappings
This query can handle the simple condition of the specified XML with only the first level elements. However elements such as TestObject and EmbeddedObject end up flattened. What I am looking for is to get some type of mapping like
ElementName | Value
=====================================================
FirstName | TEST
LastName | PERSON
DOB | 1/1/2000
TestObject.SomeProperty | CHECKED
TestObject.EmbeddedObject.SomeOtherProperty | NOT CHECKED
The hard part for me is the hierarchical structure with the . operator. I don't care if it is some other delimiter than . that gets output, it is more of just getting the output done, and I don't know enough about XML in SQL to be able to know even what to query.
Please note that I can also not use OPENXML since this is looking to be deployed on SQL Azure which does not support that feature at this time.

With a CTE and cross apply
;with cte as
(
select
convert(varchar(100), x.n.value('fn:local-name(.)','varchar(100)') ) as path,
convert(varchar(100), x.n.value('fn:local-name(.)','varchar(100)') ) AS name,
x.n.query('*') AS children,
x.n.value('.','varchar(1000)') as value
from #strxml.nodes('/xml/*') AS x(n)
union all
select
convert(varchar(100), x.path + '.' + c.n.value('fn:local-name(.)','varchar(100)') ),
convert(varchar(100), c.n.value('fn:local-name(.)','varchar(100)') ) ,
c.n.query('*'),
c.n.value('.','varchar(1000)')
from cte x
cross apply x.children.nodes('*') AS c(n)
)
select path, value from cte where datalength(children) = 5

Related

How might I concatenate all values in a row into a string?

Suppose I have a row of data, store such as the following:
------------------------
| Col 1 | Col 2 | Col 3 |
|------------------------|
| Foo | Bar | Foobar |
How might I concatinate this into a single string, such as the below?
Foo-Bar-Foobar
The column headings (and number of column headings) in this table will not be known, so selecting by column name is not an option(?).
Please note that I am not trying to concatinate a list of values in a column, I am trying to concatinate the values stores in one single row. I would also prefer to avoid using pivots, as I will be working with large sets of data and do not want to take the hit to performance.
In such cases I really adore the mighty abilities of XML in dealing with generic sets:
SELECT STUFF(b.query('
for $element in ./*
return
<x>;{$element/text()}</x>
').value('.','nvarchar(max)'),1,1,'')
FROM
(
SELECT TOP 3 * FROM sys.objects o FOR XML PATH('row'),ELEMENTS XSINIL,TYPE
) A(a)
CROSS APPLY a.nodes('/row') B(b);
The result
sysrscols;3;4;0;S ;SYSTEM_TABLE;2017-08-22T19:38:02.860;2017-08-22T19:38:02.867;1;0;0
sysrowsets;5;4;0;S ;SYSTEM_TABLE;2009-04-13T12:59:05.513;2017-08-22T19:38:03.197;1;0;0
sysclones;6;4;0;S ;SYSTEM_TABLE;2017-08-22T19:38:03.113;2017-08-22T19:38:03.120;1;0;0
Remarks
Some things to mention
I use the ; as delimiter, as the - might break with values containing hyphens (e.g. DATE)
I use TOP 3 from sys.objects to create an easy-cheesy-stand-alone sample
Thx to Zohard Peled I added ELEMENTS XSINIL to force the engine not to omit NULL values.
UPDATE Create JSON in pre-2016 versions
You can try this to create a JSON-string in versions before 2016
SELECT '{'
+ STUFF(b.query('
for $element in ./*
return
<x>,"{local-name($element)}":"{$element/text()}"</x>
').value('.','nvarchar(max)'),1,1,'')
+ '}'
FROM
(
SELECT TOP 3 * FROM sys.objects o FOR XML PATH('row'),TYPE
) A(a)
CROSS APPLY a.nodes('/row') B(b);
The result
{"name":"sysrscols","object_id":"3","schema_id":"4","parent_object_id":"0","type":"S ","type_desc":"SYSTEM_TABLE","create_date":"2017-08-22T19:38:02.860","modify_date":"2017-08-22T19:38:02.867","is_ms_shipped":"1","is_published":"0","is_schema_published":"0"}
{"name":"sysrowsets","object_id":"5","schema_id":"4","parent_object_id":"0","type":"S ","type_desc":"SYSTEM_TABLE","create_date":"2009-04-13T12:59:05.513","modify_date":"2017-08-22T19:38:03.197","is_ms_shipped":"1","is_published":"0","is_schema_published":"0"}
{"name":"sysclones","object_id":"6","schema_id":"4","parent_object_id":"0","type":"S ","type_desc":"SYSTEM_TABLE","create_date":"2017-08-22T19:38:03.113","modify_date":"2017-08-22T19:38:03.120","is_ms_shipped":"1","is_published":"0","is_schema_published":"0"}
Hint
You might add ELEMENTS XSINIL to this query as well. This depends, if you'd like NULLs to simply miss, or if you want to include them as "SomeColumn":""
I use UnitE and this is what I would use to select the columns dynamically from the person table.
INFORMATION_SCHEMA.COLUMNS stores the column list for the table and the SELECT statement is built around that.
Declare #Columns NVARCHAR(MAX)
Declare #Table varchar(15) = 'capd_person'
SELECT #Columns=COALESCE(#Columns + ',', '') + COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE (TABLE_NAME=#Table )
EXEC('SELECT DISTINCT ' + #Columns + ' FROM ' + #Table)
You would need to change the EXEC command to suit your needs, using CONCAT as described before.
Simply do a SELECT CONCAT(col1,col2,col3) FROM table
However if you wish to make it neat
Use:
SELECT CONCAT(col1,'-',col2,'-',col3) FROM table.
Find more help here.
An improved version of JonTout's answer:
Declare #Columns NVARCHAR(MAX)
Declare #Table varchar(15) = 'TableName'
SELECT #Columns=COALESCE(#Columns + '+', '') +'CONVERT(varchar(max),ISNULL('+ COLUMN_NAME+',''''))+''-'''
FROM INFORMATION_SCHEMA.COLUMNS
WHERE (TABLE_NAME=#Table )
EXEC('SELECT ' + #Columns + ' FROM ' + #Table)

How to display XML column data

I have a table column consist with the XML files. I want to read XML data and display it.
I come up with the following code. But it read only one row in the column
want to display other XML data also
declare #xml xml
select #xml = event_data_XML from #temp
SELECT * FROM (
SELECT
CAST(f.x.query('data(#name)') as varchar(150)) as data_name,
CAST(f.x.query('data(value)') as varchar(150)) as data_value
FROM #xml.nodes('/event') as t(n)
CROSS APPLY t.n.nodes('data') as f(x)) X
PIVOT (MAX(data_value) FOR data_name IN (NTDomainName, DatabaseName, ServerName)) as pvt
Output should be like this(NTDomainName, DatabaseName, ServerName are xml data)
There are a bunch of ways you could do this. I'll show you a way I think you'd find easiest.
To start, here's a table with a little test data:
CREATE TABLE dbo.stuff (
id int identity (1,1) primary key
, event_data_xml xml
, create_date datetime default(getdate())
, is_active bit default(1)
);
INSERT INTO dbo.stuff (event_data_xml)
VALUES ('<event name="thing" package="as">something</event>')
INSERT INTO dbo.stuff (event_data_xml)
VALUES ('<event name="otherthing" package="as">something else</event>')
---All records
SELECT * FROM dbo.[stuff];
Make sense so far? Here's the query I'd use if I wanted to mix XML data and column data:
---Parsed up
SELECT event_data_xml.value('/event[1]', 'nvarchar(max)') AS [parsed element #text]
, event_data_xml.value('/event[1]/#name', 'nvarchar(max)') AS [parsed attribute value]
, create_date --column from table
FROM dbo.stuff
WHERE is_active = 1;
Using the value() function on the XML column passing in an xpath to what I want to display and SQL Server data type for how I want it returned.
Just make sure you're selecting a single value with your xpath expression.

Incorrect Results in my SQL select Query when parsing XML text column

Hi all I have a table that holds my business Id's and it is varchar(255) data type
I also have a separate table that stores an XML structured document in a text data type column when the business gets approved by a lender (it stores the companys information etc).
I am trying to return all business ID's that are NOT approved by a lender, the only way i can know this is if the business ID does not exist in the XML.
I cannot join on any tables as i do not have any relational data, but i am trying to subquery it.
Any ideas? here is what i have
Select bus_id
From dbo.tbl_business
Where bus_id Not In (
Select Cast(company_xml_info As Varchar(Max))
From tbl_company_reports
Where Cast(company_xml_info As Varchar(Max)) Is Not Null
And company_xml_info Like '%Business id="' + bus_id + '"%'
And company_xml_info Is Not Null
And company_xml_current_status = 'Approved'
)
Here is an example mark of something similar you can do. This should run fine in SQL Management Studio 2008 and up:
DECLARE #Data TABLE (BusinessId VARCHAR(8))
INSERT INTO #Data (BusinessId) VALUES ('A68'),('A69'),('A70');
DECLARE #CompanyXml TABLE (company_xml_info VARCHAR(MAX));
INSERT INTO #CompanyXml (company_xml_info ) VALUES ('<CompanyInfo>
<Businesses>
<Business id="A68">
<Businessceo>Test</Businessceo>
</Business>
</Businesses>
</CompanyInfo>')
,('<CompanyInfo>
<Businesses>
<Business id="A70">
<Businessceo>Test2</Businessceo>
</Business>
</Businesses>
</CompanyInfo>')
--Data as is
Select *
From #Data
--example of your code as is
SELECT *
From #CompanyXml
--exclusionary listing
SELECT *
From #Data
EXCEPT
--the secret of this is part 1 casting it to xml. Then you extend that with '.value'. That wants a structure to get to the Id.
--I wrap that in ()'s then say the first instance of that [1] as in theory you could have more instances and do very complex parsing.
--Then it needs a type of sql to transform this value into
SELECT CAST(company_xml_info AS XML).value('(CompanyInfo/Businesses/Business/#id)[1]', 'varchar(8)')
From #CompanyXml
Update 6-29-17
If you have something that has repeat elements in a tree structure of your XML, I prefer the 'nodes' method of repeating them and then you do not have to worry about using a first. You merely need to iterate through what you have from the use of the 'nodes' syntax and get a value like so
DECLARE #X XML = '<CompanyInfo><Businesses><Business id="C1405"/><Business id="C1408"/><Business id="C1408"/></Businesses> </CompanyInfo>'
SELECT
x.query('.')
, x.value('#id', 'varchar(8)')
FROM #X.nodes('/CompanyInfo/Businesses/Business') AS y(x)

SQL- Collect all data into a variable

i need to collect all return data into a variable using comma separated.
let say i have a select command like: select * from #temptable.
it's return:
Field1|Field2
-------------
Value1|Value2
Expected Result: #testvariable hold the value: 'Value1','Value2'
On this table their may have 2 columns and i need to store all the return result into a single variable. We can easily collect a single value like: select #var=column1 from #temptable. But i need to store all.Here the problem is, the number of column can be vary. Mean, number of column and name of column generate from another query.So, i can't mention the field name.I need a dynamic way to do it. on this table only one row will be return. Thanks in advance.
You can do this without dynamic SQL using XML
DECLARE #xml XML = (SELECT * FROM #temptable FOR XML PATH(''))
SELECT stuff((SELECT ',' + node.value('.', 'varchar(100)')
FROM #xml.nodes('/*') AS T(node)
FOR XML PATH(''), type).value('.','varchar(max)')
, 1, 1, '');
This can probably be simplified by someone more adept at XML querying than me.
Since your column names are dynamic, so first you have to take the column names as comma separated in a variable and then can use EXEC()
for example :-
//making comma seperated column names from table B
DECLARE #var varchar(1000)=SELECT SUBSTRING(
(SELECT ',' + Colnames
FROM TABLEB
ORDER BY Colnames
FOR XML PATH('')),2,200000)
//Execute the sql statement
EXEC('select '+#var+' from tableA')
if you want to get the value returned after execution of sql statement then you can use
sp_executesql (Transact-SQL)

Finding Out If a Table is Being Used by a Report

Is there anyway to find out if a particular table is being used by a report on the reporting server?
USE ReportServer
DECLARE #TEXTTOSEARCH AS VARCHAR(200)
SET #TEXTTOSEARCH = 'urtableorview'
;WITH XMLNAMESPACES
(DEFAULT 'http://schemas.microsoft.com/sqlserver/reporting/2005/01/reportdefinition',
'http://schemas.microsoft.com/SQLServer/reporting/reportdesigner' AS rd)
SELECT name
, x.value('CommandType[1]', 'VARCHAR(100)') AS CommandType
, x.value('CommandText[1]','VARCHAR(MAX)') AS CommandText
, x.value('DataSourceName[1]','VARCHAR(150)') AS DataSource
FROM (SELECT name,
CAST(CAST(content AS VARBINARY(MAX)) AS XML) AS reportXML
FROM Catalog
WHERE content IS NOT NULL AND type != 3) AS a
CROSS APPLY reportXML.nodes('/Report/DataSets/DataSet/Query') r(x)
WHERE x.value('CommandType[1]', 'VARCHAR(50)') IS NULL
AND x.value('CommandText[1]','VARCHAR(MAX)') LIKE '%' + #TEXTTOSEARCH + '%'
ORDER BY name
I found this similar but simpler query on a technet article by Ajit Kumar Thakur https://blogs.technet.microsoft.com/dbtechresource/2015/04/04/retrieve-ssrs-report-server-database-information/.
WITH Reports AS
(
SELECT *
, CONVERT( VARCHAR(MAX), CONVERT(VARBINARY(MAX), Content)) AS ReportContent
FROM Catalog
)
SELECT Name, [Path]
FROM Reports
WHERE ReportContent LIKE '%tablename%';
This is very useful when you need "to identify dependency [on] any table, procedure or function in any report. It extracts [the] XML content of each report, converts it to varchar and then search for given object [name]. Catalog table contains XML contents of all RDL files" in your report server.