SQL SERVER FOR XML EXPLICIT - sql-server

I want to assign the result of a SELECT FOR XML EXPLICIT statement to a XML Variable such as
CREATE PROCEDURE BILLING_RESPONSE
AS DECLARE #Data AS XML
SET #Data = (SELECT
1 AS Tag,
NULL AS Parent,
NULL AS 'CallTransactions!1!',
NULL AS 'TCALTRS!2!TRS_DAT_TE!cdata',
NULL AS 'TCALTRS!2!TRS_CRT_DT!Element'
UNION ALL
SELECT
2 AS Tag,
1 AS Parent,
NULL,
TRS_DAT_TE,
TRS_CRT_DT
FROM TCALTRS
WHERE TRS_CRT_DT between CONVERT(date,GETDATE()-1) and CONVERT(date,getdate()) and
TRS_DAT_TE like '%(Submit Response)%'
FOR XML EXPLICIT
)
SELECT #DATA
GO
When i execute this query am getting the following error
Msg 1086, Level 15, State 1, Procedure BILLING_RESPONSE, Line 22
The FOR XML clause is invalid in views, inline functions, derived tables, and subqueries when they contain a set operator. To work around, wrap the SELECT containing a set operator using derived table syntax and apply FOR XML on top of it.

If this is it, you don't need the #Data variable. Let your sp return the result of query directly and you're done.
CREATE PROCEDURE BILLING_RESPONSE AS
SELECT
1 AS Tag,
NULL AS Parent,
NULL AS 'CallTransactions!1!',
NULL AS 'TCALTRS!2!TRS_DAT_TE!cdata',
NULL AS 'TCALTRS!2!TRS_CRT_DT!Element'
UNION ALL
SELECT
2 AS Tag,
1 AS Parent,
NULL,
TRS_DAT_TE,
TRS_CRT_DT
FROM TCALTRS
WHERE TRS_CRT_DT between CONVERT(date,GETDATE()-1) and CONVERT(date,getdate()) and
TRS_DAT_TE like '%(Submit Response)%'
FOR XML EXPLICIT

The error is not particularly clear, but what it is saying is that you can't use the FOR XML clause in the inline subquery because it contains a UNION (a type of set operator)
The suggested workaround is to wrap the subquery in something else and call it separately, for example:
CREATE PROCEDURE BILLING_RESPONSE
AS DECLARE #Data AS XML
;WITH DATA AS(
SELECT
1 AS Tag,
NULL AS Parent,
NULL AS 'CallTransactions!1!',
NULL AS 'TCALTRS!2!TRS_DAT_TE!cdata',
NULL AS 'TCALTRS!2!TRS_CRT_DT!Element'
UNION ALL
SELECT
2 AS Tag,
1 AS Parent,
NULL,
TRS_DAT_TE,
TRS_CRT_DT
FROM TCALTRS
WHERE TRS_CRT_DT between CONVERT(date,GETDATE()-1) and CONVERT(date,getdate()) and
TRS_DAT_TE like '%(Submit Response)%'
FOR XML EXPLICIT
)
SELECT #Data = (SELECT * FROM DATA FOR XML EXPLICIT)
SELECT #DATA
GO

Related

SQL: Cast VARCHAR to XML removes CDATA section [duplicate]

