How to Group and Include all Categories - sql

I would like to take my raw data and group it by Name but show all the categories that each name is associated with. Hopefully the picture simplifies what I'm trying to do. Does anyone know how to accomplish this? I have considered making a pivot and then combining columns somehow, but I'm wondering if there is a more straightforward and simpler way. Thanks!

You can use the FOR XML PATH to build your CSV string. The STUFF simply removes the leading comma from the list.
SELECT Name, Categories = STUFF((
SELECT ', ' + Category
FROM dbo.YourTable
WHERE Name = x.Name
FOR XML PATH(''), TYPE).value('.[1]', 'nvarchar(max)'), 1, 2, '')
FROM dbo.YourTable AS x
GROUP BY Name;

You can use stuff and xml path as below:
Select [Name],
Categories = stuff((select Concat(',',category) from #rawdata r where r.[Name] = ou.[Name] for xml path('')), 1,1, '')
from #rawdata ou group by [Name]
If you are using SQL Server 2017 or SQL Azure then you can use String_Agg as below
Select [Name], String_Agg(category,',') from #rawdata
group by [Name]

Related

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.

Get values from XML tags with dynamically specified data fields

I have 2 tables:
Table1 has a list of XML tag names that I want to extract from an XML field. I simulate this by running this query
SELECT
'CLIENT'
UNION SELECT
'FEE'
UNION SELECT
'ADDRESS'
This results in a single column with 3 rows in it, the names of which will be used to extract corresponding data from XML tags.
The second table has a column called ClientData, it is in XML format and it has thousands of rows of data. My task is to extract values from XML tags specified in Table1, in this case I want values from 3 xml tags: Client, FEE and ADDRESS.
So, if the XML is this
<XML>
<CLIENT>some client</CLIENT>
<FEE>some fee</FEE>
<ADDRESS>some address</ADDRESS>
</XML>
After running a query I should get this:
Client, FEE, ADDRESS
some client, some fee, some address
Right now i have a query:
SELECT
coalesce(Cast(ClientData as xml).value('(/XML/CLIENT)[1]', 'varchar(max)'), ''),
coalesce(Cast(ClientData as xml).value('(/XML/FEE)[1]', 'varchar(max)'), ''),
coalesce(Cast(ClientData as xml).value('(/XML/ADDRESS)[1]', 'varchar(max)'), '')
FROM dbo.Table2 WITH(NOLOCK)
This gives me the necessary result, however it is not dynamic. Meaning, if I want to include a 4th xml value, lets say, PHONE, I would need to add coalesce(Cast(ClientData as xml).value('(/XML/PHONE)[1]', 'varchar(max)'), '') to the SELECT
My question is,
How do I make my query dynamic so that instead of hardcoding tag names that I want to extract from XML in Table2 I would use Tabl1 as a source of tag names to extract?
I hope my explanation was good enough :)
Thank you!
You can achieve this using DYNAMIC SQL
The TagsTable should have all the possible Tags
we can then construct SQL using the tag names and execute it
create table TagsTable
( tagName varchar(256)
)
insert into TagsTable values ('CLIENT')
insert into TagsTable values ('FEE')
insert into TagsTable values ('ADDRESS')
declare #query nvarchar(max)
SELECT #query = STUFF((select ',' + 'coalesce(Cast(ClientData as xml).value(''(/XML/'
+ tagName + ')[1]'', ''varchar(max)''), '''') as ' + tagName +' '
FROM TagsTable
FOR XML PATH ('') ), 1,1,'')
SET #query = 'SELECT ' + #query + 'FROM dbo.Table2 WITH(NOLOCK)'
select #query
exec sp_executesql #query

What is the meaning of SELECT ... FOR XML PATH(' '),1,1)?

I am learning sql in one of the question and here I saw usage of this,can some body make me understand what xml path('') mean in sql? and yes,i browsed through web pages I didn't understand it quite well!
I am not getting the Stuff behind,now what does this piece of code do ?(only select part)
declare #t table
(
Id int,
Name varchar(10)
)
insert into #t
select 1,'a' union all
select 1,'b' union all
select 2,'c' union all
select 2,'d'
select ID,
stuff(
(
select ','+ [Name] from #t where Id = t.Id for XML path('')
),1,1,'')
from (select distinct ID from #t )t
There's no real technique to learn here. It's just a cute trick to concatenate multiple rows of data into a single string. It's more a quirky use of a feature than an intended use of the XML formatting feature.
SELECT ',' + ColumnName ... FOR XML PATH('')
generates a set of comma separated values, based on combining multiple rows of data from the ColumnName column. It will produce a value like ,abc,def,ghi,jkl.
STUFF(...,1,1,'')
Is then used to remove the leading comma that the previous trick generated, see STUFF for details about its parameters.
(Strangely, a lot of people tend to refer to this method of generating a comma separated set of values as "the STUFF method" despite the STUFF only being responsible for a final bit of trimming)
SQL you were referencing is used for string concatenation in MSSQL.
It concatenates rows by prepending , using for xml path to result
,a,b,c,d. Then using stuff it replaces first , for , thus removing it.
The ('') in for xml path is used to remove wrapper node, that is being automatically created. Otherwise it would look like <row>,a,b,c,d</row>.
...
stuff(
(
select ',' + CAST(t2.Value as varchar(10)) from #t t2 where t1.id = t2.id
for xml path('')
)
,1,1,'') as Value
...
more on stuff
more on for xml path

SQL Dynamic Pivot - how to order columns

I'm working on a dynamic pivot query on a table that contains:
OID - OrderID
Size - size of the product
BucketNum - the order that the sizes
should go
quantity - how many ordered
The size column contains different sizes depending upon the OID.
So, using the code found here, I put this together:
DECLARE #listCol VARCHAR(2000)
DECLARE #query VARCHAR(4000)
SELECT #listCol = STUFF(( SELECT distinct '], [' + [size]
FROM #t
FOR
XML PATH('')
), 1, 2, '') + ']'
SET #query = 'SELECT * FROM
(SELECT OID, [size], [quantity]
FROM #t
) src
PIVOT (SUM(quantity) FOR Size
IN (' + #listCol + ')) AS pvt'
EXECUTE ( #query )
This works great except that the column headers (the sizes labels) are not in the order based upon the bucketnum column. The are in the order based upon the sizes.
I've tried the optional Order By after the pivot, but that is not working.
How do I control the order in which the columns appear?
Thank you
You need to fix this:
SELECT #listCol = STUFF(( SELECT distinct '], [' + [size]
FROM #t
FOR
XML PATH('')
), 1, 2, '') + ']'
To return the columns in the right order. You might have to do something like this instead of using DISTINCT:
SELECT [size]
FROM #t
GROUP BY [size]
ORDER BY MIN(BucketNum)
SELECT #listCol = STUFF(
(SELECT DISTINCT ',' + QUOTENAME(size) AS [size]
FROM #t
ORDER BY [size]
FOR XML PATH('')
I saw this link just today, which uses a CTE to build the column list (which, presumably, you could order) on the fly without the need for dynamic sql:
http://blog.stevienova.com/2009/07/13/using-ctes-to-create-dynamic-pivot-tables-in-sql-20052008/
I had the same problem and tried the solution suggested above but, probably due to my level of understanding, couldn't get it to work. I found a simple hack was to create a Temp table with the column headers ordered correctly using Order by statements and then pull in that list to the variable that sets the dynamic pivot query column names.
e.g.
SELECT WeekNum INTO #T3
FROM #T2
GROUP BY WeekNum
ORDER BY MIN(WeekNum)
SELECT #ColumnName1 = ISNULL(#ColumnName1 + ',','') + QuoteName(WeekNum)
FROM (SELECT WeekNum From #T3) AS WeekNum
Worked a treat.
Hope that helps someone.