Comma-separated values to rows with sum in SQL Server - sql

create table #t(org varchar(50), area int)
insert into #t values ('a', 500), ('b', 200), ('c', 400)
select * from #t
Output:
org area
--------------
a 500
b 200
c 400
I want to convert the output to this format:
org area
-----------------
a,b,c 1100
i.e. comma-separated all the orgs and sum of it's area value.

Sub-query is really not necessary :
select stuff((select ','+t1.org from #t t1 for xml path('')), 1, 1, '') as org,
sum(area) as area
from #t t;

Use a SubQuery/CTE, a window function SUM() OVER(), and STRING_AGG()
create table #t(org varchar(50), area int)
insert into #t values ('a',500),('b',200),('c',400);
select string_agg(org, ',') org, area
from
(
select org, sum(area) over() area
from #t
)t
group by area
or
select org, sum(area) area
from
(
select stuff(
(select ',' + org from #t for xml path('')), 1, 1, '') org, area
from #t tt
) t
group by org

If use sql server upper than 2017, you can use String_agg()
create table #t(org varchar(50), area int)
insert into #t values ('a',500),('b',200),('c',400)
select String_agg(org, ',') as org, Sum(area) as sumarea
from #t

You can use the following query in two parts as shown below.
In the first part get all the values in the comma-separated and in a final query select the sum of all the values of the area.
create table #t(org varchar(50), area int)
insert into #t values ('a', 500), ('b', 200), ('c', 400)
DECLARE #List VARCHAR(8000)
SELECT #List = COALESCE(#List + ',', '') + org
FROM #t --For getting values in comma separated
SELECT #List as AllValues
, sum(area) as Total
from #t
Live db<>fiddle demo.

Related

Need query to join table on column with comma separated

Table1
ID
Notes
ReasonID
1
Test1
[11,12]
2
Test2
[13,14]
Table 2
Reasonid
Name
11
Other1
12
Other2
13
Other3
14
Other4
Result should look like this, where Notes column from Table1 should concat with Name column from Table2.
ID
Final_Notes
1
Test1,Other1,Other2
2
Test2,Other3,Other4
If you use SQL Server 2017+, you may try to parse the ReasonID column as JSON, use an appropriate JOIN and then aggregate with STRING_AGG().
Sample data:
SELECT *
INTO Table1
FROM (VALUES
(1, 'Test1', '[11,12]'),
(2, 'Test2', '[13,14]')
) t (ID, Notes, ReasonID)
SELECT *
INTO Table2
FROM (VALUES
(11, 'Other1'),
(12, 'Other2'),
(13, 'Other3'),
(14, 'Other4')
) t (ReasonID, Name)
Statement:
SELECT
ID,
FinalNotes = CONCAT(
Notes,
',',
(
SELECT STRING_AGG(t2.Name, ',') WITHIN GROUP (ORDER BY CONVERT(int, j.[key]))
FROM OPENJSON(ReasonID) j
-- Important, JOIN with possible implicit conversion
JOIN Table2 t2 ON j.[value] = t2.ReasonID
)
)
FROM Table1
Result:
ID
FinalNotes
1
Test1,Other1,Other2
2
Test2,Other3,Other4
db<>fiddle
Please try the following solution.
It will work starting from SQL Server 2012 onwards.
It is using the following:
XML/XQuery to tokenize comma separated list of values.
FOR XML PATH to compose FinalNotes comma separated list.
SQL
-- DDL and sample data population, start
DECLARE #Table1 TABLE(ID INT, Notes VARCHAR(60), ReasonID VARCHAR(60));
INSERT INTO #Table1(ID, Notes, ReasonID) VALUES
(1, 'Test1', '[11,12]'),
(2, 'Test2', '[13,14]');
DECLARE #Table2 TABLE(Reasonid INT, Name VARCHAR(60));
INSERT INTO #Table2(Reasonid, Name) VALUES
(11, 'Other1'),
(12, 'Other2'),
(13, 'Other3'),
(14, 'Other4');
-- DDL and sample data population, end
DECLARE #separator CHAR(1) = ',';
;WITH rs AS
(
SELECT ID, Notes, Name
FROM #Table1 AS t
CROSS APPLY (SELECT TRY_CAST('<root><r><![CDATA[' +
REPLACE(REPLACE(REPLACE(ReasonID,'[',''),']',''), #separator, ']]></r><r><![CDATA[') +
']]></r></root>' AS XML)) AS t1(c)
CROSS APPLY c.nodes('/root/r/text()') AS t2(x)
INNER JOIN #Table2 AS t3 ON t3.Reasonid = x.value('.', 'INT')
)
SELECT ID, CONCAT(Notes
, (SELECT #separator + c.Name AS [text()]
FROM rs AS c
WHERE c.ID = p.ID
FOR XML PATH(''))) AS FinalNotes
FROM rs AS p
GROUP BY ID, Notes;
Output
+----+---------------------+
| ID | FinalNotes |
+----+---------------------+
| 1 | Test1,Other1,Other2 |
| 2 | Test2,Other3,Other4 |
+----+---------------------+
use SUBSTRING(string, 2, LEN(string)-2) for deleting [] and Parsename to split based on comma and join and concat as follows
Your data
DECLARE #Table1 TABLE(
ID INTEGER NOT NULL,
Notes VARCHAR(60) NOT NULL,
ReasonID VARCHAR(60) NOT NULL
);
INSERT INTO #Table1(ID, Notes, ReasonID)
VALUES
(1, 'Test1', '[11,12]'),
(2, 'Test2', '[13,14]');
DECLARE #Table2 TABLE(
Reasonid INTEGER NOT NULL,
Name VARCHAR(60) NOT NULL
);
INSERT INTO #Table2(Reasonid, Name)
VALUES
(11, 'Other1'),
(12, 'Other2'),
(13, 'Other3'),
(14, 'Other4');
your query
SELECT id,
Concat(notes, ',', T2.name, ',', T3.name) FinalNotes
FROM (SELECT id,
notes,
Parsename(Replace(SUBSTRING(ReasonID, 2, LEN(ReasonID)-2), ',', '.'), 2) R1,
Parsename(Replace(SUBSTRING(ReasonID, 2, LEN(ReasonID)-2), ',', '.'), 1) R2
FROM #table1) T1
join #table2 T2
ON T1.R1 = T2.reasonid
join #table2 T3
ON T1.R2 = T3.reasonid
by using XML
DROP TABLE IF EXISTS #t -- temporary table
select t1.ID,t1.Notes, Name into #t -- temporary table
from
(
SELECT A.ID,a.Notes,
Split.a.value('.', 'VARCHAR(100)') AS String
FROM (SELECT ID, Notes,
CAST ('<M>' + REPLACE(SUBSTRING(ReasonID, 2, LEN(ReasonID)-2) , ',', '</M><M>') + '</M>' AS XML) AS String
FROM #Table1) AS A CROSS APPLY String.nodes ('/M') AS Split(a)) t1
join #Table2 t2 on t1.String=t2.Reasonid
---XML Path
SELECT ID,concat(notes,',',
STUFF((SELECT ', ' + CAST(name AS VARCHAR(10)) [text()]
FROM #t t1
WHERE t1.ID = t.ID
FOR XML PATH(''), TYPE)
.value('.','NVARCHAR(MAX)'),1,2,' ')) FinalNotes
FROM #t t
GROUP BY ID,notes

Iterating through a SQL Server 2008 R2 table variable and concatenate values

I have a table variable #Holding two columns: an id (not unique) and a message:
id message
---- -------
2 give
2 me
2 help
3 Need
3 help
1 help!
The result should be
2 give me help
3 Need help
1 help!
This it very much simplified, but shows that there are id which may exist more than once, and some kind of text which should be concatenated into a string.
I cannot manage it to loop through this table variable (but not through a table too!).
I tried a cursor (which I did not understand correctly) but it failed of course.
The number of records are not that much, not even 100 in that table variable.
Thanks yr. help
Michael
Original question
No CURSOR, WHILE loop, or User-Defined Function needed.
Just need to be creative with FOR XML and PATH.
[Note: This solution only works on SQL 2005 and later. Original question didn't specify the version in use.]
CREATE TABLE #YourTable ([ID] INT, [Name] CHAR(1), [Value] INT)
INSERT INTO #YourTable ([ID],[Name],[Value]) VALUES (1,'A',4)
INSERT INTO #YourTable ([ID],[Name],[Value]) VALUES (1,'B',8)
INSERT INTO #YourTable ([ID],[Name],[Value]) VALUES (2,'C',9)
SELECT
[ID],
STUFF((
SELECT ', ' + [Name] + ':' + CAST([Value] AS VARCHAR(MAX))
FROM #YourTable
WHERE (ID = Results.ID)
FOR XML PATH(''),TYPE).value('(./text())[1]','VARCHAR(MAX)')
,1,2,'') AS NameValues
FROM #YourTable Results
GROUP BY ID
DROP TABLE #YourTable
This should work if you are using SQL Server.
select T1.id,
stuff((select ' '+T2.[message]
from #A as T2
where T1.id = T2.id
for xml path(''), type).value('.', 'varchar(max)'), 1, 1, '') as [message]
from #A as T1
group by T1.id
Pretty the same, just less letters in the code:
DECLARE #t AS TABLE ( id INT, msg VARCHAR(100) );
INSERT INTO #t
VALUES ( 2, 'give' );
INSERT INTO #t
VALUES ( 2, 'me' );
INSERT INTO #t
VALUES ( 2, 'help' );
INSERT INTO #t
VALUES ( 3, 'Need' );
INSERT INTO #t
VALUES ( 3, 'help' );
INSERT INTO #t
VALUES ( 1, 'help!' );
SELECT DISTINCT
id ,
(
SELECT ST1.msg + ' '
FROM #t ST1
WHERE ST1.id = ST2.id
FOR XML PATH('')
) t
FROM
#t ST2;

Can a Pivot table be used with a unknown number of columns?

If I have a team table with a unknown amount of members, is there a way to make the pivot query dynamic?
create table #t (
team varchar (20), member varchar (20)
)
insert into #t values ('ERP', 'Jack')
insert into #t values ('ERP', 'John')
insert into #t values ('ERP', 'Mary')
insert into #t values ('ERP', 'Tim')
insert into #t values ('CRM', 'Robert')
insert into #t values ('CRM', 'Diana')
select * from #t
select team, [1] as teamMember1, /* 1st select */
[2] as teamMember2, [3] as teamMember3
from
(select team , member, row_number () /* 3rd select */
over (partition by team order by team) as rownum
from #t) a
pivot (max(member) for rownum in ([1], [2], [3])) as pvt
drop table #t
Why yes, yes there is. Here's a script I cooked up years ago for a similar problem that was ultimately solved by giving the user Excel and washing my hands of it. I apologize it's not configured with your example data, but hopefully it's easy to follow.
Hope that helps,
John
--------------START QUERY--------------
-- Example Table
CREATE TABLE #glbTestTable
(
ProviderID INT,
Total INT,
PaymentDate SMALLDATETIME
)
--So the dates insert properly
SET DATEFORMAT dmy
-- Populate Example Table
INSERT INTO #glbTestTable VALUES (232, 12200, '12/01/09')
INSERT INTO #glbTestTable VALUES (456, 10200, '12/01/09')
INSERT INTO #glbTestTable VALUES (563, 11899, '02/03/09')
INSERT INTO #glbTestTable VALUES (221, 5239, '13/04/09')
INSERT INTO #glbTestTable VALUES (987, 7899, '02/03/09')
INSERT INTO #glbTestTable VALUES (1, 1234, '02/08/09')
INSERT INTO #glbTestTable VALUES (2, 4321, '02/07/09')
INSERT INTO #glbTestTable VALUES (3, 5555, '02/06/09')
-- Raw Output
SELECT *
FROM #glbTestTable
-- Build Query for Pivot --
DECLARE #pvtColumns VARCHAR(MAX)
SET #pvtColumns = ''
-- Grab up to the first 1023 "Columns" that we want to use in Pivot Table.
-- Tables can only have 1024 columns at a maximum
SELECT TOP 1023 #pvtColumns = #pvtColumns + '[' + CONVERT(VARCHAR, PaymentDate, 103) + '], '
FROM (SELECT DISTINCT PaymentDate FROM #glbTestTable) t_distFP
-- Create PivotTable Query
DECLARE #myQuery VARCHAR(MAX)
SET #myQuery = '
SELECT ProviderID, ' + LEFT(#pvtColumns, LEN(#pvtColumns) - 1) + '
FROM (SELECT ProviderID, PaymentDate, Total
FROM #glbTestTable) AS SourceTable
PIVOT
(
SUM(Total)
FOR PaymentDate IN (' + LEFT(#pvtColumns, LEN(#pvtColumns) - 1) + ')
) AS PivotTable'
-- Run the Pivot Query
EXEC(#myQuery)
-- Cleanup
DROP TABLE #glbTestTable
---------------END QUERY---------------

Extracting numbers to a table from string column between delimiters

I have a table like this
Declare #Temp Table(Data VarChar(20))
Insert Into #Temp Values('F_200_100_')
Insert Into #Temp Values('F_50_')
Insert Into #Temp Values('F_30_')
Insert Into #Temp Values('F_50_10')
Insert Into #Temp Values('F_100_')
Insert Into #Temp Values('F_20_')
I want my output to be distinct values of the numbers extracted from data column
20
30
50
100
200
I have tried using patindex but I am looking for ideas
tried this
select
Left(
SubString(Data, PatIndex('%[0-9]%', Data), 8000),
PatIndex('%[^0-9]%', SubString(Data, PatIndex('%[0-9]%', Data), 8000) + 'X')-1
)
from #temp
Reference
http://blogs.lessthandot.com/index.php/DataMgmt/DataDesign/extracting-numbers-with-sql-server
try this:
DECLARE #YourTable table (RowID int, Layout varchar(200))
INSERT INTO #YourTable
select ROW_NUMBER() over (order by (select 0)) as rn,replace(RIGHT(data,len(data)-CHARINDEX('_',data,1)),'_',',') from temptab
;WITH SplitSting AS
(
SELECT
RowID,LEFT(Layout,CHARINDEX(',',Layout)-1) AS Part
,RIGHT(Layout,LEN(Layout)-CHARINDEX(',',Layout)) AS Remainder
FROM #YourTable
WHERE Layout IS NOT NULL AND CHARINDEX(',',Layout)>0
UNION ALL
SELECT
RowID,LEFT(Remainder,CHARINDEX(',',Remainder)-1)
,RIGHT(Remainder,LEN(Remainder)-CHARINDEX(',',Remainder))
FROM SplitSting
WHERE Remainder IS NOT NULL AND CHARINDEX(',',Remainder)>0
UNION ALL
SELECT
RowID,Remainder,null
FROM SplitSting
WHERE Remainder IS NOT NULL AND CHARINDEX(',',Remainder)=0
)
SELECT distinct cast(part as int) FROM SplitSting where len(part) > 0 order by cast(part as int)

SQL Server Simple Group by query

I have a simple problem , Although i believe its simple , am not able to figure out the same.
Consider i have the below table with exactly same data as given below :
CREATE TABLE #temp
(
link varchar(255),
number INT,
fname varchar(255)
)
insert into #temp VALUES ('abc',1,'f1')
insert into #temp VALUES ('abc',2,'f2')
insert into #temp VALUES ('abc',3,'f3')
insert into #temp VALUES ('abc',4,'f6')
insert into #temp VALUES ('abc',10,'f100')
insert into #temp VALUES ('abe',-1,'f0')
insert into #temp VALUES ('abe',1,'f1')
insert into #temp VALUES ('abe',2,'f2')
insert into #temp VALUES ('abe',3,'f3')
insert into #temp VALUES ('abe',4,'f6')
insert into #temp VALUES ('abe',20,'f200')
insert into #temp VALUES ('cbe',-1,'f0')
insert into #temp VALUES ('cbe',1,'f1')
insert into #temp VALUES ('cbe',2,'f2')
insert into #temp VALUES ('cbe',3,'f3')
Now for a given link , i need to get the max 'number' and the corresponding 'fname' which has the max 'number' for the given 'link'.
1)Ex : if link is 'abc' , output should be
abc, 10, f100
2)Ex : if link if 'abe' , Output should be
abe, 20, f200
3)Now link can be also given as a pattern , like (link like 'ab%') , so output should be
abc, 10, f100
abe, 20, f200
4)if (link like 'cb%') , so output should be
cbe, 3, f3
Any help in writing this group by query. I have a solution using CAST and string concat like below , but that seems to be in-efficient.
select link,number,fname from #temp
where link like 'ab%' and link+'_'+CAST(number AS varchar(255))
in (select link+'_'+CAST(MAX(number) AS varchar(255)) from #temp
group by link)
Thanks..
Using a self join:
SELECT x.link,
x.number,
x.fname
FROM #temp x
JOIN (SELECT t.link,
MAX(t.number) AS max_number
FROM #temp t
GROUP BY t.link) y ON y.link = x.link
AND y.max_number = x.number
Using a CTE and ROW_NUMBER (SQL Server 2005+):
WITH cte AS (
SELECT x.link,
x.number,
x.fname,
ROW_NUMBER() OVER(PARTITION BY x.link
ORDER BY x.number DESC) rank
FROM #temp x)
SELECT c.link,
c.number,
c.fname
FROM cte c
WHERE c.rank = 1