Using distinct with stuff/for xml path('') - sql

I'd like to put together only unique values in the concatenated string. My code is currently:
select rc.Routage
, COUNT(distinct rc.Event)
, STUFF((select ', ' + cast(rcA.Event as varchar)
from Receiving rcA
where rcA.supplier = 'user'
for xml path(''))
, 1, 1, '')
from Receiving rc
where rc.supplier = 'user'
group by rc.Routage
order by COUNT(distinct rc.Event)desc
This gives me the output I'd expect, but I would like to eliminate the duplicate values in the stuff/for xml path field.
I've tried various combinations of distinct and group by in the stuff/xml section, but can't piece it together properly.
To clarify, for COUNT(distinct rc.Event) = 2, I would like to see 2 distinct events from the stuff clause. How can I do this?

Use select distinct in the subquery:
select rc.Routage,
count(distinct rc.Event),
stuff((select distinct ', ' + cast(rcA.Event as varchar(max))
from Receiving rcA
where rcA.supplier = 'user' and
rcA.DATETIME > '20170322' and
rc.Routage = rcA.Routage
for xml path('')
), 1, 2, '')
from Receiving rc
where rc.supplier = 'user' and rc.DATETIME > '20170322'
group by rc.Routage;
Notes:
In SQL Server, never use varchar() (or related types) without a length. The default varies by context and you are (potentially) introducing a bug that is really hard to find.
You want the stuff() to remove two characters, not 1, because you have a comma followed by a space.
This formulation assumes that Event does not have XML special characters. It is easy to tweak if that is an issue.
Also, this type of query is usually faster if you eliminate the duplicates in a subquery:
select rc.Routage, rc.numEvents,
stuff((select distinct ', ' + cast(rcA.Event as varchar(max))
from Receiving rcA
where rcA.supplier = 'user' and
rcA.DATETIME > '20170322' and
rc.Routage = rcA.Routage
for xml path(''), type
).value('.', 'varchar(max)'
), 1, 2, ''
)
from (select rc.Routage, count(distinct rc.Event) as numEvents
from Receiving rc
where rc.supplier = 'user' and rc.DATETIME > '20170322'
group by rc.Routage
) rc;

Do the distinct in a subquery, before the XML processing gets anywhere near it:
select rc.Routage
, COUNT(distinct rc.Event)
, STUFF((select ', ' + cast(rcA.Event as varchar)
from (select distinct Event from Receiving a
where supplier = 'user'
and DATETIME > '20170322'
and rc.Routage=a.Routage
) rcA
for xml path(''))
, 1, 1, '')
from Receiving rc
where rc.supplier = 'user'
and rc.DATETIME > '20170322'
group by rc.Routage
order by COUNT(distinct rc.Event)desc

Related

STUFF only unique values along with sorting another column

I want to combine values with comma-separated. For that used stuff.
But my data has duplicate values, and I just need unique items from that.
Here is a query that I'm using.
SELECT STUFF
(
RTRIM
(
( SELECT N', ' + CAST(column1Name AS varchar(MAX))
FROM dbo.tableName
ORDER BY column2Name
FOR XML PATH (N'')
)
)
, 1, 2, N'')
I tried SELECT DISTINCT within STFF, but that requires a column that used for sorting within SELECT clause, I am using STUFF so I can't use that column in SELECT clause.
SELECT STUFF
(
RTRIM
(
( SELECT DISTINCT N', ' + CAST(column1Name AS varchar(MAX))
FROM dbo.tableName
ORDER BY column2Name
FOR XML PATH (N'')
)
)
, 1, 2, N'')
I also tried to use sub-query to do sorting and use distinct outside, but that also gave a compile error.
SELECT STUFF
(
RTRIM
(
( SELECT DISTINCT N', ' + CAST(column1Name AS varchar(MAX))
FROM
(
SELECT column1Name
FROM dbo.tableName
ORDER BY column2Name
) tableAlias
FOR XML PATH (N'')
)
)
, 1, 2, N'')
I also tried GROUP BY, which also forces me to add column2Name in the SELECT clause.
SELECT STUFF
(
RTRIM
(
( SELECT N', ' + CAST(column1Name AS varchar(MAX))
FROM dbo.tableName
GROUP BY column1Name
ORDER BY column2Name
FOR XML PATH (N'')
)
)
, 1, 2, N'')
Is there any way to stuff unique values along with sorting based on a different column?
You can still use the GROUP BY method. The only thing is when you GROUP BY column1Name, there might be multiple value of column2Name, so you need to use aggregate on it, example MIN(column2Name)
SELECT STUFF
(
RTRIM
(
( SELECT N', ' + CAST(column1Name AS varchar(MAX))
FROM dbo.tableName
GROUP BY column1Name
ORDER BY MIN(column2Name)
FOR XML PATH (N'')
)
)
, 1, 2, N'')

