Shred XML For Each Row in SQL Table - sql

I have a table that contains two columns, and ID, and XML data. I'd like to shred the XML for each ID. I'm pulling out a single value within the XML and all the XML is structured the same I'm just not sure how to loop through the table and apply XML query to each row.
The query I need to apply is as follows:
Select top 1
Element1 = XV.value('(.)[1]','nvarchar(32)')
from #xml.nodes('Parameters/Parameter/Value') as x(XV)
So the end results would have two columns, ID and shredded value from XML.

Without any knowledge about your actual XML and how you want to shred it to get some values it is impossible to answer in completness, but this shoudl point you in the right direction:
Returns the ID and the XML as is
SELECT ID
,TheXmlColumn
FROM YourTable
This returns the ID and a value out of your XML
SELECT ID
,TheXmlColumn.value('Some XPaht','SomeType') AS SomeValueFromXML
FROM YourTable
And if there are more embedded rows it would be something like this
SELECT ID
,nd.value('Some XPaht','SomeType') AS SomeValueFromXMLRow
FROM YourTable
OUTER APPLY TheXmlColumn.nodes('SomeXPath') AS A(nd)
My magic glass bulb tells me, that you might need something like this:
SELECT ID
,TheXmlColumn.value('(Parameters/Parameter/Value)[1]','nvarchar(max)') AS SomeValueFromXML
FROM YourTable

Related

How do i find max combination from given result string in SQL

Here is the output.
ID Stack
-----------------------------------
123 307290,303665,307285
123 307290,307285,303424,303665
123 307290,307285,303800,303665
123 307061,307290
I want output like only last three row. The reason is in 1st output line stack column all three numbers are available in output line 2 and 3 stack column, so I don't need output line 1.
But the output lines 2,3,4 is different so I want those lines in my result.
I have tried doing it with row_number() and charindex but I'm not getting the proper result.
Thank you.
All the comments telling you to change your database's structure are right! You really should avoid comma separated values. This is breaking 1.NF and will be a pain in the neck forever.
The result of the second CTE might be used to shift all data into a new 1:n related structure.
Something like this?
DECLARE #tbl TABLE(ID INT,Stack VARCHAR(100));
INSERT INTO #tbl VALUES
(123,'307290,303665,307285')
,(123,'307290,307285,303424,303665')
,(123,'307290,307285,303800,303665')
,(123,'307061,307290');
WITH Splitted AS
(
SELECT ID
,Stack
,ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS RowIndex
,CAST('<x>' + REPLACE(Stack,',','</x><x>') + '</x>' AS XML) Casted
FROM #tbl
)
,DerivedDistinctValues AS
(
SELECT DISTINCT
ID
,Stack
,RowIndex
,StackNr.value('.','int') AS Nr
FROM Splitted
CROSS APPLY Casted.nodes('/x') AS A(StackNr)
)
SELECT ddv1.ID
,ddv1.Stack
FROM DerivedDistinctValues AS ddv1
FULL OUTER JOIN DerivedDistinctValues AS ddv2 ON ddv1.RowIndex<>ddv2.RowIndex
AND ddv1.Nr=ddv2.Nr
WHERE ddv2.ID IS NULL
GROUP BY ddv1.ID,ddv1.Stack
This will be slow, especially with larger data sets.
Some explanation:
The first CTE will transform the CSV numbers to <x>307290</x><x>303665</x>... This can be casted to XML, which allows to generate a derived table returning all the numbers as rows. This happens in the second CTE calling the XQuery function .nodes().
The last query will do a full outer join - each with each. All rows, where there is at least one row without a corresponding row are to be kept.
But I assume, that this might not work with each and any situation (e.g. circular data)

XML column Data into rows

