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]
Related
I am using the stuff function to create an comma separated value
stuff works if I run it alone, but when I use in combination with another column to check against, it is failing.
Is it due to datatype issue?
Here is my code
and cast(verifyemails as varchar(max)) in (select STUFF((SELECT Distinct ',' + '''' + cast(emails as varchar(max)) + '''' from roleslist
left join users on users.fkuserid = roleslist.fkroleuserid
where
and fkUserID = 350
group by emails
FOR XML PATH('')), 1,1,'')
The above does not give the results, even the emails do exists in the table, but the below query is working if I run it alone. Does it have to anything with trim or anything?
select STUFF((SELECT Distinct ',' + '''' + cast(emails as varchar(max)) + '''' from roleslist
left join users on users.fkuserid = roleslist.fkroleuserid
where
and fkUserID = 350
group by emails
FOR XML PATH('')), 1,1,'')
If you're doing an IN you shouldn't bother creating a comma-delimited string. If you use a comma-delimited string you need to use LIKE instead of IN. Should be something like this...
and
(select STUFF((SELECT Distinct ',' + '''' + cast(emails as varchar(max)) + ''''
from roleslist
left join users
on users.fkuserid = roleslist.fkroleuserid
where fkUserID = 350
group by emails
FOR XML PATH('')), 1,1,'') LIKE '%' + cast(verifyemails as varchar(max)) + '%'
Or you could simply remove the comma-delimiting stuff and do this...
and cast(verifyemails as varchar(max)) in
(select cast(emails as varchar(max)) from roleslist
left join users
on users.fkuserid = roleslist.fkroleuserid
where fkUserID = 350
group by emails)
How do I remove the trailing comma from the result of this query:
SELECT CONVERT(varchar(10), clientid) + ','
FROM daily
FOR XML PATH('')
The output is 0,0,351,350,
This is not a duplicate. The other questions are not using xml path, but I am.
Usually you use the FOR XML PATH query as a sub query and put the comma at the beginning instead of end so that it is easier to STUFF:
SELECT STUFF((
SELECT ',' + CONVERT(varchar(10), clientid)
FROM daily
FOR XML PATH('')
), 1, 1, '')
Works in SQL Server
SELECT LEFT(CONVERT(varchar(10), clientid) + ',', LEN(CONVERT(varchar(10), clientid) + ',') - 1)
FROM daily
FOR XML PATH('')
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.
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
Hi!
Today I learned for xml path technique to concatenate strings in mssql. Since I've never worked with xml in mssql and google hasn't helped, I need to ask you.
Let's imagine the default case. We need to concatenate some strings from a table:
declare #xmlRepNames xml = (
select
', [' + report_name + ']'
from (
select distinct
report_order,
report_name
from #report
) x
order by
report_order
for xml path(''), type)
select
stuff((select #xmlRepNames.value('.', 'nvarchar(max)')), 1, 1, '')
So I get smth like this:
[str1], [str2], [strn]
Ok. It works fine. But I have two very similar concatenate blocks. The difference is just in the way the result string looks like:
[str1], [str2], [strn]
and
isnull([str1], 0) as [str1], isnull([str2], 0) as [str2], isnull([strn], 0) as [strn]
So I can write 2 very similar code blocks (already done, btw) with different string constructors or to try extend previous code to has xml variable containing 2 kind of constructors and then concatenate by xml node type. Doing 2nd way I met some problems - I wrote this:
declare #xmlRepNames xml = (
select
', [' + report_name + ']' as name,
', isnull([' + report_name + '], 0) as [' + report_name + ']' as res
from (
select distinct
report_order,
report_name
from #report
) x
order by
report_order
for xml path(''), type)
select
stuff((select #xmlRepNames.value('/name', 'nvarchar(max)')), 1, 1, ''),
stuff((select #xmlRepNames.value('/res', 'nvarchar(max)')), 1, 1, '')
but it raise error "XQuery [value()]: 'value()' requires a singleton (or empty sequence), found operand of type 'xdt:untypedAtomic *'".
To replace, e.g., '/name' to '/name[1]' or any other '/name[x]', will return just x-th 'name' record but not all 'name' records concatenated.
[question]: is it possible to solve problem 2nd way like I want and if it's possible then how?
[disclaimer]: the problem isn't really serious for me now (1st way just a little bit uglier but also fine), but it seems very interesting how to come over :)
Thanks!
Your subquery cannot return two values. If you just want to concatenate strings, you do not need the xml data type at all. You can do the stuff() and subquery in a single statement:
declare #Rep1Names nvarchar(max) = (
stuff((select ', [' + report_name + ']' as name
from (select distinct report_order, report_name
from #report
) x
order by report_order
for xml path('')
)
), 1, 1, '');
declare #Rep2Names nvarchar(max) = (
stuff(select ', isnull([' + report_name + '], 0) as [' + report_name + ']' as res
from (select distinct report_order, report_name
from #report
) x
order by report_order
for xml path('')
)
), 1, 1, '');
Ok, so I haven't been completely satisfied the way Gordon Linoff suggested and since I've found this kind of problem actual for me I'm adding here another solution without using for xml path:
declare
#pivot_sequence nvarchar(max),
#columns_sequence nvarchar(max)
select
#pivot_sequence = coalesce(#pivot_sequence + ', [', '[')
+ col_name + ']',
#columns_sequence = coalesce(#columns_sequence + ', ', '')
+ 'isnull([' + col_name + '], 0) as [' + col_name + ']'
from some_table
order by
/* some_columns if needed to order concatenation */
Obviously, it'll work much slower but in cases when you haven't many rows it won't drastically affect on performance. In my case I have dynamic pivot query and these strings are built for it - I won't have many columns in pivot so it's fine for me.