SQL how to put values on one line

It is necessary that in the second column in a single line should be all related accounts.
This shows an error
Conversion failed when converting the varchar value ',' to data type int.
SELECT [UserID],
STUFF((SELECT ', ' + UserID
FROM #RelatedIDs
WHERE (UserID = t.UserID)
FOR XML PATH('')) ,1,1,'') AS RelIDs
FROM #RelatedIDs t
GROUP BY UserID
You could cast the integer value to a varchar:
SELECT [UserID],
STUFF((SELECT ',' + CAST(UserID as VARCHAR(100))
FROM #RelatedIDs
WHERE (UserID = t.UserID)
FOR XML PATH('')) ,1,1,'') AS RelIDs
FROM #RelatedIDs t
GROUP BY UserID
If you are running a recent version of SQL Server (2017 or higher), you can get the same result in a much less obsfucated manner with string_agg():
SELECT t.UserID, STRING_AGG(r.UserID, ',') RelIDs
FROM #RelatedIDs t
INNER JOIN #RelatedIDs r on r.UserID = t.UserID
GROUP BY t.UserID
With the query put this way, it is plain to see that it makes little sense. The self-join operates on the same column as the one that defines the group, so this will just generate a list of identical UserIDs in column RelIDs (one per occurence of the given UserID in the original query).
Use CONCAT() instead of +:
SELECT [UserID],
STUFF((SELECT CONCAT(', ', UserID)
FROM #RelatedIDs ri
WHERE ri.UserID = t.UserID
FOR XML PATH('')
), 1, 2, '') AS RelIDs
FROM #RelatedIDs t
GROUP BY UserID;
Notice that I also changed the stuff arguments from 1, 1 to 1, 2. That is because you have a space after the comma.
Your correlation clause also suggests that the results will not be very interesting, when you get this to work.

Find out Equal row set

I have a number of row base on plan and plan detail, I want to find out the same row set with other detail like plan 1 has 3 rows of data in detail table so need to find out the same rows for another plan. I have some sample data with this post may be more helpful to understand my problem. below is the Sample data Image, iwan to group by the record but not on a single row base on the full row set base on
PlanId, MinCount, MaxCount and CurrencyId
my expected data is below
I had tried to do with Some lengthy process like append all data in a single row and compare with other data, but it seems very lengthy process and takes to much time for 100 records I have an approx 20000 records in an actual database so not a good solution, please suggest me some thought
This would be trivial in MS Sql Server 2017 by using STRING_AGG.
But in 2012 one of the methods is to use the FOR XML trick.
SELECT PlanId, StairCount, MinCount, MaxCount, CurrencyId,
STUFF((
SELECT CONCAT(',', t1.AccountId)
FROM YourTable t1
WHERE t1.PlanId = t.PlanId
AND t1.StairCount = t.StairCount
AND t1.MinCount = t.MinCount
AND t1.MaxCount = t.MaxCount
AND t1.CurrencyId = t.CurrencyId
ORDER BY t1.AccountId
FOR XML PATH('')), 1, 1, '') AS AccountIdList
FROM YourTable t
GROUP BY PlanId, StairCount, MinCount, MaxCount, CurrencyId
Test here
Your desired result is rather hard to produce in SQL Server. The simplest method requires two levels of string concatenation:
with t as (
select a.account_id,
stuff( (select '[' +
convert(varchar(255), staircount) + ',' +
convert(varchar(255), mincount) + ',' +
convert(varchar(255), maxcount) + ',' +
convert(varchar(255), currencyid) +
']'
from t
where t.account_id = a.account_id
order by staircount
for xml path ('')
), 1, 1, '') as details
from (select distinct account_id from t) a
)
select d.details,
stuff( (select cast(varchar(255), account_id) + ','
from t
where t.details = d.details
for xml path ('')
), 1, 1, '') as accounts
from (select distinct details from t) d;
This isn't exactly your output, but it might be good enough for your problem.