I have a requirement where I have XML data column in database, which I need to pull in the form of values from rows XML data column from database. My XML is like
<ListID><ID>169346</ID><ID>289492</ID><ID>315264</ID><ID>415265</ID></ListID>
<ListID><ID>169356</ID><ID>299492</ID><ID>315264</ID><ID>415265</ID></ListID>
And I want data to be pulled up like
ID
169346
289492
315264
415265
169356
299492
315264
415265
You can use something like this:
SELECT XC.value('.', 'int')
FROM dbo.YourTableHere
CROSS APPLY XmlColumn.nodes('//ID') AS XT(XC)
This basically takes every <ID> element that exists in the XML column, and extract the values as int and shows them in a result set.
Update: from your question, mentioning I have XML data column, I assumed that your column in the SQL Server table is in fact of type XML. And it should be, if you're storing XML in it!
But if it's not - then you need to cast your column to XML first, before using the function .nodes() in my code sample:
SELECT XC.value('.', 'int')
FROM dbo.YourTableHere
CROSS APPLY CAST(YourColumn AS XML).nodes('//ID') AS XT(XC)
Here is one other way is to convert it to XML & then convert it to row :
SELECT split.a.value('.', 'varchar(max)') ID
FROM
(
SELECT CAST(ID AS XML) AS String from <table_name>
) a
CROSS APPLY String.nodes('//ID') AS split(a);
Result :
ID
169346
289492
315264
415265
169356
299492
315264
415265

SQL return list of ntext and convert it to XML

I have a query that will return and a list of ntext, and in these ntext they contain XML value.
my question is how to convert each of ntext to xml and do logic with it
Query:
select a.content
from dbo.content as a
inner join dbo.xml_collection_tbl as b on a.xml_fg_id = b.xml_collection_id
where a.inherit_from='val1' and b.collection_title='val2' and a.content_table= 'val3'
result:
what I want to do here is to check rather the Query returns contain the value that I looking for. lets say the page title = "hello World"
I tried below.But it returns many empty rows and with one correct row
select cast(a.content_html as xml).query('(//root[pagetitle/text()="AAA"])') content_html1
from dbo.content as a
inner join dbo.xml_collection_tbl as b on a.xml_fg_id = b.xml_collection_id
where a.inherit_from='val1' and b.collection_title='val2' and a.content_table= 'val3'
expected result is: return only one row where it's not empty (row 54)
First of all: NTEXT, TEXT and IMAGE are deprecated for centuries and will not be supported in future versions! Get rid of this type as soon as possible!
SQL-Server does not store the XML as the text you see, but as a hierarchically stuctured tree. This makes the handling of an XML astonishingly fast (no parsing on string level!). Your approach has to parse each and every XML over and over, which is a very expensive operation! Change your XML's storage to the native XML type and you will be very happy with the new performance!
If you have to stick with this, you can try as such:
DECLARE #t TABLE (ID INT IDENTITY, YourXML NTEXT);
INSERT INTO #t VALUES('<root><pagetitle>111</pagetitle></root>')
,('<root><pagetitle>aaa</pagetitle></root>')
,('<root><pagetitle>222</pagetitle></root>')
SELECT A.CastedXML
,B.pt.query('.')
FROM #t AS t
CROSS APPLY(SELECT CAST(YourXML AS XML) AS CastedXML) AS A
CROSS APPLY A.CastedXML.nodes('/root/pagetitle[text()="aaa"]') AS B(pt);
Demo of XQuery expression https://learn.microsoft.com/en-us/sql/xquery/xquery-language-reference-sql-server to filter data
with sd as (
select cast(content_html as xml) as col
from (
values
('<root><pagetitle>FFF</pagetitle></root>')
,('<root><pagetitle>AAA</pagetitle></root>')
) as a(content_html)
)
select t.n.value('.[1]', 'varchar(100)') as content_html1
from sd
cross apply col.nodes('root/pagetitle[text()="AAA"]') t(n)

Retrieve data from a For XML subquery

