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
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.
Iv had another problem, i just change my UNION to UNION ALL and it work correctly but i want to add another XML path (at the beginning) - this will be a const for both querys.
(SELECT 1 AS "ns0:kindOfItem",
code AS "ns0:wholeCode",
REPLACE(weight, ',', '.') AS "ns0:weight",
1 AS "ns0:ammountOfNumbers",
(SELECT price AS "ns0:value",
'EUR' as "ns0:currency"
FOR XML PATH ('ns0:sendedItems'), TYPE),
(SELECT
'EUR' as "ns0:currency"
FOR XML PATH ('ns0:present'), TYPE)
FROM [PL].[dbo].[dk_documents] where id in (1,2,3)
UNION ALL
(SELECT 1 AS "ns0:kindOfItem",
code AS "ns0:wholeCode",
REPLACE(weight, ',', '.') AS "ns0:weight",
1 AS "ns0:ammountOfNumbers",
(SELECT price AS "ns0:value",
'EUR' as "ns0:currency"
FOR XML PATH ('ns0:sendedItems'), TYPE),
(SELECT
'EUR' as "ns0:currency"
FOR XML PATH ('ns0:present'), TYPE)
FROM [PL2].[dbo].[dk_documents] where id in (1,2,3)
FOR XML PATH('test'))
It work correctly but i want sth like this :
SELECT 1 as test,
(SELECT 1 AS "ns0:kindOfItem",
code AS "ns0:wholeCode",
REPLACE(weight, ',', '.') AS "ns0:weight",
1 AS "ns0:ammountOfNumbers",
(SELECT price AS "ns0:value",
'EUR' as "ns0:currency"
FOR XML PATH ('ns0:sendedItems'), TYPE),
(SELECT
'EUR' as "ns0:currency"
FOR XML PATH ('ns0:present'), TYPE)
FROM [PL].[dbo].[dk_documents] where id in (1,2,3)
UNION ALL
(SELECT 1 AS "ns0:kindOfItem",
code AS "ns0:wholeCode",
REPLACE(weight, ',', '.') AS "ns0:weight",
1 AS "ns0:ammountOfNumbers",
(SELECT price AS "ns0:value",
'EUR' as "ns0:currency"
FOR XML PATH ('ns0:sendedItems'), TYPE),
(SELECT
'EUR' as "ns0:currency"
FOR XML PATH ('ns0:present'), TYPE)
FROM [PL2].[dbo].[dk_documents] where id in (1,2,3)
FOR XML PATH('test'))
FOR XML PATH('anotherPath')
i got this error:
Only one expression can be specified in the select list when the subquery is not introduced with EXISTS.
Output should be like
<test>1</test>
<>All of tese columns from QUERY with union ALL</>
Just an example :
WITH XMLNAMESPACES(DEFAULT 'Dummy')
SELECT 1 as test,
2 as anotherOne,
(SELECT * FROM
(SELECT id, symbol from table1
WHERE id in (1,2,3)
UNION ALL
SELECT id, nrdok from table2
WHERE id in (4,5,6))as yolo
FOR XML PATH(''),TYPE)
FOR XML PATH('test')
It gives me an output :
<test xmlns="Dummy">
<test>1</test>
<anotherOne>2</anotherOne>
<id xmlns="Dummy">1</id>
<symbol xmlns="Dummy">test10</symbol>
<id xmlns="Dummy">2</id>
<symbol xmlns="Dummy">test10</symbol>
<id xmlns="Dummy">3</id>
<symbol xmlns="Dummy">test10</symbol>
<id xmlns="Dummy">4</id>
<symbol xmlns="Dummy">test11</symbol>
<id xmlns="Dummy">5</id>
<symbol xmlns="Dummy">test11</symbol>
<id xmlns="Dummy">6</id>
<symbol xmlns="Dummy">test11</symbol>
</test>
And i want :
<test xmlns="Dummy">
<test>1</test>
<anotherOne>2</anotherOne>
<id>1</id>
<symbol>test10</symbol>
<id>2</id>
<symbol>test10</symbol>
<id>3</id>
<symbol>test10</symbol>
<id>4</id>
<symbol>test11</symbol>
<id>5</id>
<symbol>test11</symbol>
<id>6</id>
<symbol>test11</symbol>
</test>
As pointed out at your previous question the repeated namespaces are not wrong, just annoying, and - if there are many and long URIs, they can blow up your XML to remarkable size...
There I placed a link to a related question already. The trick is to create the XML without the namespace and add the namespace in the finaly SELECT ... FOR XML PATH only:
But I must admit, that after a long while of trial and error I found, that there seem to be a bug if the DEFAULT namespace is involved. Any approach I tried led to either repeated namespace declarations or to repeated empty namespace declarations.
So the only solution I could find is this (I go to wash my fingers now :-) ):
DECLARE #table1 TABLE(id INT,symbol VARCHAR(100));
INSERT INTO #table1 VALUES
(1,'Test 1')
,(2,'Test 2')
,(3,'Test 3')
,(4,'Test 4');
DECLARE #nordic_table2 TABLE(id INT,nrdok VARCHAR(100));
INSERT INTO #nordic_table2 VALUES
(1,'Test 1')
,(2,'Test 2')
,(3,'Test 3')
,(4,'Test 4');
DECLARE #XmlWithoutNamespace XML=
(
SELECT 1 as test
,2 as anotherOne
,(
SELECT *
FROM
(
SELECT id, symbol from #table1
WHERE id in (1,2,3)
UNION ALL
SELECT id, nrdok from #nordic_table2
WHERE id in (4,5,6)
) AS yolo
FOR XML PATH(''),TYPE
)
FOR XML PATH('')
);
SELECT
CAST(
'<test xmlns="Dummy">'
+
CAST(#XmlWithoutNamespace AS NVARCHAR(MAX))
+
'</test>'
AS XML);
UPDATE
I strongly advise you to change your structure to this
SELECT id AS [#id]
,symbol AS [*]
FROM
(
SELECT id, symbol from #table1
WHERE id in (1,2,3)
UNION ALL
SELECT id, nrdok from #nordic_table2
WHERE id in (4,5,6)
) AS yolo
FOR XML PATH('symbol'),TYPE
The result would be this, which is much better to read and to query...
<test xmlns="Dummy">
<test>1</test>
<anotherOne>2</anotherOne>
<symbol id="1">Test 1</symbol>
<symbol id="2">Test 2</symbol>
<symbol id="3">Test 3</symbol>
<symbol id="4">Test 4</symbol>
</test>
UPDATE 2: More Namespaces...
It is actually really hard - almost impossible - to deal with namespaces properly. There is some highly developed logic within FOR XML PATH and in methods like .modify(). I tried several approaches but did not find a convincing one...
The only way I found is very ugly. The trick is, to create 1-level-XML only (no nested elements from sub-selects!) and store them as strings. But before you cut away the root with the namespace declarations. Doing so you'll get invalid XML fragments.
You concatenate them and CAST the whole lot in the last step back to XML.
--Just a container to collect the XML parts
DECLARE #CollectXML TABLE(ID INT IDENTITY,Content XML,CleanedAsString NVARCHAR(MAX));
--The final ",ROOT('xyz')" will force the namespace's declaration into the root node
WITH XMLNAMESPACES('SomeTestUri' AS abc)
INSERT INTO #CollectXML(Content)
SELECT
(
SELECT 1 as [abc:test]
,2 as anotherOne
FOR XML PATH(''),ROOT('xyz'),TYPE
);
--No we use ugly string manipulation to cut out the inner part without the namespace declaration
UPDATE #CollectXML SET CleanedAsString=
(
SELECT Rest
FROM #CollectXML
CROSS APPLY (SELECT CAST(Content AS NVARCHAR(MAX))) AS Casted(XmlAsString)
CROSS APPLY (SELECT REVERSE(SUBSTRING(XmlAsString,CHARINDEX('>',XmlAsString)+1,LEN(XmlAsString)))) AS Cut1(part1Reverse)
CROSS APPLY (SELECT REVERSE(SUBSTRING(part1Reverse,CHARINDEX('<',part1Reverse)+1,LEN(part1Reverse)))) AS Cut2(Rest)
WHERE ID=1
)
WHERE ID=1;
--The same with the second part
WITH XMLNAMESPACES('SomeTestUri' AS abc)
INSERT INTO #CollectXML(Content)
SELECT
(
SELECT id AS [#abc:id]
,symbol AS [*]
FROM
(
SELECT id, symbol from #table1
WHERE id in (1,2,3)
UNION ALL
SELECT id, nrdok from #nordic_table2
WHERE id in (4,5,6)
) AS yolo
FOR XML PATH('abc:symbol'),ROOT('xyz'),TYPE --the not needed root will take the namespace declaration out of the deeper elements
);
--and the ugly string manipulation
UPDATE #CollectXML SET CleanedAsString=
(
SELECT Rest
FROM #CollectXML
CROSS APPLY (SELECT CAST(Content AS NVARCHAR(MAX))) AS Casted(XmlAsString)
CROSS APPLY (SELECT REVERSE(SUBSTRING(XmlAsString,CHARINDEX('>',XmlAsString)+1,LEN(XmlAsString)))) AS Cut1(part1Reverse)
CROSS APPLY (SELECT REVERSE(SUBSTRING(part1Reverse,CHARINDEX('<',part1Reverse)+1,LEN(part1Reverse)))) AS Cut2(Rest)
WHERE ID=2
)
WHERE ID=2;
--The XML is put together and - as the very last step! - casted back to XML
SELECT
CAST(
'<test xmlns="Dummy" xmlns:abc="SomeTestUri">'
+
(SELECT CleanedAsString FROM #CollectXML WHERE ID=1)
+
(SELECT CleanedAsString FROM #CollectXML WHERE ID=2)
+
'</test>'
AS XML);
The result for this
<test xmlns="Dummy" xmlns:abc="SomeTestUri">
<abc:test>1</abc:test>
<anotherOne>2</anotherOne>
<abc:symbol abc:id="1">Test 1</abc:symbol>
<abc:symbol abc:id="2">Test 2</abc:symbol>
<abc:symbol abc:id="3">Test 3</abc:symbol>
<abc:symbol abc:id="4">Test 4</abc:symbol>
</test>
You can test below SQL Select statement.
I hope it helps,
declare #xml xml
declare #xml1 xml
set #xml1 = '<test>1</test>'
declare #xml2 xml
set #xml2 = ( select top 4 name from sys.databases FOR XML PATH('database') )
set #xml = (SELECT #xml1, #xml2 FOR XML PATH(''))
select #xml
Output will be as follows
I have the following xml in my database:
<email>
<account language="en" ... />
</email>
I am using something like this now: but still have to find the attribute value.
SELECT convert(xml,m.Body).query('/Email/Account')
FROM Mail
How can I get the value of the language attribute in my select statement with SQL?
Use XQuery:
declare #xml xml =
'<email>
<account language="en" />
</email>'
select #xml.value('(/email/account/#language)[1]', 'nvarchar(max)')
declare #t table (m xml)
insert #t values
('<email><account language="en" /></email>'),
('<email><account language="fr" /></email>')
select m.value('(/email/account/#language)[1]', 'nvarchar(max)')
from #t
Output:
en
fr
This should work:
DECLARE #xml XML
SET #xml = N'<email><account language="en" /></email>'
SELECT T.C.value('#language', 'nvarchar(100)')
FROM #xml.nodes('email/account') T(C)
It depends a lot on how you're querying the document. You can do this, though:
CREATE TABLE #example (
document NText
);
INSERT INTO #example (document)
SELECT N'<email><account language="en" /></email>';
WITH XmlExample AS (
SELECT CONVERT(XML, document) doc
FROM #example
)
SELECT
C.value('#language', 'VarChar(2)') lang
FROM XmlExample CROSS APPLY
XmlExample.doc.nodes('//account') X(C);
DROP TABLE #example;
EDIT after changes to your question.
if the xml data is stored in sql server as a string column then use cast
select cast(your_field as XML)
.value('(/email/account/#language)[1]', 'varchar(20)')
from your_table
I'm writing one stored procedure, which I have to create a xml column from db.
µ = CHAR(181) this is value separator,
¶ = CHAR(182) this is row separator
This is the statement I wrote. I know its not well formed.
SELECT #xmlString= CAST('<root><Section> ID =' + REPLACE(REPLACE ('20211µ1¶20212µ2', CHAR(182),
'</Section><Section> ID ='),CHAR(181), ' Slno=') + '</Section></root>' AS XML)
This is the pattern which I need to display like this.
<root>
<sections id="20211" slno="1" ></sections>
<sections id="20215" slno="2" ></sections>
</root>
declare #s varchar(50) = '20211µ1¶20212µ2'
declare #xmlString xml
;with C as
(
select T.N.value('value[1]', 'int') as id,
T.N.value('value[2]', 'int') as slno
from (select cast('<item><value>'+replace(replace(#s, 'µ','</value><value>'), '¶','</value></item><item><value>')+'</value></item>' as xml)) as X(XMLCol)
cross apply X.XMLCol.nodes('item') as T(N)
)
select #xmlString =
(
select C.id as [#id] ,
C.slno as [#slno]
from C
for xml path('sections'), root('root'), type
)
select #xmlString
Result:
<root>
<sections id="20211" slno="1" />
<sections id="20212" slno="2" />
</root>