SQL Server throwing "could not serialize" based on join order - sql

I'm seeing something I've never seen before in SQL Server. I'm getting an error message about a character (vertical tab) that is not allowed based (seemingly) only on join order. Note that I cannot find the character anywhere in the actual data.
Has anyone else seen this? Thanks in advance! Errors and code below:
This query fails:
SELECT
'prefix-' + COALESCE(CAST(a.id AS VARCHAR(MAX)), CAST(cta.id AS VARCHAR(MAX))) AS [object_id],
objectValues.Value AS related_values
FROM
object_table a
CROSS APPLY
dbo.tvfGetValuesCsvForObject(a.id) AS objectValues
FULL OUTER JOIN
v_object_table_change_tracking_aggregation cta ON a.id = cta.id
WHERE
'object-' + COALESCE(CAST(a.id AS VARCHAR(MAX)), CAST(cta.id AS VARCHAR(MAX))) = 'object-123456'
The error message:
Msg 6841, Level 16, State 1, Line 111
FOR XML could not serialize the data for node 'NoName' because it contains a character (0x000B) which is not allowed in XML. To retrieve this data using FOR XML, convert it to binary, varbinary or image data type and use the BINARY BASE64 directive.
This code succeeds:
SELECT
'prefix-' + COALESCE(CAST(a.id AS VARCHAR(MAX)), CAST(cta.id AS VARCHAR(MAX))) AS [object_id],
objectValues.Value AS related_values
FROM
object_table a
FULL OUTER JOIN
v_object_table_change_tracking_aggregation cta ON a.id = cta.id
CROSS APPLY
dbo.tvfGetValuesCsvForObject(a.id) AS objectValues
WHERE
'object-' + COALESCE(CAST(a.id AS VARCHAR(MAX)), CAST(cta.id AS VARCHAR(MAX))) = 'object-123456'
Note that the only difference between the two is the join/cross apply order, and that the cardinality of the values contained in a.id is the same (because the full outer join will not drop any records).
I've also included the definition of the related table-valued-function:
CREATE FUNCTION dbo.tvfGetEaValuesCsvForProject (#object_id INT)
RETURNS TABLE
AS
RETURN
WITH CSVList AS
(
SELECT
(
SELECT
'"' + REPLACE(kvp.EAValueChar, '"', '') + '", ' ,
'"' + REPLACE(kvp.EAValueCharMax, '"', '') + '", '
FROM dbo.object_related_fields_kvp kvp
JOIN dbo.field_definitions def
ON def.id = kvp.id
WHERE kvp.object_id = #object_id
--text, longtext
AND def.value_type IN (0,8)
ORDER BY kvp.id
FOR XML PATH(''), TYPE
).value('.', 'varchar(max)') AS Value
)
SELECT
'[' +
--remove trailing comma
LEFT(Value,LEN(Value) - 1)+
']' AS Value
FROM CSVList
GO
I don't believe the view is related because it isn't handling the concatenation of the columns, so I haven't included the definition of the view. Also, I stripped out column/table names and replaced them with generic names. I think I kept references intact, but if you see mistakes there, that's why.

Related

Remove leading comma from FOR XML PATH function

I created a variable that accepts multiple string values using the FOR XML PATH:
set #Control_Number =
(SELECT DISTINCT
SUBSTRING(
(
SELECT ''', '''+ co.control_number AS [text()]
FROM #AGBCaseCompanyMap2 co
WHERE co.company_id = co2.company_id
ORDER BY co.company_id
FOR XML PATH ('')
), 2, 1000) [control_number]
The result I'm getting is including a leading comma: (', '0045', '4343').
I've used the STUFF function in the past to remove this but I can't figure out how to use it here. I keep getting errors like "STUFF function requires 4 values." Does anyone know how to remove that leading quote and comma?
You just need to add the quotes separately with STUFF in the query:
set #Control_Number =
(SELECT DISTINCT
STUFF(
(
SELECT ', '+ '''' + co.control_number + '''' AS [text()]
FROM #AGBCaseCompanyMap2 co
WHERE co.company_id = co2.company_id
ORDER BY co.company_id
FOR XML PATH ('')
), 1, 2, '') [control_number]

Return NON-Matched values from the result of XML path

I have a situation where I need to compare two tables where t1.ColumnA = t2.ColumnA and t1.ColumnB<>t2.ColumnB. The caveat to this problem is that t2.ColumnB is using a "FOR XML PATH" to concatenate like values from another table (linked server using OPENQUERY). This all has to be done within a View.
To concatenate the rows, I am using the following code:
SELECT DISTINCT
CAST(A.CHECK_NUMBER AS nvarchar) [CHECK_NUMBER]
,(
SELECT B.INVOICE_NUMBER + '|'
FROM OPENQUERY([SERVER], 'SELECT * FROM CHECK_LISTING ') B
WHERE B.CHECK_NUMBER = A.CHECK_NUMBER
AND (NULLIF (B.INVOICE_NUMBER, '') IS NOT NULL)
FOR XML PATH('')
) [INVOICE_NUMBER]
, (
SELECT LTRIM(RTRIM(B.PURCHASE_ORDER_ID)) + '|'
FROM OPENQUERY([SERVER], 'SELECT * FROM CHECK_LISTING ') B
WHERE B.CHECK_NUMBER = A.CHECK_NUMBER
AND (NULLIF (B.PURCHASE_ORDER_ID, '') IS NOT NULL)
FOR XML PATH('')
) [PURCHASE_ORDER_ID]
FROM OPENQUERY([SERVER], 'SELECT * FROM CHECK_LISTING ') AS A
This works perfectly and concatenates just like it needs to. My problem is that my next view I created was to run that view against a local table to see the difference in the INVOICE_NUMBER.
SELECT
A.EntryID,
A.Check#,
A.CheckDate,
A.CheckAmount,
A.VendorID,
A.VendorName,
B.INVOICE_NUMBER,
A.Invoice#
FROM dbo.APChecks AS A
LEFT JOIN
dbo.CHECKS_Step2 AS B
ON A.Check#=B.CHECK_NUMBER
WHERE (A.Invoice# != B.INVOICE_NUMBER)
When I try to run this, the query takes at least 25+ minutes. I had to stop the query manually. Some concatenated values are longer than 1000 chars. I was told that it was not possible to INDEX on a dynamic entry like an XML PATH.
Any suggestions? Thank you in advance.

How to correctly combine multiple rowss into comma seperated string in SQL Server

I am trying to use the STUFF function to combine multiple rows of data into a comma separated string.
Here is what I have done
SELECT
s.Id
,s.Name
,STUFF(
(
SELECT
c.Name
FROM [Groups] AS c
INNER JOIN [GroupToUser] AS stc ON stc.CategoryId = c.Id
WHERE stc.StageId = s.Id
FOR XML PATH('')
), 1, 1, '') AS GroupsTheUserBelongsTo
FROM [Users] AS s
This is working somehow like expected. However, it is returning XML string missing the first character.
How can I make it return a comma separated string instead of XML?
You should just need to make sure you put the ',' comma in the query. so ',' + c.Name.
SELECT
s.Id
,s.Name
,STUFF(
(
SELECT
',' + c.Name
FROM [Groups] AS c
INNER JOIN [GroupToUser] AS stc ON stc.CategoryId = c.Id
WHERE stc.StageId = s.Id
FOR XML PATH('')
), 1, 1, '') AS GroupsTheUserBelongsTo
FROM [Users] AS s
So to explain what is happening for you. you have a standard query that you then tell sql-server to turn into XML but without a root element and because there is no column name it will also be without tags. When you add the comma or other delimiter it will put a comma between each row. STUFF(xmlstring,1,1,'') says take the in the first position of the string take out the character that is there and put in 1 occurrence of '' which basically is the same as removing the leading comma.
here is a little example to break it down into pieces to better understand what is going on.
DECLARE #Table AS TABLE (Col VARCHAR(10))
INSERT INTO #Table VALUES ('A'),('B'),('C')
DECLARE #Values AS VARCHAR(MAX)
SELECT #Values = (
SELECT
',' + Col
FROM
#Table
FOR XML PATH (''))
SELECT #Values
SELECT STUFF(#Values,1,1,'')
#DanGuzman makes a good point that if you are worried about special characters in your Names column becoming encoded/replace and xml encoding remaining in your string you can try:
SELECT
s.Id
,s.Name
,STUFF( (
SELECT
',' + c.Name
FROM [Groups] AS c
INNER JOIN [GroupToUser] AS stc ON stc.CategoryId = c.Id
WHERE stc.StageId = s.Id
FOR XML PATH (''), TYPE).value('.','nvarchar(MAX)')
,1,1,'') AS GroupsTheUserBelongsTo
FROM
[Users] AS s

SQL for concatenating strings/rows into one string/row? (How to use FOR XML PATH with INSERT?)

I am concatenating several rows/strings in an table (on Microsoft SQL Server 2010) into a string by using a method as suggested here:
SELECT ',' + col FROM t1 FOR XML PATH('')
However, if I try to insert the resulting string as (single) row into another table like so:
INSERT INTO t2
SELECT ', ' + col FROM t1 FOR XML PATH('')
I receive this error message:
The FOR XML clause is not allowed in a INSERT statement.
t2 currently has a single column of type NVARCHAR(80). How can I overcome this problem, i.e. how can I collapse a table t1 with many rows into a table t2 with row that concatenates all the strings from t1 (with commas)?
Rather than xml path why not do it like this?
DECLARE #Cols VARCHAR(8000)
SELECT #Cols = COALESCE(#Cols + ', ', '') +
ISNULL(col, 'N/A')
FROM t1
Insert into t2 values(#Cols);
You need to cast it back to an nvarchar() before inserting. I use this method, deletes the first separator as well and as I'm doing the , type part, it handles entities correctly.
insert into t2
select stuff((
select ', ' + col from t1
for xml path(''), type
).value('.', 'nvarchar(80)'), 1, 2, '')
So you concatenate all col with prepending comma+space as an xml-object. Then you take the .value() of child with xquery-path . which means "take the child we are at, don't traverse anywhere". You cast it as an nvarchar(80) and replace a substring starting at position 1 and length 2 with an empty string ''. So the 2 should be replaced with however long your separator is.

TSQL Reverse FOR XML Encoding

I am using FOR XML in a query to join multiple rows together, but the text contains quotes, "<", ">", etc. I need the actual character instead of the encoded value like """ etc. Any suggestions?
Basically what you're asking for is invalid XML and luckly SQL Server will not produce it. You can take the generated XML and extract the content, and this operation will revert the escaped characters to their text representation. This revert normally occurs in the presnetaitonlayer, but it can occur in SQL Server itslef by instance using XML methods to extract the content of the produced FOR XML output. For example:
declare #text varchar(max) = 'this text has < and >';
declare #xml xml;
set #xml = (select #text as [node] for xml path('nodes'), type);
select #xml;
select x.value(N'.', N'varchar(max)') as [text]
from #xml.nodes('//nodes/node') t(x);
I have a similar requirement to extract column names for use in PIVOT query.
The solution I used was as follows:
SELECT #columns = STUFF((SELECT '],[' + Value
FROM Table
ORDER BY Value
FOR XML PATH('')), 1, 2, '') + ']'
This produces a single string:
[Value 1],[Value 2],[Value 3]
I hope this points you in the right direction.
--something like this?
SELECT * INTO #Names FROM (
SELECT Name='<>&' UNION ALL
SELECT Name='ab<>'
) Names;
-- 1)
SELECT STUFF(
(SELECT ', ' + Name FROM #Names FOR XML PATH(''))
,1,2,'');
-- 2)
SELECT STUFF(
(SELECT ', ' + Name FROM #Names FOR XML PATH(''),TYPE).value('text()[1]','nvarchar(max)')
,1,2,'');
-- 2) is slower but will not return encoded value.
Hope it help.