I am creating a table as follows:
CREATE TABLE dbo.Test
(
A int,
B int
)
GO
INSERT INTO Test VALUES (1, 11)
GO
INSERT INTO Test VALUES (5, 55)
GO
INSERT INTO Test VALUES (4, 44)
GO
I have a query which converts this into XML as :
SELECT A,B
FROM Test
ORDER BY A
FOR XML AUTO, ROOT ('myroot'), ELEMENTS
I need to use the above query as a subquery to get the following result:
A B
1 11
4 44
5 55
I am trying a query like this but it gives an error:
SELECT Z.Value('#A', 'INT'),
Z.Value('#B', 'INT')
FROM (SELECT A, B
FROM Test
ORDER BY A
FOR XML AUTO,Elements, ROOT ('myroot')) Doc(Z)
Msg 4121, Level 16, State 1, Line 1
Cannot find either column "Z" or the user-defined function or aggregate "Z.Value", or the > name is ambiguous.
I can write a simple query like below to get the result but the requirement is that I have to convert it into XMl and then retrieve the same result from it using the subquery.
Select * from test order by A
I know that I can insert the records returned by For XML in a table variable and then use Cross apply to fetch the result but as said above, I am looking to get this done in a single query without any temporary table or temporary variable.
There're a several issues here. First, your xml looks like this:
<myroot>
<Test>
<A>1</A><B>11</B>
</Test>
<Test>
<A>4</A><B>44</B>
</Test>
<Test>
<A>5</A><B>55</B>
</Test>
</myroot>
And you're trying to fetch data as attributes (#A, #B). You need to fetch it as elements (A[1] or (A/text())[1]).
Second, you have to use type keyword if you want your xml to be xml type.
Third, to split data by rows you need nodes() function. So your query becomes:
select
D.Z.value('(A/text())[1]', 'int'),
D.Z.value('(B/text())[1]', 'int')
from (
select A, B
from Test
order by A
for xml auto, elements, root('myroot'), type
) as Doc(Z)
outer apply Doc.Z.nodes('myroot/Test') as D(Z)
BTW, I'd better to use attributes, like this:
select
D.Z.value('#A', 'int'),
D.Z.value('#B', 'int')
from (
select A, B
from Test
order by A
for xml raw('Test'), root('myroot'), type
) as Doc(Z)
outer apply Doc.Z.nodes('myroot/Test') as D(Z)
sql fiddle demo
You forgot TYPE mode (without it you get nvarchar instead of xml) and value keyword should be in lower-case.
Try this:
SELECT Z.Z.value('#A', 'INT'),
Z.Z.value('#B', 'INT')
FROM (
SELECT A, B
FROM Test
ORDER BY A
FOR XML AUTO, ROOT ('myroot'), TYPE
) Doc(Doc)
CROSS APPLY Doc.nodes('/myroot/Test')Z(Z)
But I would prefer generating XML without AUTO mode (your query would break down if you write dbo.Test instead of Test), in more declaretive way using PATH keyword:
SELECT Z.Z.value('#A', 'INT'),
Z.Z.value('#B', 'INT')
FROM (
SELECT A AS '#A', B AS '#B'
FROM dbo.Test
ORDER BY A
FOR XML PATH('Test'), ROOT ('myroot'), TYPE
) Doc(Doc)
CROSS APPLY Doc.nodes('/myroot/Test')Z(Z)

Compare Xml data in SQL

I have two tables with same NVARCHAR field that really contains XML data.
in some cases this really-XML-field is really same as one row in other table but differs in attributes order and therefor string comparison does not return the correct result!!!
and to determining the same XML fields ,I need to have a comparison like:
cast('<root><book b="" c="" a=""/></root>' as XML)
= cast('<root><book a="" b="" c=""/></root>' as XML)
but I get this Err Msg:
The XML data type cannot be compared or sorted, except when using the
IS NULL operator.
then what is the best solution to determine the same XML without re-casting them to NVARCHAR?
Why cast it at all? Just plug them into an XML column in a temp table and run Xquery to compare them to the other table. EDIT: Included example of the comparison. There are many, many ways to run the query against the XML to get the rows that are the same - exactly how that query is written is going to depend on preference, requirements, etc. I went with a simple group by/count, but a self join could be used, WHERE EXISTS against the columns that are being searched for duplicates, you name it.
CREATE TABLE #Test (SomeXML NVARCHAR(MAX))
CREATE TABLE #XML (SomeXML XML)
INSERT #Test (SomeXML)
VALUES('<root><book b="b" c="c" a="a"/></root>')
,('<root><book a="a" b="b" c="c"/></root>')
INSERT #XML (SomeXML)
SELECT SomeXML FROM #Test;
WITH XMLCompare (a,b,c)
AS
(
SELECT
x.c.value('#a[1]','char(1)') AS a
,x.c.value('#b[1]','char(1)') AS b
,x.c.value('#c[1]','char(1)') AS c
FROM #XML
CROSS APPLY SomeXMl.nodes('/root/book') X(C)
)
SELECT
a
,b
,c
FROM XMLCompare as a
GROUP BY
a
,b
,c
HAVING COUNT(*) >1