Select statement within a select statement

I have the following select statement that returns exactly what I want:
DECLARE #result varchar(max) = ''
SELECT #result += (result.Fullname + '<br/>')
FROM (SELECT DISTINCT Fullname
FROM Providers
WHERE Status='A') as result
select substring(#result, 0, len(#result) - 4)
The only problem is, I want the output from this query to be displayed as a column entry from a larger select statement.
Eg.
SELECT
Column AS [AColumnName],
SELECT #result += (result.Fullname + '<br/>')
FROM (SELECT DISTINCT Fullname
FROM Providers
WHERE Status='A') as result
select substring(#result, 0, len(#result) - 4) as [LenderList]
FROM
Table
But I am currently getting the error: Incorrect syntax near the keyword 'SELECT'.
The error pointing to line 4
Any ideas?
You need aggregate string concatenation in SQL Server. There are already many answers on the subquery, but to save you the trouble:
SELECT Column AS [AColumnName],
STUFF((SELECT DISTINCT '<br/>' + Fullname
FROM Providers
WHERE Status = 'A'
FOR XML PATH (''), TYPE
).value('.', 'varchar(max)'
), 1, 5, ''
) as result
FROM Table;
The use of the type is important because your string has special XML characters.
Can you simply run it in 2 statements?
SELECT #result += (result.Fullname + '<br/>')
FROM (SELECT DISTINCT Fullname
FROM Providers
WHERE Status='A') as result
SELECT
Column AS [AColumnName],
substring(#result, 0, len(#result) - 4)
FROM Table
Which Database? If you can use for xml, then something like...
select substring(a.innards, 0, len(a.innards) - 4) as [LenderList]
from
(
SELECT innards = STUFF(
(SELECT DISTINCT Fullname + '</br>'
FROM Providers
WHERE [Status] = 'A'
FOR XML PATH(''), TYPE).value('.[1]', 'nvarchar(max)')
, 1
, 0
, '')
) a

Transact-SQL Comma Separated Values Not Sorted

I'm trying to control the output of this query to return the values sorted from lower to higher values. As you can see on the last line of the results below I'm getting 16,15,1 as the result, but I need to get 1,15,16 as the result.
Below is the script I'm currently using. I would appreciate any help I can get. Thank you in advance.
SELECT STUFF ((SELECT ',' + CONVERT(varchar(max), UserId)
FROM MessageRecipients mr2
WHERE mr1.ConversationId = mr2.ConversationId FOR xml path('')), 1, 1,'') AS 'Result'
FROM MessageRecipients mr1
GROUP BY ConversationId
This is the result I get with the current script (column name 'result'):
1,19
1,15
16,15,1
Just add an ORDER BY to the inner SELECT:
SELECT STUFF (
(SELECT ',' + CONVERT(varchar(max), UserId)
FROM MessageRecipients mr2
WHERE mr1.ConversationId = mr2.ConversationId
ORDER BY UserId
FOR xml path(''))
, 1, 1,'') AS 'Result'
FROM MessageRecipients mr1
GROUP BY ConversationId