SQL group records with same value, and place that into a table - sql

I want to group same record with SQL, the following is my result
Name Code Qty
data1 AG 12
data1 AS 15
data2 MS 10
data2 IS 11
I want it to be like this instead.
Name Code Qty Code Qty
data1 AG 12 AS 15
data2 MS 10 IS 11
Can this be done in SQL only?

Can this be done in SQL only?
With a variable number of columns you have to build a the query dynamically.
I would hardly call this "SQL only" but it can be done in T-SQL and here is one way.
-- Sample table
declare #T table
(
Name varchar(5),
Code varchar(2),
Qty int
)
-- Sample data
insert into #T values
('data1', 'AG', 12),
('data1', 'AS', 15),
('data1', 'AQ', 17),
('data2', 'MS', 10),
('data2', 'IS', 11)
declare #XML xml
declare #SQL nvarchar(max)
declare #Max int
-- Max number of codes per name
select #Max = max(C)
from (select count(*) as C
from #T
group by Name) as T
-- Convert table to XML
set #XML = (select Name,
(select Code,
Qty
from #T as T2
where T1.Name = T2.Name
for xml path('c'), type)
from #T as T1
group by Name
for xml path('r'))
-- Build a dynamic query
;with Numbers(Number) as
(
select 1
union all
select Number + 1
from Numbers
where Number < #Max
)
select #SQL = 'select T.N.value(''Name[1]'', ''varchar(5)'') as Name ' +
(select ',T.N.value(''c['+cast(Number as nvarchar(10))+']/Code[1]'', ''char(2)'') as Code
,T.N.value(''c['+cast(Number as nvarchar(10))+']/Qty[1]'', ''int'') as Qty'
from Numbers
for xml path(''), type).value('.', 'nvarchar(max)') +
' from #xml.nodes(''/r'') as T(N)'
-- Execute query
exec sp_executesql #SQL, N'#xml xml', #XML
Result:
Name Code Qty Code Qty Code Qty
----- ---- ----------- ---- ----------- ---- -----------
data1 AG 12 AS 15 AQ 17
data2 MS 10 IS 11 NULL NULL
Try here: https://data.stackexchange.com/stackoverflow/q/122860/

Assuming you are using SQLServer, you could use rank to assign a sequential number to each code with the name group, like so:
select Name, Code, Qty, Rank() OVER (PARTITION BY Name ORDER BY Code) AS CodeRank
from MyTable
Then you can use the pivot functionality within either SQLServer or SSRS to format this as required.

Not exactly. You can hack around with the idea of concatenating lots of Code,Qty pairs into a single value for each record, though. I'm not sure what you're planning to do with multiple columns with the same name, even if such a thing were possible.

Related

Convert Data in a Column to a row in SQL Server

