Assuming you are referring to the string aggregation hack done to compensate for SqlServer's lack of GROUP_CONCAT
, you can use this ingenious hack here:
WITH myStuffedCte AS
(SELECT
STUFF((
SELECT ',' + Name
FROM Country
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '') AS SomeCol
)
SELECT ISNULL(reverse(replace(STUFF(reverse(SomeCol),
charindex(',',reverse(SomeCol)),0,'#'),'#,',' dna ')),
SomeCol)
FROM myStuffedCte;
SqlFiddle here
Updated example of Stuff with a GROUPING
criteria
In order to use STUFF
in a GROUP BY
fashion, you can manually filter the inner STUFF subquery based on an outer query's rows with a WHERE
clause.
WITH myStuffedCte AS
(
SELECT p.FirstName, p.LastName,
STUFF((
SELECT ',' + c.Name
FROM Country c
INNER JOIN PersonCountryVisits pcv
ON pcv.CountryId = c.CountryId
WHERE pcv.PersonId = p.PersonID -- Manual Filter step
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '') AS SomeCol
FROM Person p
)
SELECT FirstName, LastName,
ISNULL(reverse(replace(STUFF(reverse(SomeCol),
charindex(',',reverse(SomeCol)),0,'#'),'#,',' dna ')),
SomeCol) AS CountriesVisited
FROM myStuffedCte;
Updated Sql Fiddle example here
SqlClr Alternative to Stuff
, IFF you can use SqlClr on your SqlServer:
If you have access to a Sql-Clr enabled SqlServer, you can ditch both of these hacks and instead, replace STUFF / FOR XML
with a GROUP_CONCAT simulation of SqlUserDefinedAggregate
like this one, and at the same time do the last comma replacement with a SqlFunction
UDF, like this:
[SqlFunction]
public static SqlString ReplaceLast(SqlString source, SqlString find, SqlString replace,
int countFromRight)
{
var splits = source.Value
.Split(new[] {find.Value}, StringSplitOptions.None);
var replacePoint = splits.Length - countFromRight;
return (replacePoint > 0)
? new SqlString(
string.Join(replace.Value,
new[] { string.Join(find.Value, splits.Take(replacePoint)) }
.Union(splits.Skip(replacePoint))))
: source;
}
In which case the sample query becomes the entirely more palatable:
SELECT FirstName, LastName,
dbo.ReplaceLast(dbo.GROUP_CONCAT(c.Name, ','), ',', ' and ', 1)
FROM Person p
INNER JOIN PersonCountryVisits pcv
ON pcv.PersonId = p.PersonID
INNER JOIN Country c
ON pcv.CountryId = c.CountryId
GROUP BY FirstName, LastName;
I've put up a Gist on GitHub which provides all the salient bits to create the GROUP_CONCAT
and ReplaceLast
SqlClr functions. (SqlFiddle not available - No SQLClr ...)