get xml value from an NVARCHAR(MAX) column - sql-server

I read many related posts and tried to get the xml value from a column with type NVARCHAR(MAX) for any specific tags.
CREATE TABLE dataTable (RECID NVARCHAR(MAX),XMLRECORD NVARCHAR(MAX));
My XMLRECORD column will contain a data with tag like
<row id='1'>
<c2>Account-sample</c2>
</row>
Below attached is a select query that I created, which yielded a CLOB instead of the actual value. Any idea on how to get the actual value? (i.e. Account-sample)
select b.x.value('data(/row/c2)[1]', 'NVARCHAR(max)')
from dataTable a
cross apply(select cast(cast(XMLRECORD as VARCHAR(max)) as XML) x) b;

Use the Below Query
select *, try_cast(xmlrecord as xml).value('(row/c2)[1]', 'nvarchar(500)') as c2
from dataTable

Related

T-SQL: Parse XML Data in from classes which inherit from a common base,

I'm trying to parse XML data in SQL Server. I have a XML column in a table, the XML stored in it can vary by type, but they all inherit from the same base type.
Row 1: has XML like so:
<Form>
<TaskType>1</TaskType>
--Other Properties ...
</Form>
Row 2: has XML like so:
<License>
<TaskType>2</TaskType>
--Other Properties ...
</License>
Normally I might parse XML with this T-SQL code snippet:
SELECT
xmlData.A.value('.', 'INT') AS Animal
FROM
#XMLToParse.nodes('License/TaskType') xmlData(A)
This doesn't work since in a view since I'm dependent on the name to find the node.
How can I always find the TaskType XML element in my XML content?
Please try the following solution.
XPath is using asterisk * as a wildcard.
http://www.tizag.com/xmlTutorial/xpathwildcard.php
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (ID INT IDENTITY PRIMARY KEY, xmldata XML);
INSERT #tbl (xmldata) VALUES
(N'<Form>
<TaskType>1</TaskType>
<TaskName>Clone</TaskName>
<!--Other XML elements-->
</Form>'),
(N'<License>
<TaskType>2</TaskType>
<TaskName>Copy</TaskName>
<!--Other XML elements-->
</License>');
-- DDL and sample data population, end
SELECT ID
, c.value('(TaskType/text())[1]', 'INT') AS TaskType
, c.value('(TaskName/text())[1]', 'VARCHAR(20)') AS TaskName
FROM #tbl
CROSS APPLY xmldata.nodes('/*') AS t(c);
Output
ID
TaskType
TaskName
1
1
Clone
2
2
Copy
Apparently you can just interate the nodes like so without being aware of their name:
SELECT xmlData.A.value('.', 'INT') AS Animal
FROM #XMLToParse.nodes('node()/TaskType') xmlData(A)

Pulling a value from Xml in a column

I have a table in Sql server that stores Xml data in one of its columns.
The Xml column data looks like this:
<TestDef Weight="0" FailValue="2" ConceptID="-327">
<ToleranceDef ObjectType="SomeName" TargetValue="0"TargetRange="2" />
</TestDef>
I need to write a query that fetches out all the conceptId's from each rows Xml column.
Here it would be -327
I know I can cast the Xml column to a nvarchar(max) then use some reg exp to get the value but not sure how to use the regular expression
Here's an example using a table variable. It will be the same concept with an actual table:
Declare #XmlTable table (
Id Integer Identity,
XmlValue XML
)
Insert Into #XmlTable (XmlValue) values ('<TestDef Weight="0" FailValue="2" ConceptID="-327"><ToleranceDef ObjectType="SomeName" TargetValue="0" TargetRange="2" /></TestDef>')
Insert Into #XmlTable (XmlValue) values ('<TestDef Weight="0" FailValue="2" ConceptID="-325"><ToleranceDef ObjectType="SomeName" TargetValue="0" TargetRange="2" /></TestDef>')
select
Id,
XmlValue,
XmlValue.value('(/TestDef/#ConceptID)[1]', 'integer') as ConceptId
from
#XmlTable

Using SQL to transpose/flatten XML structure to columns

I am using SQL Server (2008/2012) and I know there are similar answers from lots of searching, however I can't seem to find the appropriate example/pointers for my case.
I have an XML column in a SQL Server table holding this data:
<Items>
<Item>
<FormItem>
<Text>FirstName</Text>
<Value>My First Name</Value>
</FormItem>
<FormItem>
<Text>LastName</Text>
<Value>My Last Name</Value>
</FormItem>
<FormItem>
<Text>Age</Text>
<Value>39</Value>
</FormItem>
</Item>
<Item>
<FormItem>
<Text>FirstName</Text>
<Value>My First Name 2</Value>
</FormItem>
<FormItem>
<Text>LastName</Text>
<Value>My Last Name 2</Value>
</FormItem>
<FormItem>
<Text>Age</Text>
<Value>40</Value>
</FormItem>
</Item>
</Items>
So even though the structure of <FormItem> is going to be the same, I can have multiple (most commonly no more than 20-30) sets of form items..
I am essentially trying to return a query from SQL in the format below, i.e. dynamic columns based on /FormItem/Text:
FirstName LastName Age ---> More columns as new `<FormItem>` are returned
My First Name My Last Name 39 Whatever value etc..
My First Name 2 My Last Name 2 40
So, at the moment I had the following:
select
Tab.Col.value('Text[1]','nvarchar(100)') as Question,
Tab.Col.value('Value[1]','nvarchar(100)') as Answer
from
#Questions.nodes('/Items/Item/FormItem') Tab(Col)
Of course that hasn't transposed my XML rows into columns, and obviously is fixed with fields anyway.. I have been trying various "Dynamic SQL" approaches where the SQL performs a distinct selection of (in my case) the <Text> node, and then uses some sort of Pivot? but I couldn't seem to find the magic combination to return the results I need as a dynamic set of columns for each row (<Item> within the collection of <Items>).
I'm sure it can be done having seen so many very similar examples, however again the solution eludes me!
Any help gratefully received!!
Parsing the XML is fairly expensive so instead of parsing once to build a dynamic query and once to get the data you can create a temporary table with a Name-Value list and then use that as the source for a dynamic pivot query.
dense_rank is there to create the ID to pivot around.
To build the column list in the dynamic query it uses the for xml path('') trick.
This solution requires that your table has a primary key (ID). If you have the XML in a variable it can be somewhat simplified.
select dense_rank() over(order by ID, I.N) as ID,
F.N.value('(Text/text())[1]', 'varchar(max)') as Name,
F.N.value('(Value/text())[1]', 'varchar(max)') as Value
into #T
from YourTable as T
cross apply T.XMLCol.nodes('/Items/Item') as I(N)
cross apply I.N.nodes('FormItem') as F(N)
declare #SQL nvarchar(max)
declare #Col nvarchar(max)
select #Col =
(
select distinct ','+quotename(Name)
from #T
for xml path(''), type
).value('substring(text()[1], 2)', 'nvarchar(max)')
set #SQL = 'select '+#Col+'
from #T
pivot (max(Value) for Name in ('+#Col+')) as P'
exec (#SQL)
drop table #T
SQL Fiddle
select Tab.Col.value('(FormItem[Text = "FirstName"]/Value)[1]', 'varchar(32)') as FirstName,
Tab.Col.value('(FormItem[Text = "LastName"]/Value)[1]', 'varchar(32)') as LastName,
Tab.Col.value('(FormItem[Text = "Age"]/Value)[1]', 'int') as Age
from #Questions.nodes('/Items/Item') Tab(Col)
I wanted to add my "own answer" really just for completeness to possibly help others.. however it is most definitely based on the great help from #Mikael above!! so again, this is really for completeness only - all kudos to #Mikael.
Basically I ended up with the following proc. I needed to select some data/filter, and get some joined data too and allow some boolean filtering on some of the input params. Then drop into the next section which was create a temp table of my relational data and the required xml nodes via the cross apply. The final step was to then pivot the results/dynamically create the columns from the selected XML node..
CREATE PROCEDURE [dbo].[usp_RPT_ExtractFlattenentries]
#CompanyID int,
#MainSelector nvarchar(50) = null,
#SecondarySelector nvarchar(255) = null,
#DateFrom datetime = '01-jan-2012',
#DateTo datetime = '31-dec-2100',
#SysReference nvarchar(20) = null
AS
BEGIN
SET NOCOUNT ON;
-- Create the table var to hold the XML form data from the entries
declare #FeedbackXml table (
ID int identity primary key,
XMLCol xml,
CompanyName nvarchar(20),
SysReference nvarchar(20),
RecordDate datetime,
EntryName nvarchar(255),
MainSelector nvarchar(50)
)
-- STEP 1: Get the raw submission data based on the params passed in
-- *Note: The double casting is necessary as the "form" field is nvarchar (not varchar) and we need xml in UTF-8 format
begin
insert into #FeedbackXml
(XMLCol, CompanyName, SysReference, RecordDate, EntryName, MainSelector)
select cast(cast(e.form as nvarchar(max)) as xml), c.name, e.SysReference, e.RecordDate, e.name, e.wizard
from
entries s
left join
companies o on e.companies = c.ID
where
(#CompanyID = -1 or #CompanyID = e.companies)
and
(#MainSelector is null or #MainSelector = e.wizard)
and
(#SecondarySelector is null or #SecondarySelector = e.name)
and
(#SysReference is null or #SysReference = e.SysReference)
and
(e.RecordDate >= #DateFrom and e.RecordDate <= #DateTo)
end
-- STEP 2: Flatten the required XML structure to provide a base for the pivot, and include other fields we wish to output
select dense_rank() over(order by ID) as ID,
T.RecordDate, T.CompanyName, T.SysReference, T.EntryName, T.MainSelector,
F.N.value('(FieldNameNode/text())[1]', 'nvarchar(max)') as FieldName,
F.N.value('(FieldNameValue/text())[1]', 'nvarchar(max)') as FieldValue
into #TempData
from #FeedbackXml as T
cross apply T.XMLCol.nodes('/root/companies/') as I(N) -- Xpath to the desired node start point
cross apply I.N.nodes('company') as F(N) -- The actual node collection that forms the "field name" and "field value" data
-- STEP 3: Pivot the #TempData table creating a dynamic column structure based on the selected XML nodes in step 2
declare #SQL nvarchar(max)
declare #Col nvarchar(max)
select #Col =
(
select distinct ','+quotename(FieldName)
from #TempData
for xml path(''), type
).value('substring(text()[1], 2)', 'nvarchar(max)')
set #SQL = 'select CompanyName, SysReference, EntryName, MainSelector, RecordDate, '+#Col+'
from #TempData
pivot (max(FieldValue) for FieldName in ('+#Col+')) as P'
exec (#SQL)
drop table #TempData
END
Again, really only added this answer to provide a complete picture from my perspective, and may help others.

Update XML Node with Conversion in SQL Server 2005

I have a column that contains XML data, but is TEXT type and not XML type. (I have to leave it like this for another reason).
Basically i need to cast it to NText first and then XML. The only problem is my current format that works for selecting the Node value doesnt work to update it.
Error Message: Incorrect syntax near the keyword 'AS'.
UPDATE tbl_Module_RequestForms_Items
SET CAST(CAST(TicorOregon..tbl_Module_RequestForms_Items.XML AS NTEXT) AS XML).value('(//Record/Submitted)[1]', 'NVARCHAR(max)') = 'True'
WHERE CAST(CAST(TicorOregon..tbl_Module_RequestForms_Items.XML AS NTEXT) AS XML).value('(//Record/Submitted)[1]', 'NVARCHAR(max)') <> 'True'
XML Data:
<Record>
<Submitted>False</Submitted>
</Record>
There might be valid reason to store XML in a [n]varchar(max). If you want to only store the XML it is perfectly OK but if you want to modify parts of the XML using TSQL or if you need to query the XML for values or use node/attribute values in a where clause you should switch to XML where you can benefit from indexes on the data and skip the type conversions. Since text is deprecated you should at least consider to switch data type to [n]varchar(max)
If you had your data in a XML column you would use XML DML to modify the XML. In your case you would use replace value of like this.
update tbl_Module_RequestForms_Items
set XMLData.modify('replace value of (/Record/Submitted/text())[1] with "True"')
where XMLData.value('(/Record/Submitted)[1]', 'bit') = 0
Without the XML data type that is not possible so you have to extract the entire XML document, modify it and then update the table with the modified XML document.
You can of course do that using some kind of client development tool but it is also possible in TSQL.
Declare a table variable with the primary key from tbl_Module_RequestForms_Items and the XMLData column but as data type XML.
Copy the rows from tbl_Module_RequestForms_Items to the table variable that should be updated.
Update the XML using replace value of.
Apply the changes back to tbl_Module_RequestForms_Items.
Something like this where I assume that ID is the primary key for tbl_Module_RequestForms_Items and that your XML data is in column XMLData:
declare #T table
(
ID int primary key,
XMLData xml
)
insert into #T
select M.ID,
M.XMLData
from tbl_Module_RequestForms_Items as M
where cast(cast(XMLData as nvarchar(max)) as xml).value('(/Record/Submitted)[1]', 'bit') = 0
update #T
set XMLData.modify('replace value of (/Record/Submitted/text())[1] with "True"')
update M
set XMLData = cast(T.XMLData as nvarchar(max))
from tbl_Module_RequestForms_Items as M
inner join #T as T
on M.ID = T.ID

SQL Server: Output an XML field as tabular data using a stored procedure

I am using a table with an XML data field to store the audit trails of all other tables in the database.
That means the same XML field has various XML information. For example my table has two records with XML data like this:
1st record:
<client>
<name>xyz</name>
<ssn>432-54-4231</ssn>
</client>
2nd record:
<emp>
<name>abc</name>
<sal>5000</sal>
</emp>
These are the two sample formats and just two records. The table actually has many more XML formats in the same field and many records in each format.
Now my problem is that upon query I need these XML formats to be converted into tabular result sets.
What are the options for me? It would be a regular task to query this table and generate reports from it. I want to create a stored procedure to which I can pass that I need to query "<emp>" or "<client>", then my stored procedure should return tabular data.
does this help?
INSERT INTO #t (data) SELECT '
<client>
<name>xyz</name>
<ssn>432-54-4231</ssn>
</client>'
INSERT INTO #t (data) SELECT '
<emp>
<name>abc</name>
<sal>5000</sal>
</emp>'
DECLARE #el VARCHAR(20)
SELECT #el = 'client'
SELECT
x.value('local-name(.)', 'VARCHAR(20)') AS ColumnName,
x.value('.','VARCHAR(20)') AS ColumnValue
FROM #t
CROSS APPLY data.nodes('/*[local-name(.)=sql:variable("#el")]') a (x)
/*
ColumnName ColumnValue
-------------------- --------------------
client xyz432-54-4231
*/
SELECT #el = 'emp'
SELECT
x.value('local-name(.)', 'VARCHAR(20)') AS ColumnName,
x.value('.','VARCHAR(20)') AS ColumnValue
FROM #t
CROSS APPLY data.nodes('/*[local-name(.)=sql:variable("#el")]') a (x)
/*
ColumnName ColumnValue
-------------------- --------------------
emp abc5000
*/
Neither xyz432-54-4231 nor abc5000 is valid XML.
You can try to select only one particular format with a like statement, f.e.:
select *
from YourTable
where YourColumn like '[a-z][a-z][a-z][0-9][0-9][0-9][0-9]'
This would match 3 letters followed by 4 numbers.
A better option is probably to add an extra column to the table, where you save the type of the logging. Then you can use that column to select all "emp" or "client" rows.
An option would be to create a series of views that present the aduit table, per type in the relations that you're execpting
for example
select
c.value('name','nvarchar(50)') as name,
c.value('ssn', 'nvarchar(20)') as ssn
from yourtable
cross apply yourxmlcolumn.nodes('/client') as t(c)
you could then follow the same pattern for the emp
you could also create a view (or computed column) to identify each xml type like this:
select yourxmlcolumn.value('local-name(/*[1])', 'varchar(100)') as objectType
from yourtable
Use open xml method
DECLARE #idoc int
EXEC sp_xml_preparedocument #idoc OUTPUT, #xmldoc
SELECT * into #test
FROM OPENXML (#idoc, 'xmlfilepath',2)
WITH (Name varchar(50),ssn varchar(20)
)
EXEC sp_xml_removedocument #idoc
after you get the data in the #test
and you can manipulate this.
you may be put the diff data in diff xml file.

Resources