Fairly new to SQL, so I do apologise!
Currently I have the following SQL Query:
select [data]
from Database1.dbo.tbl_Data d
join Database1.tbl_outbound o on d.session_id = o.session_id
where o.campaign_id = 1047
and d.session_id = 12
This returns ONE column which looks like this (and it can return different number of rows, depending on campaign_id and session_id!):
[data]
[1] Entry 1
[2] Entry 2
[3] Entry 3
[4] Entry 4
[5] Entry 5
.....
[98] Entry 98
[99] Entry 99
I would like to convert the data so they are displayed in 1 row and not 1 column, for example:
[data1] [data2] [data3] [data4] [data5] .... [data98] [data99]
[1] Entry 1 Entry 2 Entry 3 Entry 4 Entry 5 .... Entry 98 Entry 99
I hope I have explained that well enough! Thanks! :)
I have seen some information floating around about Pivot and Unpivot, but couldn't get it to play ball!
Try This Dynamic sql which helps your requirement
IF OBJECT_ID('tempdb..#Temp')IS NOT NULL
DROP TABLE #Temp
CREATE TABLE #Temp (data VARCHAR(100))
GO
IF OBJECT_ID('tempdb..#FormatedTable')IS NOT NULL
DROP TABLE #FormatedTable
Go
INSERT INTO #Temp(data)
SELECT 'Entry1' UNION ALL
SELECT 'Entry2' UNION ALL
SELECT 'Entry3' UNION ALL
SELECT 'Entry4' UNION ALL
SELECT 'Entry5'
SELECT ROW_NUMBER()OVER(ORDER BY Data) AS SeqId,
Data,
'Data'+CAST(ROW_NUMBER()OVER(ORDER BY Data) AS VARCHAR(100)) AS ReqColumn
INTO #FormatedTable
FROM #Temp
DECLARE #Sql nvarchar(max),
#DynamicColumn nvarchar(max),
#MaxDynamicColumn nvarchar(max)
SELECT #DynamicColumn = STUFF((SELECT ', '+QUOTENAME(ReqColumn)
FROM #FormatedTable FOR XML PATH ('')),1,1,'')
SELECT #MaxDynamicColumn = STUFF((SELECT ', '+'MAX('+(ReqColumn)+') AS '+QUOTENAME(CAST(ReqColumn AS VARCHAR(100)))
FROM #FormatedTable FOR XML PATH ('')),1,1,'')
SET #Sql=' SELECT ROW_NUMBER()OVER(ORDER BY (SELECT 1)) AS SeqId, '+ #MaxDynamicColumn+'
FROM
(
SELECT * FROM #FormatedTable
) AS src
PIVOT
(
MAX(Data) FOR [ReqColumn] IN ('+#DynamicColumn+')
) AS Pvt
'
EXEC (#Sql)
PRINT #Sql
Result
SeqId Data1 Data2 Data3 Data4 Data5
----------------------------------------------
1 Entry1 Entry2 Entry3 Entry4 Entry5
There is no really simple way. You can use pivot or conditional aggregation. I prefer the latter:
select max(case when left(data, 3) = '[1]' then data end) as data_001,
max(case when left(data, 3) = '[2]' then data end) as data_002,
max(case when left(data, 5) = '[100]' then data end) as data_100
from Database1.dbo.tbl_Data d join
Database1.tbl_outbound o
on d.session_id = o.session_id
where o.campaign_id = 1047 and d.session_id = 12;
Note that the columns are fixed, so you will always have 100 columns, regardless of the number of actual values in the data.
If you need a flexible number of columns, then you need dynamic pivoting, which requires constructing the query as a string and then executing the string.
The easiest way to do that is to utilize SQLCLR.
Check out the solution and explanation on An Easier Way of Transposing Query Result in SQL Server

T-Sql Query with dynamic (unknown) number of columns

We have a project where we should provide the possible to the user to add own custom columns to various tables.
Edit: these are 2 tables, not one.
**Products**
ProductId
Name
Price
Date
UserId
**ProductsCustomColumns**
ProductId
ColumnName
ColumnValue
EDIT: Please note that the dynamic columns are recorded as values and we don't know the count of these...it can be 0 or 200 or any.
Here is an example:
Now when we query the products tables we want to show all the predefined columns and after them all custom columns.
Obviously each user can have own number of columns with values and names.
SELECT *, (and the custom columns) FROM Products WHERE UserId = 3 AND ProductId = 1
Here are 2 questions:
Would that be good solution from performance point of view or there is better approach for solving the dynamic columns requirement?
How can I create a query that could read all records from ProductsCustomColumns for given userId and productId and append the records as columns to the query?
Thanks.
You need to write dynamic Query
DECLARE #COLUMNS VARCHAR(MAX)='', #QRY VARCHAR(MAX);
SELECT #COLUMNS = #COLUMNS +COLUMN_NAME +',' FROM
INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='Products'
SELECT #COLUMNS =SUBSTRING (#COLUMNS,1 ,LEN(#COLUMNS)-1)
SELECT #QRY ='SELECT '+#COLUMNS + ' FROM Products WHERE UserId = 3 AND ProductId = 1'
EXEC (#QRY)
EDIT: From your Comments & Edited Question
Schema I assumed from your Question
CREATE TABLE Products (
ProductId INT,
Name VARCHAR(250),
Price DECIMAL(18,2),
DateS DATETIME,
UserId INT)
INSERT INTO Products
SELECT 1,'Oil Product', 2000, GETDATE(), 3
UNION ALL
SELECT 2,'Amway', 600, GETDATE(), 2
UNION ALL
SELECT 3,'Thermal', 5000, GETDATE(), 1
UNION ALL
SELECT 4,'Oil Product', 500, GETDATE(), 4
CREATE TABLE ProductsCustomColumns
(
ProductId INT ,
ColumnName VARCHAR(200),
ColumnValue VARCHAR(15))
INSERT INTO ProductsCustomColumns
SELECT 1, 'Licence_No', '1545'
UNION ALL
SELECT 1, 'Location ', 'Atlanta'
UNION ALL
SELECT 2, 'Qty ', '5'
UNION ALL
SELECT 3, 'Gross', '80000'
Now your Dynamic Code goes here
DECLARE #COLUMN_PCC VARCHAR(MAX)='', #PRODUCT_ID INT=1,#USER_ID INT=3, #QRY VARCHAR(MAX) ;
--preparing Custom Column Name List with comma ','
SELECT #COLUMN_PCC = #COLUMN_PCC+ [COLUMNNAME] +',' FROM ProductsCustomColumns
WHERE ProductId= #PRODUCT_ID
SELECT #COLUMN_PCC =SUBSTRING(#COLUMN_PCC,1,LEN(#COLUMN_PCC)-1)
--Preparing Dynamic Query
SELECT #QRY =' SELECT P.*, AV.* FROM Products P
INNER JOIN
(
SELECT * FROM (
SELECT * FROM ProductsCustomColumns WHERE ProductId= '+CAST(#PRODUCT_ID AS VARCHAR(50))+'
)
AS A
PIVOT
(
MAX (COLUMNVALUE)
FOR [COLUMNNAME] IN ('+#COLUMN_PCC +')
)AS PVT
)AS AV ON P.ProductId= AV.ProductId
AND P.UserId='++CAST(#USER_ID AS VARCHAR(50))+'
'
EXEC ( #QRY)
And the Result will be
+-----------+-------------+---------+-------------------------+--------+-----------+------------+----------+
| ProductId | Name | Price | DateS | UserId | ProductId | Licence_No | Location |
+-----------+-------------+---------+-------------------------+--------+-----------+------------+----------+
| 1 | Oil Product | 2000.00 | 2016-12-09 18:06:24.090 | 3 | 1 | 1545 | Atlanta |
+-----------+-------------+---------+-------------------------+--------+-----------+------------+----------+
You need dynamic sql no other way to do this
DECLARE #sql VARCHAR(max),
#cust_col VARCHAR(max)
SET #cust_col = (SELECT Quotename(CustomColumns) + ','
FROM ProductsCustomColumns
FOR xml path(''))
SELECT #cust_col = LEFT(#cust_col, Len(#cust_col) - 1)
SET #sql = 'SELECT *, ' + #cust_col + ' FROM Products WHERE UserId = 3 AND ProductId = 1'
EXEC (#sql)
In general it is a very bad idea to add custom data in additional columns of your main table. Just imagine 100 customers using this. All of them have differing table schemas and you wnat to write an update script for all of them?
It is a pain in the neck, if you have to deal with result sets where you don't know the structure in advance!
You have several choices:
Add one column of type XML. The advantage: The resultset is fix. You just need a customer specific rule, how to interpret the XML. You can solve this with an inline table valued function. Pass in the XML and get a derived table back. Call this with CROSS APPLY and you are out...
Add a new table with the customerID and Key-Value-Pairs
If the additional data is not completely different, add some of the columns to your main table as SPARSE columns

Dynamic SELECT statement, generate columns based on present and future values

Currently building a SELECT statement in SQL Server 2008 but would like to make this SELECT statement dynamic, so the columns can be defined based on values in a table. I heard about pivot table and cursors, but seems kind of hard to understand at my current level, here is the code;
DECLARE #date DATE = null
IF #date is null
set # date = GETDATE() as DATE
SELECT
Name,
value1,
value2,
value3,
value4
FROM ref_Table a
FULL OUTER JOIN (
SELECT
PK_ID ID,
sum(case when FK_ContainerType_ID = 1 then 1 else null) Box,
sum(case when FK_ContainerType_ID = 2 then 1 else null) Pallet,
sum(case when FK_ContainerType_ID = 3 then 1 else null) Bag,
sum(case when FK_ContainerType_ID = 4 then 1 else null) Drum
from
Packages
WHERE
#date between PackageStart AND PackageEnd
group by PK_ID ) b on a.Name = b.ID
where
Group = 0
The following works great for me , but PK_Type_ID and the name of the column(PackageNameX,..) are hard coded, I need to be dynamic and it can build itself based on present or futures values in the Package table.
Any help or guidance on the right direction would be greatly appreciated...,
As requested
ref_Table (PK_ID, Name)
1, John
2, Mary
3, Albert
4, Jane
Packages (PK_ID, FK_ref_Table_ID, FK_ContainerType_ID, PackageStartDate, PackageEndDate)
1 , 1, 4, 1JAN2014, 30JAN2014
2 , 2, 3, 1JAN2014, 30JAN2014
3 , 3, 2, 1JAN2014, 30JAN2014
4 , 4, 1, 1JAN2014, 30JAN2014
ContainerType (PK_ID, Type)
1, Box
2, Pallet
3, Bag
4, Drum
and the result should look like this;
Name Box Pallet Bag Drum
---------------------------------------
John 1
Mary 1
Albert 1
Jane 1
The following code like I said works great, the issue is the Container table is going to grow and I need to replicated the same report without hard coding the columns.
What you need to build is called a dynamic pivot. There are plenty of good references on Stack if you search out that term.
Here is a solution to your scenario:
IF OBJECT_ID('tempdb..##ref_Table') IS NOT NULL
DROP TABLE ##ref_Table
IF OBJECT_ID('tempdb..##Packages') IS NOT NULL
DROP TABLE ##Packages
IF OBJECT_ID('tempdb..##ContainerType') IS NOT NULL
DROP TABLE ##ContainerType
SET NOCOUNT ON
CREATE TABLE ##ref_Table (PK_ID INT, NAME NVARCHAR(50))
CREATE TABLE ##Packages (PK_ID INT, FK_ref_Table_ID INT, FK_ContainerType_ID INT, PackageStartDate DATE, PackageEndDate DATE)
CREATE TABLE ##ContainerType (PK_ID INT, [Type] NVARCHAR(50))
INSERT INTO ##ref_Table (PK_ID,NAME)
SELECT 1,'John' UNION
SELECT 2,'Mary' UNION
SELECT 3,'Albert' UNION
SELECT 4,'Jane'
INSERT INTO ##Packages (PK_ID, FK_ref_Table_ID, FK_ContainerType_ID, PackageStartDate, PackageEndDate)
SELECT 1,1,4,'2014-01-01','2014-01-30' UNION
SELECT 2,2,3,'2014-01-01','2014-01-30' UNION
SELECT 3,3,2,'2014-01-01','2014-01-30' UNION
SELECT 4,4,1,'2014-01-01','2014-01-30'
INSERT INTO ##ContainerType (PK_ID, [Type])
SELECT 1,'Box' UNION
SELECT 2,'Pallet' UNION
SELECT 3,'Bag' UNION
SELECT 4,'Drum'
DECLARE #DATE DATE, #PARAMDEF NVARCHAR(MAX), #COLS NVARCHAR(MAX), #SQL NVARCHAR(MAX)
SET #DATE = '2014-01-15'
SET #COLS = STUFF((SELECT DISTINCT ',' + QUOTENAME(T.[Type])
FROM ##ContainerType T
FOR XML PATH, TYPE).value('.', 'NVARCHAR(MAX)'),1,1,'')
SET #SQL = 'SELECT [Name], ' + #COLS + '
FROM (SELECT [Name], [Type], 1 AS Value
FROM ##ref_Table R
JOIN ##Packages P ON R.PK_ID = P.FK_ref_Table_ID
JOIN ##ContainerType T ON P.FK_ContainerType_ID = T.PK_ID
WHERE #DATE BETWEEN P.PackageStartDate AND P.PackageEndDate) X
PIVOT (COUNT(Value) FOR [Type] IN (' + #COLS + ')) P
'
PRINT #COLS
PRINT #SQL
SET #PARAMDEF = '#DATE DATE'
EXEC SP_EXECUTESQL #SQL, #PARAMDEF, #DATE=#DATE
Output:
Name Bag Box Drum Pallet
Albert 0 0 0 1
Jane 0 1 0 0
John 0 0 1 0
Mary 1 0 0 0
Static Query:
SELECT [Name],[Box],[Pallet],[Bag],[Drum] FROM
(
SELECT *
FROM
(
SELECT rf.Name,cnt.[Type], pk.PK_ID AS PKID, rf.PK_ID AS RFID
FROM ref_Table rf INNER JOIN Packages pk ON rf.PK_ID = pk.FK_ref_Table_ID
INNER JOIN ContanerType cnt ON cnt.PK_ID = pk.FK_ContainerType_ID
) AS SourceTable
PIVOT
(
COUNT(PKID )
FOR [Type]
IN ( [Box],[Pallet],[Bag],[Drum])
) AS PivotTable
) AS Main
ORDER BY RFID
Dynamic Query:
DECLARE #columnList nvarchar (MAX)
DECLARE #pivotsql nvarchar (MAX)
SELECT #columnList = STUFF(
(
SELECT ',' + '[' + [Type] + ']'
FROM ContanerType
FOR XML PATH( '')
)
,1, 1,'' )
SET #pivotsql =
N'SELECT [Name],' + #columnList + ' FROM
(
SELECT *
FROM
(
SELECT rf.Name,cnt.[Type], pk.PK_ID AS PKID, rf.PK_ID AS RFID
FROM ref_Table rf INNER JOIN Packages pk ON rf.PK_ID = pk.FK_ref_Table_ID
INNER JOIN ContanerType cnt ON cnt.PK_ID = pk.FK_ContainerType_ID
) AS SourceTable
PIVOT
(
COUNT(PKID )
FOR [Type]
IN ( ' + #columnList + ')
) AS PivotTable
) AS Main
ORDER BY RFID;'
EXEC sp_executesql #pivotsql
Following my tutorial below will help you to understand the PIVOT functionality:
We write sql queries in order to get different result sets like full, partial, calculated, grouped, sorted etc from the database tables. However sometimes we have requirements that we have to rotate our tables. Sounds confusing?
Let's keep it simple and consider the following two screen grabs.
SQL Table:
Expected Results:
Wow, that's look like a lot of work! That is a combination of tricky sql, temporary tables, loops, aggregation......, blah blah blah
Don't worry let's keep it simple, stupid(KISS).
MS SQL Server 2005 and above has a function called PIVOT. It s very simple to use and powerful. With the help of this function we will be able to rotate sql tables and result sets.
Simple steps to make it happen:
Identify all the columns those will be part of the desired result set.
Find the column on which we will apply aggregation(sum,ave,max,min etc)
Identify the column which values will be the column header.
Specify the column values mentioned in step3 with comma separated and surrounded by square brackets.
So, if we now follow above four steps and extract information from the above sales table, it will be as below:
Year, Month, SalesAmount
SalesAmount
Month
[Jan],[Feb] ,[Mar] .... etc
We are nearly there if all the above steps made sense to you so far.
Now we have all the information we need. All we have to do now is to fill the below template with required information.
Template:
Our SQL query should look like below:
SELECT *
FROM
(
SELECT SalesYear, SalesMonth,Amount
FROM Sales
) AS SourceTable
PIVOT
(
SUM(Amount )
FOR SalesMonth
IN ( [Jan],[Feb] ,[Mar],
[Apr],[May],[Jun] ,[Jul],
[Aug],[Sep] ,[Oct],[Nov] ,[Dec])
) AS PivotTable;
In the above query we have hard coded the column names. Well it's not fun when you have to specify a number of columns.
However, there is a work arround as follows:
DECLARE #columnList nvarchar (MAX)
DECLARE #pivotsql nvarchar (MAX)
SELECT #columnList = STUFF(
(
SELECT ',' + '[' + SalesMonth + ']'
FROM Sales
GROUP BY SalesMonth
FOR XML PATH( '')
)
,1, 1,'' )
SET #pivotsql =
N'SELECT *
FROM
(
SELECT SalesYear, SalesMonth,Amount
FROM Sales
) AS SourceTable
PIVOT
(
SUM(Amount )
FOR SalesMonth
IN ( ' + #columnList +' )
) AS PivotTable;'
EXEC sp_executesql #pivotsql
Hopefully this tutorial will be a help to someone somewhere.
Enjoy coding.