When I generate Xml in Sql Server 2008 R2 using For Explicit (because my consumer wants one of the elements wrapped in CDATA) and store the results in an Xml variable, the data I want wrapped in CDATA tags no longer appears wrapped in CDATA tags. If I don't push the For Xml Explicit results into an Xml variable then the CDATA tags are retained. I am using the #Xml variable as an SqlParameter from .Net.
In this example, the first select (Select #Xml) does not have Line2 wrapped in CDATA tags. But the second select (the same query used to populate the #Xml variable) does have the CDATA tags wrapping the Line2 column.
Declare #Xml Xml
Begin Try
Drop Table #MyTempTable
End Try
Begin Catch
End Catch
Select
'Record' As Record
, 'Line1' As Line1
, 'Line2' As Line2
Into
#MyTempTable
Select #Xml =
(
Select
x.Tag
, x.Parent
, x.[Root!1]
, x.[Record!2!Line1!Element]
, x.[Record!2!Line2!cdata]
From
(
Select
1 As Tag, Null As Parent
, Null As [Root!1]
, Null As [Record!2!Line1!Element]
, Null As [Record!2!Line2!cdata]
From
#MyTempTable
Union
Select
2 As Tag, 1 As Parent
, Null As [Root!1]
, Line1 As [Record!2!Line1!Element]
, Line2 As [Record!2!Line2!cdata]
From
#MyTempTable
) x
For
Xml Explicit
)
Select #Xml
Select
x.Tag
, x.Parent
, x.[Root!1]
, x.[Record!2!Line1!Element]
, x.[Record!2!Line2!cdata]
From
(
Select
1 As Tag, Null As Parent
, Null As [Root!1]
, Null As [Record!2!Line1!Element]
, Null As [Record!2!Line2!cdata]
From
#MyTempTable
Union
Select
2 As Tag, 1 As Parent
, Null As [Root!1]
, Line1 As [Record!2!Line1!Element]
, Line2 As [Record!2!Line2!cdata]
From
#MyTempTable
) x
For
Xml Explicit
Begin Try
Drop Table #MyTempTable
End Try
Begin Catch
End Catch
You can't. The XML data type does not preserve CDATA sections.
Have a look here for a discussion about the subject.
http://social.msdn.microsoft.com/forums/en-US/sqlxml/thread/e22efff3-192e-468e-b173-ced52ada857f/

Invalid STRING_SPLIT args in SQL Server

I am trying to parse a cell that is data type nvarchar(max) like it is csv in SQL Server 2017. I was hoping to use the STRING_SPLIT return a row of data for each value in the array-like string. However, when I run the following code I get an error.
select this_column
into #t
from that_table
where coordinate_x = 1
and coordinate_y = 7
select * from STRING_SPLIT(#t.this_column, ',')
Msg 4104, Level 16, State 1, Line 16
The multi-part identifier "#t.this_column" could not be bound
Msg 8116, Level 16, State 1, Line 16
Argument data type void type is invalid for argument 1 of string_split function
How can I parse this data?
You can't pass a table to a inline table function, you need to include it in your FROM:
SELECT {YourColumns}
FROM #t T
CROSS APPLY STRING_SPLIT(T.this_column, ',') SS;
You'll want to use a cross apply with your temp table
Here's a working example so you can see how that works:
DECLARE #TestData TABLE
(
[TestData] NVARCHAR(MAX)
);
INSERT INTO #TestData (
[TestData]
)
VALUES ( N'1,2,3,4,5,6,7,8,9');
SELECT [b].[value] FROM #TestData [a]
CROSS APPLY[STRING_SPLIT([a].[TestData], ',') [b];
So for your situation, just a small tweak to what you already have, something like:
SELECT [b].[value] FROM #t a
CROSS APPLY STRING_SPLIT(a.this_column,',') b

For xml path returns null instead of nothing

I thought that following query suppose to return nothing, but, instead, it returns one record with a column containing null:
select *
from ( select 1 as "data"
where 0 = 1
for xml path('row') ) as fxpr(xmlcol)
If you run just the subquery - nothing is returned, but when this subquery has an outer query, performing a select on it, null is returned.
Why is that happening?
SQL Server will try to predict the type. Look at this
SELECT tbl.[IsThereAType?] + '_test'
,tbl.ThisIsINT + 100
FROM
(
SELECT NULL AS [IsThereAType?]
,3 AS ThisIsINT
UNION ALL
SELECT 'abc'
,NULL
--UNION ALL
--SELECT 1
-- ,NULL
) AS tbl;
The first column will be predicted as string type, while the second is taken as INT. That's why the + operator on top works. Try to add a number to the first or a string to the second. This will fail.
Try to uncomment the last block and it will fail too.
The prediction is done at a very early stage. Look at this, where I did include the third UNION ALL (invalid query, breaking the type):
EXEC sp_describe_first_result_set
N'SELECT *
FROM
(
SELECT NULL AS [IsThereAType?]
,3 AS ThisIsINT
UNION ALL
SELECT ''abc''
,NULL
UNION ALL
SELECT 1
,NULL
) AS tbl';
The result returns "IsThereAType?" as INT! (I'm pretty sure this is rather random and might be different on your system.)
Btw: Without this last block the type is VARCHAR(3)...
Now to your question
A naked XML is taken as NTEXT (altough this is deprecated!) and needs ,TYPE to be predicted as XML:
EXEC sp_describe_first_result_set N'SELECT ''blah'' FOR XML PATH(''blub'')';
EXEC sp_describe_first_result_set N'SELECT ''blah'' FOR XML PATH(''blub''),TYPE';
The same wrapped within a sub-select returns as NVARCHAR(MAX) resp. XML
EXEC sp_describe_first_result_set N'SELECT * FROM(SELECT ''blah'' FOR XML PATH(''blub'')) AS x(y)';
EXEC sp_describe_first_result_set N'SELECT * FROM(SELECT ''blah'' FOR XML PATH(''blub''),TYPE) AS x(y)';
Well, this is a bit weird actually...
An XML is a scalar value taken as NTEXT, NVARCHAR(MAX) or XML (depending on the way you are calling it). But it is not allowed to place a naked scalar in a sub-select:
SELECT * FROM('blah') AS x(y) --fails
While this is okay
SELECT * FROM(SELECT 'blah') AS x(y)
Conclusio:
The query parser seems to be slightly inconsistent in your special case:
Although a sub-select cannot consist of one scalar value only, the SELECT ... FOR XML (which returs a scalar actually) is not rejected. The engine seems to interpret this as a SELECT returning a scalar value. And this is perfectly okay.
This is usefull with nested sub-selects as a column (correlated sub-queries) to nest XML:
SELECT TOP 5 t.TABLE_NAME
,(
SELECT COLUMN_NAME,DATA_TYPE
FROM INFORMATION_SCHEMA.COLUMNS AS c
WHERE c.TABLE_SCHEMA=t.TABLE_SCHEMA
AND c.TABLE_NAME=t.TABLE_NAME
FOR XML PATH('Column'),ROOT('Columns'),TYPE
) AS AllTablesColumns
FROM INFORMATION_SCHEMA.TABLES AS t;
Without the FOR XML clause this would fail (...more than one value... / ...Only one column...)
Pass a generic SELECT as a parameter?
Some would say this is not possible, but you can try this:
CREATE FUNCTION dbo.TestType(#x XML)
RETURNS TABLE
AS
RETURN
SELECT #x AS BringMeBack;
GO
--The SELECT must be wrapped in paranthesis!
SELECT *
FROM dbo.TestType((SELECT TOP 5 * FROM sys.objects FOR XML PATH('x'),ROOT('y')));
GO
DROP FUNCTION dbo.TestType;
Empty XML Data is treated as NULL in SQL Server.
select *
from ( select 1 as "data"
where 0 = 1
for xml path('row') ) as fxpr(xmlcol)
The Subquery will be executed first and the result of the subquery i.e (Empty Rowset) will be converted to XML therefore, getting NULL Value.

Calling User-Defined Function in select statement returning xml data

I created a user-defined function in SQL Server 2012 that returns XML. I would like to call the function in a SELECT statement. Is this possible?
When I try doing it, I get the error:
The FOR XML clause is not allowed in a ASSIGNMENT statement.
I want the SELECT statement to return a set of these named methods that have dependencies of other named methods within their logic.
In the main CTE, I get the latest versions of methods that have dependencies. The UDF goes thru the logic of each method and returns any methods called within it. So, I want to call the UDF in the SELECT statement and return XML of the dependent method names.
The function works and returns XML data. This is the function:
ALTER FUNCTION [dbo].[GetCalledMLMs]
(
-- Add the parameters for the function here
#MLM_Txt nvarchar(MAX)
)
RETURNS XML
AS
BEGIN
-- Declare the return variable here
DECLARE #CalledMLMs XML
Declare #MLMTbl table (pos int, endpos int, CalledMLM nvarchar(200))
--Logic to get the data...
Select #CalledMLMs = CalledMLM from #MLMTbl FOR XML PATH
-- Return the result of the function
RETURN #CalledMLMs
END
This is the CTE that calls the UDF.
;with cte as
(
select distinct Name, max(ID) as LatestVersion
from MLM_T
where Logic like '%:= MLM %' and Logic not like '%standard_libs := mlm%'
group by Name
)
select MLM2.Name, LatestVersion,
dbo.GetCalledMLMs(MLM2.Logic) as CalledMLMs
from cte join MLM_T MLM2 on cte.Name = MLM2.Name
and cte.LatestVersion = MLM2.ID
and MLM2.Active = 1 and MLM2.Status in (3, 4)
When running this query I get the error that XML is not allowed to be used in assignment statement.
Is there any way to call a function in the SELECT statment that returns an XML data type?
If you want to set a variable to a value you have to use SET and a scalar value on the right side.
The syntax SELECT #SomeVariable=SomeColumn FROM SomeTable is not possible with FOR XML (and rather dangerous anyway...), because the XML is not a column of the SELECT but something after the process of selecting.
Your problem is situated here:
Select #CalledMLMs = CalledMLM from #MLMTbl FOR XML PATH
Try to change this to
SET #CalledMLMs = (SELECT CalledMLM FROM #MLMTbl FRO XML PATH);
I solved the problem by changing the function to return a table, not XML.
So it looks like this:
FUNCTION [dbo].[GetCalledMLMsTbl]
(
-- Add the parameters for the function here
#MLM_Txt nvarchar(MAX)
)
--RETURNS XML
RETURNS #MLMTbl TABLE
(
pos int,
endpos int,
CalledMLM nvarchar(200)
)
AS
BEGIN
--logic here
insert into #MLMTbl (pos, endpos, CalledMLM) Values (#startpos, #endpos, #MLM_name)
RETURN
END
Then I called the function in the 'from' clause in the select
;with cte as
(
select distinct Name, max(ID) as LatestVersion
from CV3MLM
where Logic like '%:= MLM %' and Logic not like '%standard_libs := mlm%'
--and Name not like '%V61_CCC'
group by Name
)
select MLM2.Name, LatestVersion, C.CalledMLM
from cte join MLM_tbl MLM2 on cte.Name = MLM2.Name and cte.LatestVersion = MLM2.ID
and MLM2.Active = 1 and MLM2.Status in (3, 4)
cross apply dbo.GetCalledMLMsTbl(MLM2.Logic) C
order by MLM2.Name, LatestVersion

Equal command in T-SQL written twice - works once, doesn't the second time

This is my T-SQL code:
;with tmpFolderPermissions(fp_folder, DataItem, fp_modify) as
(
select
fp_folder, LEFT(fp_modify, CHARINDEX(',',fp_modify+',')-1),
STUFF(fp_modify, 1, CHARINDEX(',',fp_modify+','), '')
from dbo.tblFolderPermissions
union all
select
fp_folder, CAST (LEFT(fp_modify, CHARINDEX(',', fp_modify+',') -1) AS VARCHAR(200)),
STUFF(fp_modify, 1, CHARINDEX(',',fp_modify+','), '')
from tmpFolderPermissions
where fp_modify > ''
)
select fp_folder, DataItem
from tmpFolderPermissions
And it works fine, as a result I get a table with about 200 rows. However, if above that whole code I add the same command as above:
select fp_folder, DataItem
from tmpFolderPermissions
so basically, if I write this twice in a row, the first command shows me 200 results, which is good, but the second gives me this error:
Invalid object name 'tmpFolderPermissions'.
Why does it give me an error, when it executed a milisecond ago?
tmpFolderPermissions is a CTE. And CTE's cannot be reused in multiple querys as it is part of the query itself. So the second time you call it, it doesn't exists.
Use a temp table or view instead.
Quote from link:
A common table expression (CTE) can be thought of as a temporary result set that is defined within the execution scope of a single SELECT, INSERT, UPDATE, DELETE, or CREATE VIEW statement. A CTE is similar to a derived table in that it is not stored as an object and lasts only for the duration of the query.
This is because tmpFolderPermissions is a CTE, CTE is sth like tmp table and it's not accessible outside query it comes from. If you want to reuse that data you should save it to tmp table
;with tmpFolderPermissions(fp_folder, DataItem, fp_modify) as
(
select
fp_folder, LEFT(fp_modify, CHARINDEX(',',fp_modify+',')-1),
STUFF(fp_modify, 1, CHARINDEX(',',fp_modify+','), '')
from dbo.tblFolderPermissions
union all
select
fp_folder, CAST (LEFT(fp_modify, CHARINDEX(',', fp_modify+',') -1) AS VARCHAR(200)),
STUFF(fp_modify, 1, CHARINDEX(',',fp_modify+','), '')
from tmpFolderPermissions
where fp_modify > ''
)
select fp_folder, DataItem into #tmpFolderPermissions
from tmpFolderPermissions
and then query on #tmpFolderPermissions

Resources