Xml select query xpath is slow - sql

My XML structure:
<Items>
<Item>
<guid>FC550573-7171-997F-752D-8D65590CBFD6</guid>
<Objects>
<Object>
<type>0</type>
<guid>E10D9DA9-2C8D-8024-2F07-DF21395811BF</guid>
</Object>
<Object>
<type>0</type>
<guid>D8338400-35C7-781E-A039-C0FDDF80714A</guid>
</Object>
</Objects>
</Item>
</Items>
When filling the Objects Table:
CREATE TABLE [dbo].[Objects](
[item_guid] [varchar](36) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[type] [int] NOT NULL,
[guid] [varchar](36) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL
) ON [PRIMARY]
Using the Query:
INSERT INTO [dbname].[dbo].[Objects]
([item_guid]
,[type]
,[guid])
SELECT
X.source.query('../../guid').value('.','VARCHAR(36)') as item_guid,
X.source.query('type').value('.','INT') as type,
X.source.query('guid').value('.','VARCHAR(36)') as guid
FROM(
Select xmldata from XmlFiles where fullpath=#fp
) AS T(x)
CROSS APPLY x.nodes('Items/Item/Objects/Object') As X(source)
This line is making the query VERY slow:
X.source.query('../../guid').value('.','VARCHAR(36)') as item_guid
What is the proper approach here?