Optimal way to concatenate/aggregate strings

I'm finding a way to aggregate strings from different rows into a single row. I'm looking to do this in many different places, so having a function to facilitate this would be nice. I've tried solutions using COALESCE and FOR XML, but they just don't cut it for me.
String aggregation would do something like this:
id | Name Result: id | Names
-- - ---- -- - -----
1 | Matt 1 | Matt, Rocks
1 | Rocks 2 | Stylus
2 | Stylus
I've taken a look at CLR-defined aggregate functions as a replacement for COALESCE and FOR XML, but apparently SQL Azure does not support CLR-defined stuff, which is a pain for me because I know being able to use it would solve a whole lot of problems for me.
Is there any possible workaround, or similarly optimal method (which might not be as optimal as CLR, but hey I'll take what I can get) that I can use to aggregate my stuff?
SOLUTION
The definition of optimal can vary, but here's how to concatenate strings from different rows using regular Transact SQL, which should work fine in Azure.
;WITH Partitioned AS
(
SELECT
ID,
Name,
ROW_NUMBER() OVER (PARTITION BY ID ORDER BY Name) AS NameNumber,
COUNT(*) OVER (PARTITION BY ID) AS NameCount
FROM dbo.SourceTable
),
Concatenated AS
(
SELECT
ID,
CAST(Name AS nvarchar) AS FullName,
Name,
NameNumber,
NameCount
FROM Partitioned
WHERE NameNumber = 1
UNION ALL
SELECT
P.ID,
CAST(C.FullName + ', ' + P.Name AS nvarchar),
P.Name,
P.NameNumber,
P.NameCount
FROM Partitioned AS P
INNER JOIN Concatenated AS C
ON P.ID = C.ID
AND P.NameNumber = C.NameNumber + 1
)
SELECT
ID,
FullName
FROM Concatenated
WHERE NameNumber = NameCount
EXPLANATION
The approach boils down to three steps:
Number the rows using OVER and PARTITION grouping and ordering them as needed for the concatenation. The result is Partitioned CTE. We keep counts of rows in each partition to filter the results later.
Using recursive CTE (Concatenated) iterate through the row numbers (NameNumber column) adding Name values to FullName column.
Filter out all results but the ones with the highest NameNumber.
Please keep in mind that in order to make this query predictable one has to define both grouping (for example, in your scenario rows with the same ID are concatenated) and sorting (I assumed that you simply sort the string alphabetically before concatenation).
I've quickly tested the solution on SQL Server 2012 with the following data:
INSERT dbo.SourceTable (ID, Name)
VALUES
(1, 'Matt'),
(1, 'Rocks'),
(2, 'Stylus'),
(3, 'Foo'),
(3, 'Bar'),
(3, 'Baz')
The query result:
ID FullName
----------- ------------------------------
2 Stylus
3 Bar, Baz, Foo
1 Matt, Rocks
Are methods using FOR XML PATH like below really that slow? Itzik Ben-Gan writes that this method has good performance in his T-SQL Querying book (Mr. Ben-Gan is a trustworthy source, in my view).
create table #t (id int, name varchar(20))
insert into #t
values (1, 'Matt'), (1, 'Rocks'), (2, 'Stylus')
select id
,Names = stuff((select ', ' + name as [text()]
from #t xt
where xt.id = t.id
for xml path('')), 1, 2, '')
from #t t
group by id
STRING_AGG() in SQL Server 2017, Azure SQL, and PostgreSQL:
https://www.postgresql.org/docs/current/static/functions-aggregate.html
https://learn.microsoft.com/en-us/sql/t-sql/functions/string-agg-transact-sql
GROUP_CONCAT() in MySQL
http://dev.mysql.com/doc/refman/5.7/en/group-by-functions.html#function_group-concat
(Thanks to #Brianjorden and #milanio for Azure update)
Example Code:
select Id
, STRING_AGG(Name, ', ') Names
from Demo
group by Id
SQL Fiddle: http://sqlfiddle.com/#!18/89251/1
Although #serge answer is correct but i compared time consumption of his way against xmlpath and i found the xmlpath is so faster. I'll write the compare code and you can check it by yourself.
This is #serge way:
DECLARE #startTime datetime2;
DECLARE #endTime datetime2;
DECLARE #counter INT;
SET #counter = 1;
set nocount on;
declare #YourTable table (ID int, Name nvarchar(50))
WHILE #counter < 1000
BEGIN
insert into #YourTable VALUES (ROUND(#counter/10,0), CONVERT(NVARCHAR(50), #counter) + 'CC')
SET #counter = #counter + 1;
END
SET #startTime = GETDATE()
;WITH Partitioned AS
(
SELECT
ID,
Name,
ROW_NUMBER() OVER (PARTITION BY ID ORDER BY Name) AS NameNumber,
COUNT(*) OVER (PARTITION BY ID) AS NameCount
FROM #YourTable
),
Concatenated AS
(
SELECT ID, CAST(Name AS nvarchar) AS FullName, Name, NameNumber, NameCount FROM Partitioned WHERE NameNumber = 1
UNION ALL
SELECT
P.ID, CAST(C.FullName + ', ' + P.Name AS nvarchar), P.Name, P.NameNumber, P.NameCount
FROM Partitioned AS P
INNER JOIN Concatenated AS C ON P.ID = C.ID AND P.NameNumber = C.NameNumber + 1
)
SELECT
ID,
FullName
FROM Concatenated
WHERE NameNumber = NameCount
SET #endTime = GETDATE();
SELECT DATEDIFF(millisecond,#startTime, #endTime)
--Take about 54 milliseconds
And this is xmlpath way:
DECLARE #startTime datetime2;
DECLARE #endTime datetime2;
DECLARE #counter INT;
SET #counter = 1;
set nocount on;
declare #YourTable table (RowID int, HeaderValue int, ChildValue varchar(5))
WHILE #counter < 1000
BEGIN
insert into #YourTable VALUES (#counter, ROUND(#counter/10,0), CONVERT(NVARCHAR(50), #counter) + 'CC')
SET #counter = #counter + 1;
END
SET #startTime = GETDATE();
set nocount off
SELECT
t1.HeaderValue
,STUFF(
(SELECT
', ' + t2.ChildValue
FROM #YourTable t2
WHERE t1.HeaderValue=t2.HeaderValue
ORDER BY t2.ChildValue
FOR XML PATH(''), TYPE
).value('.','varchar(max)')
,1,2, ''
) AS ChildValues
FROM #YourTable t1
GROUP BY t1.HeaderValue
SET #endTime = GETDATE();
SELECT DATEDIFF(millisecond,#startTime, #endTime)
--Take about 4 milliseconds
Update: Ms SQL Server 2017+, Azure SQL Database
You can use: STRING_AGG.
Usage is pretty simple for OP's request:
SELECT id, STRING_AGG(name, ', ') AS names
FROM some_table
GROUP BY id
Read More
Well my old non-answer got rightfully deleted (left in-tact below), but if anyone happens to land here in the future, there is good news. They have implimented STRING_AGG() in Azure SQL Database as well. That should provide the exact functionality originally requested in this post with native and built in support. #hrobky mentioned this previously as a SQL Server 2016 feature at the time.
--- Old Post:
Not enough reputation here to reply to #hrobky directly, but STRING_AGG looks great, however it is only available in SQL Server 2016 vNext currently. Hopefully it will follow to Azure SQL Datababse soon as well..
You can use += to concatenate strings, for example:
declare #test nvarchar(max)
set #test = ''
select #test += name from names
if you select #test, it will give you all names concatenated
I found Serge's answer to be very promising, but I also encountered performance issues with it as-written. However, when I restructured it to use temporary tables and not include double CTE tables, the performance went from 1 minute 40 seconds to sub-second for 1000 combined records. Here it is for anyone who needs to do this without FOR XML on older versions of SQL Server:
DECLARE #STRUCTURED_VALUES TABLE (
ID INT
,VALUE VARCHAR(MAX) NULL
,VALUENUMBER BIGINT
,VALUECOUNT INT
);
INSERT INTO #STRUCTURED_VALUES
SELECT ID
,VALUE
,ROW_NUMBER() OVER (PARTITION BY ID ORDER BY VALUE) AS VALUENUMBER
,COUNT(*) OVER (PARTITION BY ID) AS VALUECOUNT
FROM RAW_VALUES_TABLE;
WITH CTE AS (
SELECT SV.ID
,SV.VALUE
,SV.VALUENUMBER
,SV.VALUECOUNT
FROM #STRUCTURED_VALUES SV
WHERE VALUENUMBER = 1
UNION ALL
SELECT SV.ID
,CTE.VALUE + ' ' + SV.VALUE AS VALUE
,SV.VALUENUMBER
,SV.VALUECOUNT
FROM #STRUCTURED_VALUES SV
JOIN CTE
ON SV.ID = CTE.ID
AND SV.VALUENUMBER = CTE.VALUENUMBER + 1
)
SELECT ID
,VALUE
FROM CTE
WHERE VALUENUMBER = VALUECOUNT
ORDER BY ID
;
Try this, i use it in my projects
DECLARE #MetricsList NVARCHAR(MAX);
SELECT #MetricsList = COALESCE(#MetricsList + '|', '') + QMetricName
FROM #Questions;

Comma separated values in one column - SQL SERVER

Customer Table
--------------
ID Name
1 James
2 Peter
Order Table
---------------
OrderId CustId
100 1
101 1
102 2
How can I write a query that returns something like this
ID,Name,ListofOrders
1,James,"100,101"
2,Peter,"102"
In Sybase I had a function called LIST which I could use but I dont find a similar function in SQL SERVER
Please try:
select ID, [Name],
(select OrderID+',' from OrderTable where CustID=ID
group by OrderID for xml path('')) AS ListOfOrders
From CustomerTable
Create a User Defined Function as shown below
CREATE FUNCTION [dbo].[CommaSeperatedOrderIDs](#CustId INT) returns varchar(Max)
AS
BEGIN
DECLARE #CommaSeperatedValues VARCHAR(MAX)
SELECT #CommaSeperatedValues = COALESCE(#CommaSeperatedValues+',' , '') + OrderID
FROM OrderTable WHERE CustId = #CustId
RETURN #CommaSeperatedValues
END
And then,
select ID, [Name], ([dbo].[CommaSeperatedOrderIDs](ID)) AS ListofOrders
From CustomerTable
Adding full details from Sheikh Haris' link.
Given this table:
To get output like:
Use the following SQL:
SELECT field1,
Substring(convert(varchar(100),
(
SELECT (', ' + field2)
FROM #test t2
WHERE t1.field1 = t2.field1
ORDER BY field1, field2
FOR XML PATH( '' )
)), 3, 1000 )
FROM #test t1
GROUP BY field1
I added a convert function to the substring so that the WIDEMEMO field would be displayed (SQL Server)
A very simple and handy solution given on the link below.
http://tejasnshah.wordpress.com/2009/02/28/sql-server-get-column-values-as-comma-seperated-list-using-xml-path-instead-of-udfs-using-sql-coalesce/
The SQL query written on that link is in an image ...so i couldn't copy it here.