Using /text() to get the value is good for performance on untyped XML. It can also be bad to use the parent axis ../.. (as #marc_s suggested).
Here is a version with a extra cross apply and /text() to get the values.
Try this:
select T2.N.value('(guid/text())[1]', 'uniqueidentifier') as item_guid,
T3.N.value('(type/text())[1]', 'int') as type,
T3.N.value('(guid/text())[1]', 'uniqueidentifier') as guid
from (SELECT xmldata FROM dbo.XmlFiles WHERE fullpath = #fp) as T1(N)
cross apply T1.N.nodes('Items/Item') as T2(N)
cross apply T2.N.nodes('Objects/Object') as T3(N)
You have to be the judge which query is the fastest for you.

I just want to add, in case anybody else runs across this, that adding the following option makes a huge difference.
OPTION (OPTIMIZE FOR (#testXml = NULL))
If you'd like to test this yourself, here is a short test script I was running. Just look at the estimated subtree cost between these.
declare #testXml xml set #testXml = '<filters><filter name="test name" type="GREATERTHAN">1</filter><filter name="CLAIMID" type="GREATERTHAN">1</filter></filters>'
select x.value('#name','nvarchar(100) ') filtername,
x.value('.','nvarchar(200)')filtervalue,
x.value('#type','nvarchar(50) ') filtertype
from #testXml.nodes('/filters/filter') as ref(x)
--vs...
select x.value('#name','nvarchar(100) ') filtername,
x.value('.','nvarchar(200)')filtervalue,
x.value('#type','nvarchar(50) ') filtertype
from #testXml.nodes('/filters/filter') as ref(x)
OPTION (OPTIMIZE FOR (#testXml = NULL))

Try this,
We will create a temp table variable for store this xml values & insert to corresponding table Objects
//..Xml value to temp variable
Declare #x xml ='<Items><Item><guid>FC550573-7171-997F-752D-8D65590CBFD6</guid><Objects><Object>
<type>0</type><guid>E10D9DA9-2C8D-8024-2F07-DF21395811BF</guid></Object><Object>
<type>0</type><guid>D8338400-35C7-781E-A039-C0FDDF80714A</guid></Object></Objects>
</Item></Items>';
Declare #Temp_Tbl table (RowId int identity, item_guid nvarchar(36), [type] int, [guid] nvarchar(36));
Insert into #Temp_Tbl SELECT #x.value('(/Items/Item/guid)[1]', 'nvarchar(36)'),
Cont.value('(type)[1]', 'int'), Cont.value('(guid)[1]', 'nvarchar(36)')
FROM #x.nodes('/Items/Item/Objects/Object') AS Obj(Cont);
INSERT INTO [dbo].[Objects] Select item_guid,[type],[guid] from #Temp_Tbl;

Related

xml.exist - sql:column to sequence

I try to create a query with xml.exist by using the sql:column-function where the value of the column should be transformed into a usable XQuery-sequence.
This is the query with a static sequence, which is working.
SELECT
FieldA
, FieldB
FROM
MyTable
WHERE
FieldC.exist('DSAuth/role[#id=("195", "267", "350")')
To get a dynamic sequence i would use a table function with returns a table with a column "IDsequence" that have all id's as a string. e.g. ' "195", "267", "350" '.
The table function should return only one row! With multiple rows it works, but i have to group the result at last, which is bad for performance.
SELECT
FieldA
, FieldB
FROM
MyTable
CROSS APPLY
dbo.MyFunc(0) AS f
WHERE
FieldC.exist('DSAuth/role[#id=sql:column("f.IDsequence")]')
Is there a way to get a usable sequence for XQuery from sql:column("f.IDsequence")?
Thanks for help.
Edit:
The problem in performance is that FieldB (and a few more fields) is a xml column, so i have to convert it to group by.
SELECT
FieldA
, CAST(CAST(FieldB AS nvarchar(max)) AS xml) AS FieldB
FROM
MyTable
CROSS APPLY
dbo.MyFunc(0) AS f
WHERE
FieldC.exist('DSAuth/role[#id=sql:column("f.IDsequence")]')
GROUP BY
FieldA
, CAST(FieldB AS nvarchar(max))
I don't know if this is the easiest approach, but you might try to include your list into the XML and use it in the predicate like here:
--A mockup-table to show the principles
DECLARE #tbl TABLE(TheXml XML)
INSERT INTO #tbl VALUES
(
N'<root>
<a>1</a>
<a>2</a>
</root>'
)
,(
N'<root>
<a>1</a>
</root>'
)
,(
N'<root>
<a>3</a>
<a>4</a>
</root>'
);
--This is easy: Just one value
SELECT * FROM #tbl WHERE TheXml.exist('/root/a[. cast as xs:int?=1]')=1;
--This is your sequence.
--But you cannot introduce the value list with sql:column() or sql:variable().
SELECT * FROM #tbl WHERE TheXml.exist('/root/a[. cast as xs:int?=(2,3)]')=1;
--But you can add the values to your XML before like in this cte
DECLARE #values TABLE(val INT);
INSERT INTO #values VALUES(2),(3);
WITH cte(NewXml) AS
(
SELECT (SELECT (SELECT val AS [#val] FROM #values FOR XML PATH('values'),TYPE)
,TheXml AS [*]
FOR XML PATH(''),TYPE
)
FROM #tbl t
)
SELECT NewXml.query('/root') TheXml
FROM cte
WHERE NewXml.exist('/root/a[. cast as xs:int?=/values/#val]')=1;
The intermediate XML looks like this:
<values val="2" />
<values val="3" />
<root>
<a>1</a>
<a>2</a>
</root>
The final .query('/root') will return the previous XML unchanged.
UPDATE
The same would work with an introduction on string base like here:
WITH cte(NewXml) AS
(
SELECT (SELECT CAST(N'<values val="2" /><values val="3" />' AS XML)
,TheXml AS [*]
FOR XML PATH(''),TYPE
)
FROM #tbl t
)
SELECT NewXml.query('/root') TheXml
FROM cte
WHERE NewXml.exist('/root/a[. cast as xs:int?=/values/#val]')=1

Generate XML of the Structure of a table when it's empty in SQL Server

I am trying to generate a table containing one row that contains the XML of each temporary table in a stored procedure.
The problem is when the table is empty the FOR XML PATH('RS'), root('OR_RS') returns null.
What I want is: when the table is empty, return the structure of this table
something like this :
<OR_RS>
<ContactCode></ContactCode>
<EmailPaper></EmailPaper>
<ShortEmail></ShortEmail>
<WebSite></WebSite>
<Providers></Providers>
</OR_RS>
I have tried to do this :
-- ,ISNULL( (SELECT * FROM #OR_RS FOR XML PATH('RS'), root('OR_RS')), (SELECT ISNULL(ContactCode,'') , ISNULL(EmailPaper,'') , ISNULL(ShortEmail,'') , ISNULL(WebSite,'') , ISNULL(Providers,'') FROM #OR_RS FOR XML RAW)) as OR_RS
but it returns always Null:
SELECT 1 as Id, (SELECT * FROM #OR_MK FOR XML PATH('MK'), root('OR_MK') ) as OR_MK
,(SELECT * FROM #OR_CA FOR XML PATH('CA'), root('OR_CA') ) as OR_CA
-- ,ISNULL( (SELECT * FROM #OR_RS FOR XML PATH('RS'), root('OR_RS')), (SELECT ISNULL(ContactCode,'') , ISNULL(EmailPaper,'') , ISNULL(ShortEmail,'') , ISNULL(WebSite,'') , ISNULL(Providers,'') FROM #OR_RS FOR XML AUTO)) as OR_RS
,(SELECT * FROM #OR_RS FOR XML PATH('RS'), root('OR_RS')) as OR_RS
,(SELECT * FROM #OR_DC FOR XML PATH('DC'), root('OR_DC') ) as OR_DC
,(SELECT * FROM #BENEFICIARY FOR XML PATH('BEN'), root('BENEFICIARY') ) as BENEFICIARY
,(SELECT * FROM #MK_REPORT for XML PATH('REP'), root('MK_REPORT')) as MK_REPORT
Whatever you try to do with this...
You might want to have a look at sp_describe_first_result_set, but, since this is a procedure, it will not be that easy to get its result into your query...
You can use a trick (a RIGHT JOIN) to enforce a resultset. And with ELEMENTS XSINIL you force the engine to include the column in any case. Otherwise XML takes missing elements as NULL, so your empty result would not be written into the result XML at all otherwise. Try this:
DECLARE #tbl TABLE(ID INT IDENTITY,SomeValue INT,SomeString VARCHAR(100));
SELECT *
FROM #tbl
RIGHT JOIN (SELECT 1 AS x) AS tbl ON 1=1
FOR XML RAW, ELEMENTS XSINIL
The result
<row xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ID xsi:nil="true" />
<SomeValue xsi:nil="true" />
<SomeString xsi:nil="true" />
<x>1</x>
</row>
If you want to get more details, you might add ,XMLDATA or ,XMLSCHEMA to generate full meta data.

Query XML field in SQL Server

I am using sql server 2008 R2. I have table X with Column XXML with the following structure
<rec>
<set>
<Raw CLOrderID="GGM-30-08/24/10" Rej="Preopen" Sym="A" Tm="06:36:29.524" />
</set>
</rec>
I want to parse above column XXML and return output as below:
CLOrderID Rej Sym Tm
GGM-30-08/24/10 Preopen A 06:36:29.524
Use nodes() to shred the XML and value() to extract the attribute values.
select T.X.value('#CLOrderID', 'nvarchar(100)') as CLOrderID,
T.X.value('#Rej', 'nvarchar(100)') as Rej,
T.X.value('#Sym', 'nvarchar(100)') as Sym,
T.X.value('#Tm', 'time(3)') as Tm
from dbo.X
cross apply X.XXML.nodes('/rec/set/Raw') as T(X)
If you know for sure that you only will have one row extracted for each XML you can get the attribute values directly without shredding first.
select X.XXML.value('(/rec/set/Raw/#CLOrderID)[1]', 'nvarchar(100)') as CLOrderID,
X.XXML.value('(/rec/set/Raw/#Rej)[1]', 'nvarchar(100)') as Rej,
X.XXML.value('(/rec/set/Raw/#Sym)[1]', 'nvarchar(100)') as Sym,
X.XXML.value('(/rec/set/Raw/#Tm)[1]', 'time(3)') as Tm
from dbo.X
This can be done with a few for xml calls. This structure also remains flexible for future schema changes, provided /rec/set/Raw/#* is present. If that changes, you can always add a pipe with the new path for the attributes you're wanting to grab. Hope this helps.
declare #x table (id int identity(1,1) primary key, x xml)
insert into #x (x) values ('<rec>
<set>
<Raw CLOrderID="GGM-30-08/24/10" Rej="Preopen" Sym="A" Tm="06:36:29.524" />
</set>
</rec>')
select a.id, cast((
select (
select x.attribs.value('local-name(.)','nvarchar(20)') + ' '
from #x t
outer apply t.x.nodes('/rec/set/Raw/#*') x(attribs)
where t.id = a.id
for xml path('')
), (
select x.attribs.value('.','nvarchar(20)') + ' '
from #x t
outer apply t.x.nodes('/rec/set/Raw/#*') x(attribs)
where t.id = a.id
for xml path('')
)
for xml path('')
) as varchar(500))
from #x a

Querying a table that contains an XML column

I have a table that contains an xml column, my table looks like the following:
MyTable
Id(Pk, int,not null)
Name(varchar(50), not null)
Value(XML(.), not null)
The type of Value is XML
I've tried the following query and of course it is not working
/****** Script ******/
SELECT TOP 1000 [Id]
,[Name]
,[Value]
FROM [Value]
where Value like '%something%'
How can I get columns that contains something in their xml value
Would this work? I'm assuming you meant to select FROM MyTable, not from [Value]:
SELECT TOP 1000 [Id]
,[Name]
,[Value]
FROM [MyTable]
where CAST(Value AS VARCHAR(MAX)) like '%something%'
XML
<root>
<role>Alpha</role>
<role>Beta</role>
<role>Gamma</role>
</root>
select [Name] ,[Value]
from Value
where Value.value('(/root/role)[1]', 'varchar(max)') like '%Beta%'
also refer How can I query a value in SQL Server XML column

MS SQL 2005 Table to XML

I have a simple table in SQL Server 2005, I wish to convert this to XML (using the "FOR XML" clause). I'm having trouble getting my XML to look like the required output.
I've tried looking through various tutorials on the web, but I am struggling. Can someone help?
The table I have looks like this
TYPE,GROUP,VALUE
Books,Hardback,56
Books,Softcover,34
CDs,Singles,45
CDS,Multis,78
The output style I need is:
<data>
<variable name="TYPE">
<row>
<column>GROUP</column>
<column>VALUE</column>
</row>
<row>
<column>GROUP</column>
<column>VALUE</column>
</row>
</variable>
<variable name="TYPE">
<row>
<column>GROUP</column>
<column>VALUE</column>
</row>
<row>
<column>GROUP</column>
<column>VALUE</column>
</row>
</variable>
</data>
Edit:
As far as I can tell I require the multiple values. I'm generating XML for use with Xcelsius (Linking XML and Xcelsius) so have no control over in the formatting of the XML. I can generate the XML using ASP as per the linked tutorial, but I was hoping to get it straight from SQL Server.
Edit 2:
I was hoping for something elegant and tidy... but Godeke's example got the closest. Some fiddling with the SQL and I've come up with:
select
"type" as '#name',
"group" as 'row/column',
null as 'row/tmp',
"value" as 'row/column'
from tableName
for xml path('variable'), root('data')
Outputs almost in the exact way I wanted. The null/tmp line doesn't even output; it is just preventing the concatenation. Still the tag <variable name="TYPE"> repeats for each row, which I can't have.
As close as I can get is this:
select "type" as '#name', "group" as 'row/column1', "value" as 'row/column2'
from tableName
for xml path('variable'), root('data')
Naming two items the same ("column" and "column") isn't something I know how to do in one pass, but on the other hand it is an odd XML schema choice; normally elements have unique names if they contain distinct data. The obvious choice (name them both 'row/column') simply concatenates them in the output into one value.
Also note that each returned row will be a "variable" element distinct from the others. To get the nesting without redundant records will require a subquery:
select distinct "type" as '#name'
from Agent
for xml path('variable'), root('data')
was my first thought, but the distinct prevents nesting.
All this makes me think that to get the exact output you need you might have to use EXPLICIT mode. Perhaps my problem is for something like this I punt and use a DOMDocument in code :).
I prefer using for XML PATH, it provides a nicer way to control your elements etc.
See
But this is quite tricky
/*
create table #tablename
(
[type] varchar(20),
[group] varchar(20),
[value] varchar(20)
)
insert into #tablename select 'type1','group11','value111'
insert into #tablename select 'type1','group11','value112'
insert into #tablename select 'type1','group12','value121'
insert into #tablename select 'type1','group12','value122'
insert into #tablename select 'type2','group21','value211'
insert into #tablename select 'type2','group21','value212'
insert into #tablename select 'type2','group22','value221'
insert into #tablename select 'type2','group22','value222'
alter table #tablename add id uniqueidentifier
update #tablename set id = newid()
*/
select [type] as '#name',
(select
(select [column] from
(
select [group] as 'column', tbn1.type, tbn2.[group]
from #tablename tbn3 WHERE tbn3.type = tbn1.type and tbn2.[group] = tbn3.[group]
union
select [value], tbn1.type, tbn2.[group]
from #tablename tbn3 WHERE tbn3.type = tbn1.type and tbn2.[group] = tbn3.[group]
) as s
for xml path(''),type
)
from #tablename tbn2
where tbn2.type = tbn1.type
for xml path('row3'), type
)
from #tableName tbn1
GROUP BY [type]
for xml path('variable'), root('data')
gives you what you are asking for I, but elegant and tidy it is not.
The script below produces the desired format
<DATA>
<VARIABLE TYPE="Books">
<row TYPE="Books">
<GROUP>Hardback</GROUP>
<VALUE>56</VALUE>
</row>
<row TYPE="Books">
<GROUP>Softcover</GROUP>
<VALUE>34</VALUE>
</row>
</VARIABLE>
<VARIABLE TYPE="CDs">
<row TYPE="CDs">
<GROUP>Singles</GROUP>
<VALUE>45</VALUE>
</row>
<row TYPE="CDS">
<GROUP>Multis</GROUP>
<VALUE>78</VALUE>
</row>
</VARIABLE>
</DATA>
Invoke
DECLARE #tblItems table (
[TYPE] varchar(50)
,[GROUP] varchar(50)
,[VALUE] int
)
DECLARE #tblShredded table (
[TYPE] varchar(50)
,[XmlItem] xml
)
DECLARE #xmlGroupValueTuples xml
insert into #tblItems([TYPE],[GROUP],[VALUE]) values( 'Books','Hardback',56)
insert into #tblItems([TYPE],[GROUP],[VALUE]) values( 'Books','Softcover',34)
insert into #tblItems([TYPE],[GROUP],[VALUE]) values( 'CDs','Singles',45)
insert into #tblItems([TYPE],[GROUP],[VALUE]) values( 'CDS','Multis',78)
SET #xmlGroupValueTuples =
(
SELECT
"#TYPE" = [TYPE]
,[GROUP]
,[VALUE]
FROM #tblItems
FOR XML PATH('row'), root('Root')
)
INSERT #tblShredded([TYPE], XmlItem)
SELECT
[TYPE] = XmlItem.value('./row[1]/#TYPE', 'varchar(50)')
,XmlItem
FROM dbo.tvfShredGetOneColumnedTableOfXmlItems(#xmlGroupValueTuples)
SELECT
(
SELECT
VARIABLE =
(
SELECT
"#TYPE" = t.[TYPE]
,(
SELECT
tInner.XmlItem.query('./child::*')
FROM #tblShredded tInner
WHERE tInner.[TYPE] = t.[TYPE]
FOR XML PATH(''), ELEMENTS, type
)
FOR XML PATH('VARIABLE'),type
)
)
FROM #tblShredded t
GROUP BY
t.[TYPE]
FOR XML PATH(''), ROOT('DATA')
where
-- Example Inputs
/*
DECLARE #xmlListFormat xml
SET #xmlListFormat =
'
<XmlListRoot>
<Item>004421UB7</Item>
<Item>59020UH24</Item>
<Item>542514NA8</Item>
</XmlListRoot>
'
*/
-- =============================================
-- Author: 6eorge Jetson
-- Create date: 01/22/3003
-- Description: Shreds an input XML list conforming to the expected list schema
-- =============================================
CREATE FUNCTION [dbo].[tvfShredGetOneColumnedTableOfXmlItems] (#xmlListFormat xml)
RETURNS
#tblResults TABLE (XmlItem xml)
AS
BEGIN
INSERT #tblResults
SELECT
tblShredded.colXmlItem.query('.') as XmlItem
FROM
#xmlListFormat.nodes('/child::*/child::*') as tblShredded(colXmlItem)
RETURN
END