How to perform a join in SQL Server without using tables - sql

I have two lists and I want to see what the two lists DON'T have in common. For example:
List1:
'a','b','c','123'
List2:
'd','e','f','a','asd','c'
I want output to be:
'b','123','d','e','f','asd'
Something like this?
select * from ('a','b','c','123')
join ('d','e','f','a','asd','c')
on ???
Is there a pure SQL Server solution for this without using tables?

If you have control over the lists, I would just make them table variables:
DECLARE #a TABLE (str varchar(100))
INSERT INTO #a
VALUES
('a'),
('b')...
DECLARE #b table (str varchar(100))
INSERT INTO #b
VALUES
...
(SELECT str FROM #a
EXCEPT
SELECT str FROM #b)
UNION
(SELECT str FROM #b
EXCEPT
SELECT str FROM #a)

Given this function:
CREATE FUNCTION dbo.SplitStrings ( #List NVARCHAR(MAX) )
RETURNS TABLE
AS
RETURN ( SELECT Item FROM (
SELECT Item = x.i.value('(./text())[1]', 'nvarchar(max)')
FROM (
SELECT [XML] = CONVERT(XML, '<i>' + REPLACE(#List,',', '</i><i>')
+ '</i>').query('.')) AS a CROSS APPLY [XML].nodes('i') AS x(i)
) AS y WHERE Item IS NOT NULL);
GO
You can do it with a full outer join:
DECLARE
#list1 NVARCHAR(MAX) = N'a,b,c,123',
#list2 NVARCHAR(MAX) = N'd,e,f,a,asd,c',
#output NVARCHAR(MAX) = N'';
SELECT #output += N',' + COALESCE(l1.Item, l2.Item)
FROM dbo.SplitStrings(#list1) AS l1
FULL OUTER JOIN dbo.SplitStrings(#list2) AS l2
ON l1.Item = l2.Item
WHERE l1.Item IS NULL OR l2.Item IS NULL;
SELECT STUFF(#output, 1, 1, N'');
Or similar to #JNK's:
DECLARE
#list1 NVARCHAR(MAX) = N'a,b,c,123',
#list2 NVARCHAR(MAX) = N'd,e,f,a,asd,c',
#output NVARCHAR(MAX) = N'';
;WITH l1 AS (SELECT Item FROM dbo.SplitStrings(#list1)),
l2 AS (SELECT Item FROM dbo.SplitStrings(#list2))
SELECT #output += N',' + Item
FROM ( (SELECT Item FROM l1 EXCEPT SELECT Item FROM l2)
UNION
(SELECT Item FROM l2 EXCEPT SELECT Item FROM l1)) AS x;
SELECT STUFF(#output, 1, 1, N'');
And probably a variety of other ways too. If order matters, it's going to be a little more complex, but still possible.

There is no easy way to accomplish this. To filter the values from a list you need to have them as rows. So you would end up with something like:
SELECT col FROM (
SELECT 'a' as col
UNION
SELECT 'b'
UNION
SELECT 'c') t
WHERE col NOT IN ('a', 'b')

How about:
with
list1(j) as (select 'a' union select 'b'),
list2(j) as (select 'b' union select 'c')
select coalesce(list1.j, list2.j)
from list1 full join list2
on list1.j = list2.j
where (list1.j is null or list2.j is null)

I think you'll have to insert the values into 2 variable tables.
DECLARE #Table1 TABLE (Value VARCHAR(1))
DECLARE #Table2 TABLE (Value VARCHAR(1))
INSERT INTO #Table1 (Value) VALUES ('a')
INSERT INTO #Table1 (Value) VALUES ('b')
INSERT INTO #Table2 (Value) VALUES ('b')
INSERT INTO #Table2 (Value) VALUES ('c')
Then perform some set operations on the 2 tables.
DECLARE #TableUnion TABLE (Value VARCHAR(1))
DECLARE #TableIntersection TABLE (Value VARCHAR(1))
DECLARE #TableExcept TABLE (Value VARCHAR(1))
INSERT INTO #TableUnion
SELECT * FROM
((SELECT * FROM #Table1)
UNION
(SELECT * FROM #Table2)) U
INSERT INTO #TableIntersection
SELECT * FROM
((SELECT * FROM #Table1)
INTERSECT
(SELECT * FROM #Table2)) I
INSERT INTO #TableExcept
SELECT * FROM
((SELECT * FROM #TableUnion)
EXCEPT
(SELECT * FROM #TableIntersection)) E
The result set of the final select statement will contain 'a' and 'c'. Which can be concatenated into a single string as follows.
DECLARE #ExceptString VARCHAR(3)
SELECT #ExceptString =
CASE
WHEN #ExceptString IS NULL THEN Value
ELSE #ExceptString + ',' + Value
END
FROM #TableExcept

Related

Find the missing group entry in sql server

I have the below table:
Declare #t table (Name nvarchar(80))
Insert into #t values
('ABC:CE')
,('ABC:LI')
,('ABC:XP')
,('ABD:CE')
,('ABD:LI')
,('ABE:LI')
,('ABE:XP')
,('ABF:XP')
I have 3 categories CE,LI,XP. I wanted to check which group is missing in the name.
I tried below Query:
select SUBSTRING(Name,1,charindex(':',Name)-1),count(1) as grplist from #t
group by SUBSTRING(Name,1,charindex(':',Name)-1)
having count(1) <3
Expected output:
Name
ABD:XP
ABE:CE
ABF:LI
ABF:CE
Can you guys help me where I am going wrong this:
Try this:
Declare #t table (Name nvarchar(80))
Insert into #t values
('ABC:CE')
,('ABC:LI')
,('ABC:XP')
,('ABD:CE')
,('ABD:LI')
,('ABE:LI')
,('ABE:XP')
,('ABF:XP');
WITH DataSource ([group], [category]) AS
(
SELECT SUBSTRING([Name], 1, CHARINDEX(':', [Name]) - 1)
,SUBSTRING([Name], CHARINDEX(':', [Name]) + 1, 100)
FROM #t
)
SELECT DISTINCT G.[group], C.[category]
FROM DataSource G
CROSS APPLY
(
SELECT DISTINCT [category]
FROM DataSource
) C
WHERE NOT EXISTS
(
SELECT 1
FROM DataSource DS
WHERE DS.[group] = G.[group]
AND DS.[category] = C.[category]
)
You want to select the values that are not in the table. For this to happen, you must first create these values. You do this with a cross join:
select n.name + ':' + c.category as missing
from (select distinct substring(name, 1, charindex(':', name) - 1) as name from #t) n
cross join (values ('CE'), ('LI'), ('XP')) c(category)
except
select name from #t
order by missing;
Demo: http://www.sqlfiddle.com/#!18/c99228/4
I see there's an answer here already but here's my take, looks similar mine may be less efficient but hey more the merrier :)
Declare #t table (Name nvarchar(80))
Insert into #t values
('ABC:CE')
,('ABC:LI')
,('ABC:XP')
,('ABD:CE')
,('ABD:LI')
,('ABE:LI')
,('ABE:XP')
,('ABF:XP')
Declare #c table (Cat nvarchar(2))
Insert into #c values
('CE')
,('LI')
,('XP')
SELECT
possible.prefix
,possible.Cat
,possible.prefix + ':' + possible.Cat
--,selector.Prefix
--,selector.suffix
FROM
(
select
SUBSTRING(Name,1,charindex(':',Name)-1) as prefix
,SUBSTRING(Name,charindex(':',Name)+1,2) as suffix
from #t
) as selector
right Join
(
SELECT DISTINCT
SUBSTRING(Name,1,charindex(':',Name)-1) as prefix
-- ,SUBSTRING(Name,charindex(':',Name)+1,2) as suffix
,Cat
FROM #t
cross join #c
) possible
ON selector.prefix = possible.prefix
and selector.suffix = possible.Cat
where selector.suffix is null

UPDATE set FROM select [duplicate]

How do I get:
id Name Value
1 A 4
1 B 8
2 C 9
to
id Column
1 A:4, B:8
2 C:9
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
If it is SQL Server 2017 or SQL Server Vnext, SQL Azure you can use STRING_AGG as below:
SELECT id, STRING_AGG(CONCAT(name, ':', [value]), ', ')
FROM #YourTable
GROUP BY id
using XML path will not perfectly concatenate as you might expect... it will replace "&" with "&" and will also mess with <" and ">
...maybe a few other things, not sure...but you can try this
I came across a workaround for this... you need to replace:
FOR XML PATH('')
)
with:
FOR XML PATH(''),TYPE
).value('(./text())[1]','VARCHAR(MAX)')
...or NVARCHAR(MAX) if thats what youre using.
why the hell doesn't SQL have a concatenate aggregate function? this is a PITA.
I ran into a couple of problems when I tried converting Kevin Fairchild's suggestion to work with strings containing spaces and special XML characters (&, <, >) which were encoded.
The final version of my code (which doesn't answer the original question but may be useful to someone) looks like this:
CREATE TABLE #YourTable ([ID] INT, [Name] VARCHAR(MAX), [Value] INT)
INSERT INTO #YourTable ([ID],[Name],[Value]) VALUES (1,'Oranges & Lemons',4)
INSERT INTO #YourTable ([ID],[Name],[Value]) VALUES (1,'1 < 2',8)
INSERT INTO #YourTable ([ID],[Name],[Value]) VALUES (2,'C',9)
SELECT [ID],
STUFF((
SELECT ', ' + CAST([Name] AS VARCHAR(MAX))
FROM #YourTable WHERE (ID = Results.ID)
FOR XML PATH(''),TYPE
/* Use .value to uncomment XML entities e.g. > < etc*/
).value('.','VARCHAR(MAX)')
,1,2,'') as NameValues
FROM #YourTable Results
GROUP BY ID
DROP TABLE #YourTable
Rather than using a space as a delimiter and replacing all the spaces with commas, it just pre-pends a comma and space to each value then uses STUFF to remove the first two characters.
The XML encoding is taken care of automatically by using the TYPE directive.
Another option using Sql Server 2005 and above
---- test data
declare #t table (OUTPUTID int, SCHME varchar(10), DESCR varchar(10))
insert #t select 1125439 ,'CKT','Approved'
insert #t select 1125439 ,'RENO','Approved'
insert #t select 1134691 ,'CKT','Approved'
insert #t select 1134691 ,'RENO','Approved'
insert #t select 1134691 ,'pn','Approved'
---- actual query
;with cte(outputid,combined,rn)
as
(
select outputid, SCHME + ' ('+DESCR+')', rn=ROW_NUMBER() over (PARTITION by outputid order by schme, descr)
from #t
)
,cte2(outputid,finalstatus,rn)
as
(
select OUTPUTID, convert(varchar(max),combined), 1 from cte where rn=1
union all
select cte2.outputid, convert(varchar(max),cte2.finalstatus+', '+cte.combined), cte2.rn+1
from cte2
inner join cte on cte.OUTPUTID = cte2.outputid and cte.rn=cte2.rn+1
)
select outputid, MAX(finalstatus) from cte2 group by outputid
Install the SQLCLR Aggregates from http://groupconcat.codeplex.com
Then you can write code like this to get the result you asked for:
CREATE TABLE foo
(
id INT,
name CHAR(1),
Value CHAR(1)
);
INSERT INTO dbo.foo
(id, name, Value)
VALUES (1, 'A', '4'),
(1, 'B', '8'),
(2, 'C', '9');
SELECT id,
dbo.GROUP_CONCAT(name + ':' + Value) AS [Column]
FROM dbo.foo
GROUP BY id;
Eight years later... Microsoft SQL Server vNext Database Engine has finally enhanced Transact-SQL to directly support grouped string concatenation. The Community Technical Preview version 1.0 added the STRING_AGG function and CTP 1.1 added the WITHIN GROUP clause for the STRING_AGG function.
Reference: https://msdn.microsoft.com/en-us/library/mt775028.aspx
SQL Server 2005 and later allow you to create your own custom aggregate functions, including for things like concatenation- see the sample at the bottom of the linked article.
This is just an addition to Kevin Fairchild's post (very clever by the way). I would have added it as a comment, but I don't have enough points yet :)
I was using this idea for a view I was working on, however the items I was concatinating contained spaces. So I modified the code slightly to not use spaces as delimiters.
Again thanks for the cool workaround Kevin!
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],
REPLACE(REPLACE(REPLACE(
(SELECT [Name] + ':' + CAST([Value] AS VARCHAR(MAX)) as A
FROM #YourTable
WHERE ( ID = Results.ID )
FOR XML PATH (''))
, '</A><A>', ', ')
,'<A>','')
,'</A>','') AS NameValues
FROM #YourTable Results
GROUP BY ID
DROP TABLE #YourTable
An example would be
In Oracle you can use LISTAGG aggregate function.
Original records
name type
------------
name1 type1
name2 type2
name2 type3
Sql
SELECT name, LISTAGG(type, '; ') WITHIN GROUP(ORDER BY name)
FROM table
GROUP BY name
Result in
name type
------------
name1 type1
name2 type2; type3
This kind of question is asked here very often, and the solution is going to depend a lot on the underlying requirements:
https://stackoverflow.com/search?q=sql+pivot
and
https://stackoverflow.com/search?q=sql+concatenate
Typically, there is no SQL-only way to do this without either dynamic sql, a user-defined function, or a cursor.
Just to add to what Cade said, this is usually a front-end display thing and should therefore be handled there. I know that sometimes it's easier to write something 100% in SQL for things like file export or other "SQL only" solutions, but most of the times this concatenation should be handled in your display layer.
Don't need a cursor... a while loop is sufficient.
------------------------------
-- Setup
------------------------------
DECLARE #Source TABLE
(
id int,
Name varchar(30),
Value int
)
DECLARE #Target TABLE
(
id int,
Result varchar(max)
)
INSERT INTO #Source(id, Name, Value) SELECT 1, 'A', 4
INSERT INTO #Source(id, Name, Value) SELECT 1, 'B', 8
INSERT INTO #Source(id, Name, Value) SELECT 2, 'C', 9
------------------------------
-- Technique
------------------------------
INSERT INTO #Target (id)
SELECT id
FROM #Source
GROUP BY id
DECLARE #id int, #Result varchar(max)
SET #id = (SELECT MIN(id) FROM #Target)
WHILE #id is not null
BEGIN
SET #Result = null
SELECT #Result =
CASE
WHEN #Result is null
THEN ''
ELSE #Result + ', '
END + s.Name + ':' + convert(varchar(30),s.Value)
FROM #Source s
WHERE id = #id
UPDATE #Target
SET Result = #Result
WHERE id = #id
SET #id = (SELECT MIN(id) FROM #Target WHERE #id < id)
END
SELECT *
FROM #Target
Let's get very simple:
SELECT stuff(
(
select ', ' + x from (SELECT 'xxx' x union select 'yyyy') tb
FOR XML PATH('')
)
, 1, 2, '')
Replace this line:
select ', ' + x from (SELECT 'xxx' x union select 'yyyy') tb
With your query.
You can improve performance significant the following way if group by contains mostly one item:
SELECT
[ID],
CASE WHEN MAX( [Name]) = MIN( [Name]) THEN
MAX( [Name]) NameValues
ELSE
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
END
FROM #YourTable Results
GROUP BY ID
didn't see any cross apply answers, also no need for xml extraction. Here is a slightly different version of what Kevin Fairchild wrote. It's faster and easier to use in more complex queries:
select T.ID
,MAX(X.cl) NameValues
from #YourTable T
CROSS APPLY
(select STUFF((
SELECT ', ' + [Name] + ':' + CAST([Value] AS VARCHAR(MAX))
FROM #YourTable
WHERE (ID = T.ID)
FOR XML PATH(''))
,1,2,'') [cl]) X
GROUP BY T.ID
Using the Stuff and for xml path operator to concatenate rows to string :Group By two columns -->
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 (1,'B',5)
INSERT INTO #YourTable ([ID],[Name],[Value]) VALUES (2,'C',9)
-- retrieve each unique id and name columns and concatonate the values into one column
SELECT
[ID],
STUFF((
SELECT ', ' + [Name] + ':' + CAST([Value] AS VARCHAR(MAX)) -- CONCATONATES EACH APPLICATION : VALUE SET
FROM #YourTable
WHERE (ID = Results.ID and Name = results.[name] )
FOR XML PATH(''),TYPE).value('(./text())[1]','VARCHAR(MAX)')
,1,2,'') AS NameValues
FROM #YourTable Results
GROUP BY ID
SELECT
[ID],[Name] , --these are acting as the group by clause
STUFF((
SELECT ', '+ CAST([Value] AS VARCHAR(MAX)) -- CONCATONATES THE VALUES FOR EACH ID NAME COMBINATION
FROM #YourTable
WHERE (ID = Results.ID and Name = results.[name] )
FOR XML PATH(''),TYPE).value('(./text())[1]','VARCHAR(MAX)')
,1,2,'') AS NameValues
FROM #YourTable Results
GROUP BY ID, name
DROP TABLE #YourTable
Using Replace Function and FOR JSON PATH
SELECT T3.DEPT, REPLACE(REPLACE(T3.ENAME,'{"ENAME":"',''),'"}','') AS ENAME_LIST
FROM (
SELECT DEPT, (SELECT ENAME AS [ENAME]
FROM EMPLOYEE T2
WHERE T2.DEPT=T1.DEPT
FOR JSON PATH,WITHOUT_ARRAY_WRAPPER) ENAME
FROM EMPLOYEE T1
GROUP BY DEPT) T3
For sample data and more ways click here
If you have clr enabled you could use the Group_Concat library from GitHub
Another example without the garbage: ",TYPE).value('(./text())[1]','VARCHAR(MAX)')"
WITH t AS (
SELECT 1 n, 1 g, 1 v
UNION ALL
SELECT 2 n, 1 g, 2 v
UNION ALL
SELECT 3 n, 2 g, 3 v
)
SELECT g
, STUFF (
(
SELECT ', ' + CAST(v AS VARCHAR(MAX))
FROM t sub_t
WHERE sub_t.g = main_t.g
FOR XML PATH('')
)
, 1, 2, ''
) cg
FROM t main_t
GROUP BY g
Input-output is
************************* -> *********************
* n * g * v * * g * cg *
* - * - * - * * - * - *
* 1 * 1 * 1 * * 1 * 1, 2 *
* 2 * 1 * 2 * * 2 * 3 *
* 3 * 2 * 3 * *********************
*************************
I used this approach which may be easier to grasp. Get a root element, then concat to choices any item with the same ID but not the 'official' name
Declare #IdxList as Table(id int, choices varchar(max),AisName varchar(255))
Insert into #IdxLIst(id,choices,AisName)
Select IdxId,''''+Max(Title)+'''',Max(Title) From [dbo].[dta_Alias]
where IdxId is not null group by IdxId
Update #IdxLIst
set choices=choices +','''+Title+''''
From #IdxLIst JOIN [dta_Alias] ON id=IdxId And Title <> AisName
where IdxId is not null
Select * from #IdxList where choices like '%,%'
For all my healthcare folks out there:
SELECT
s.NOTE_ID
,STUFF ((
SELECT
[note_text] + ' '
FROM
HNO_NOTE_TEXT s1
WHERE
(s1.NOTE_ID = s.NOTE_ID)
ORDER BY [line] ASC
FOR XML PATH(''),TYPE).value('(./text())[1]','VARCHAR(MAX)')
,
1,
2,
'') AS NOTE_TEXT_CONCATINATED
FROM
HNO_NOTE_TEXT s
GROUP BY NOTE_ID

Stored Procedure to Insert comma seperated values as multiple records

Please help me in creating a stored procedure which accepts comma separated values and inserts as multiple rows.
So one parameter #Name will contain values A,B,C and the other parameter #Id will contain values as 1,2,3
The table values after insertion should be as below:
Name Id
------------
A 1
A 2
A 3
B 1
B 2
B 3
C 1
C 2
C 3
How can I write a stored procedure that can insert the comma-separated values as shown above. Also, If the table already consists of a Name,id pair for example, if A,2 is already there in the table, then it should not insert.
I am using SQL Server 2005. Thanks in advance.
Something like this?
DECLARE #var1 VARCHAR(100)='A,B,C';
DECLARE #var2 VARCHAR(100)='1,2,3';
WITH rep1(name, delim) AS
(
SELECT #var1 name, ',' delim
UNION ALL
SELECT LEFT(name, CHARINDEX(delim, name, 1) - 1) name, delim
FROM rep1
WHERE (CHARINDEX(delim, name, 1) > 0)
UNION ALL
SELECT RIGHT(name, LEN(name) - CHARINDEX(delim, name, 1)) name, delim
FROM rep1
WHERE (CHARINDEX(delim, name, 1) > 0)
)
,rep2(id, delim) AS
(
SELECT #var2 id, ',' delim
UNION ALL
SELECT LEFT(id, CHARINDEX(delim, id, 1) - 1) id, delim
FROM rep2
WHERE (CHARINDEX(delim, id, 1) > 0)
UNION ALL
SELECT RIGHT(id, LEN(id) - CHARINDEX(delim, id, 1)) id, delim
FROM rep2
WHERE (CHARINDEX(delim, id, 1) > 0)
)
INSERT #table
(Name
,ID)
SELECT
r1.name
,r2.id
FROM rep1 r1
CROSS JOIN rep2 r2
LEFT JOIN #table t
ON r2.id=t.id
AND t.name=r1.name
WHERE (CHARINDEX(r1.delim, r1.name, 1) = 0)
AND (CHARINDEX(r2.delim, r2.id, 1) = 0)
AND t.name IS NULL
ORDER BY r1.name
,r2.id
OPTION (MAXRECURSION 0);
Here we are sepearting Comma Seperated into rows
IF OBJECT_ID('tempdb..#Temp') IS NOT NULL
DROP TABLE #Temp
IF OBJECT_ID('tempdb..#NewTemp') IS NOT NULL
DROP TABLE #NewTemp
Declare #Testdata table ( name Varchar(max), Data varchar(max))
insert #Testdata select 'A', '1,2,3'
insert #Testdata select 'B', '1,2,3'
insert #Testdata select 'C', '1,2'
insert #Testdata select 'A', '1,2,3,4'
insert #Testdata select 'C', '1,2,3,4,5'
;with tmp(name, DataItem, Data) as (
select name, LEFT(Data, CHARINDEX(',',Data+',')-1),
STUFF(Data, 1, CHARINDEX(',',Data+','), '')
from #Testdata
union all
select name, LEFT(Data, CHARINDEX(',',Data+',')-1),
STUFF(Data, 1, CHARINDEX(',',Data+','), '')
from tmp
where Data > ''
)
Then Inserting into Temp Table
select DISTINCT name, DataItem INTO #Temp
from tmp WHERE EXISTS (Select DISTINCT name,DataItem from tmp)
order by name
Here we are controlling entry of Duplicates we can observe combination won't repeat like (A,1),(B,1)Even though they are multiple
CREATE TABLE #NewTemp(name Varchar(max), Data varchar(max))
INSERT INTO #NewTemp (name,Data)
Select name,DataItem from #Temp
Select * FROM #NewTemp
You can go and create one user defined functions for splitting the comma separated values into rows as below
How this function will work and more on it can be found here
CREATE FUNCTION dbo.Split
(
#RowData nvarchar(2000),
#SplitOn nvarchar(5)
)
RETURNS #RtnValue table
(
Id int identity(1,1),
Data nvarchar(100)
)
AS
BEGIN
Declare #Cnt int
Set #Cnt = 1
DECLARE #index INT
SET #index = Charindex(#SplitOn,#RowData)
While (#index>0)
Begin
Insert Into #RtnValue (data)
Select
Data = ltrim(rtrim(Substring(#RowData,1,#index-1)))
Set #RowData = Substring(#RowData,#index+1,len(#RowData))
Set #Cnt = #Cnt + 1
SET #index = Charindex(#SplitOn,#RowData)
End
Insert Into #RtnValue (data)
Select Data = ltrim(rtrim(#RowData))
Return
END
Once this function is created, you can use it for your requirement as below
declare #Name VARCHAR(30)
declare #Id VARCHAR(30)
SET #Name = 'A,B,C'
SET #Id = '1,2,3'
select A.Data,B.Data FROM dbo.Split(#name,',') A ,dbo.Split(#id,',') B

How to make a list of T-SQL results with comma's between them?

Suppose we have a simple query like this:
SELECT x
FROM t
WHERE t.y = z
If we have one record in the result set, I want to set variable #v to that one value. If we have two or more records, I'd like the results to be separated by a comma and a space. What is the best way to write this T-SQL code?
Example:
result set of 1 record:
Value1
result set of 2 records:
Value1, Value2
result set of 3 records:
Value1, Value2, Value3
this will give you the list of values in a comma separated list
create table #temp
(
y int,
x varchar(10)
)
insert into #temp values (1, 'value 1')
insert into #temp values (1, 'value 2')
insert into #temp values (1, 'value 3')
insert into #temp values (1, 'value 4')
DECLARE #listStr varchar(255)
SELECT #listStr = COALESCE(#listStr+', ', '') + x
FROM #temp
WHERE #temp.y = 1
SELECT #listStr as List
drop table #temp
You can use XML to do that:
DECLARE #V VarChar(4000);
SELECT #V = CONVERT(VarChar(4000), (
SELECT x + ', '
FROM t
WHERE t.y = z
FOR XML PATH('')
));
-- To remove the final , in the list:
SELECT #V = LEFT(#V, LEN(#V) - 2);
SELECT #V;
For other options check out Concatenating Row Values in SQL.
Since it's SQL Server 2008, you can use FOR XML:
SELECT SUBSTRING(
(SELECT ',' + t.x
FROM t
WHERE t.y = z
FOR XML PATH('')),
2,
200000) AS CSV
FOR XML PATH('') selects the table as XML, but with a blank path.
The SUBSTRING(select, 2, 2000000) removes the leading ', '
You could use a recursive CTE for this:
CREATE TABLE #TableWithId (Id INT IDENTITY(1,1), x VARCHAR)
INSERT INTO #TableWithId
SELECT x
FROM t
WHERE t.y = z
WITH Commas(ID, Flattened)
AS
(
-- Anchor member definition
SELECT ID, x AS Flattened
FROM #TableWithId
WHERE ID = 1
UNION ALL
-- Recursive member definition
SELECT #TableWithId.Id, Flattened + ',' + x
FROM #TableWithId
INNER JOIN Commas
ON #TableWithId.Id + 1 = Commas.Id
)
-- Statement that executes the CTE
SELECT TOP 1 Flattened
FROM Commas
ORDER BY id;
GO
How about something like this???
DECLARE #x AS VARCHAR(2000)
SET #x = ''
SELECT #x = #x + RTRIM(x) + ','
FROM t
SELECT #x = SUBSTRING(#x, 1, LEN(#x) - 1)
PRINT #x

How to use GROUP BY to concatenate strings in SQL Server?

How do I get:
id Name Value
1 A 4
1 B 8
2 C 9
to
id Column
1 A:4, B:8
2 C:9
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
If it is SQL Server 2017 or SQL Server Vnext, SQL Azure you can use STRING_AGG as below:
SELECT id, STRING_AGG(CONCAT(name, ':', [value]), ', ')
FROM #YourTable
GROUP BY id
using XML path will not perfectly concatenate as you might expect... it will replace "&" with "&" and will also mess with <" and ">
...maybe a few other things, not sure...but you can try this
I came across a workaround for this... you need to replace:
FOR XML PATH('')
)
with:
FOR XML PATH(''),TYPE
).value('(./text())[1]','VARCHAR(MAX)')
...or NVARCHAR(MAX) if thats what youre using.
why the hell doesn't SQL have a concatenate aggregate function? this is a PITA.
I ran into a couple of problems when I tried converting Kevin Fairchild's suggestion to work with strings containing spaces and special XML characters (&, <, >) which were encoded.
The final version of my code (which doesn't answer the original question but may be useful to someone) looks like this:
CREATE TABLE #YourTable ([ID] INT, [Name] VARCHAR(MAX), [Value] INT)
INSERT INTO #YourTable ([ID],[Name],[Value]) VALUES (1,'Oranges & Lemons',4)
INSERT INTO #YourTable ([ID],[Name],[Value]) VALUES (1,'1 < 2',8)
INSERT INTO #YourTable ([ID],[Name],[Value]) VALUES (2,'C',9)
SELECT [ID],
STUFF((
SELECT ', ' + CAST([Name] AS VARCHAR(MAX))
FROM #YourTable WHERE (ID = Results.ID)
FOR XML PATH(''),TYPE
/* Use .value to uncomment XML entities e.g. > < etc*/
).value('.','VARCHAR(MAX)')
,1,2,'') as NameValues
FROM #YourTable Results
GROUP BY ID
DROP TABLE #YourTable
Rather than using a space as a delimiter and replacing all the spaces with commas, it just pre-pends a comma and space to each value then uses STUFF to remove the first two characters.
The XML encoding is taken care of automatically by using the TYPE directive.
Another option using Sql Server 2005 and above
---- test data
declare #t table (OUTPUTID int, SCHME varchar(10), DESCR varchar(10))
insert #t select 1125439 ,'CKT','Approved'
insert #t select 1125439 ,'RENO','Approved'
insert #t select 1134691 ,'CKT','Approved'
insert #t select 1134691 ,'RENO','Approved'
insert #t select 1134691 ,'pn','Approved'
---- actual query
;with cte(outputid,combined,rn)
as
(
select outputid, SCHME + ' ('+DESCR+')', rn=ROW_NUMBER() over (PARTITION by outputid order by schme, descr)
from #t
)
,cte2(outputid,finalstatus,rn)
as
(
select OUTPUTID, convert(varchar(max),combined), 1 from cte where rn=1
union all
select cte2.outputid, convert(varchar(max),cte2.finalstatus+', '+cte.combined), cte2.rn+1
from cte2
inner join cte on cte.OUTPUTID = cte2.outputid and cte.rn=cte2.rn+1
)
select outputid, MAX(finalstatus) from cte2 group by outputid
Install the SQLCLR Aggregates from http://groupconcat.codeplex.com
Then you can write code like this to get the result you asked for:
CREATE TABLE foo
(
id INT,
name CHAR(1),
Value CHAR(1)
);
INSERT INTO dbo.foo
(id, name, Value)
VALUES (1, 'A', '4'),
(1, 'B', '8'),
(2, 'C', '9');
SELECT id,
dbo.GROUP_CONCAT(name + ':' + Value) AS [Column]
FROM dbo.foo
GROUP BY id;
Eight years later... Microsoft SQL Server vNext Database Engine has finally enhanced Transact-SQL to directly support grouped string concatenation. The Community Technical Preview version 1.0 added the STRING_AGG function and CTP 1.1 added the WITHIN GROUP clause for the STRING_AGG function.
Reference: https://msdn.microsoft.com/en-us/library/mt775028.aspx
SQL Server 2005 and later allow you to create your own custom aggregate functions, including for things like concatenation- see the sample at the bottom of the linked article.
This is just an addition to Kevin Fairchild's post (very clever by the way). I would have added it as a comment, but I don't have enough points yet :)
I was using this idea for a view I was working on, however the items I was concatinating contained spaces. So I modified the code slightly to not use spaces as delimiters.
Again thanks for the cool workaround Kevin!
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],
REPLACE(REPLACE(REPLACE(
(SELECT [Name] + ':' + CAST([Value] AS VARCHAR(MAX)) as A
FROM #YourTable
WHERE ( ID = Results.ID )
FOR XML PATH (''))
, '</A><A>', ', ')
,'<A>','')
,'</A>','') AS NameValues
FROM #YourTable Results
GROUP BY ID
DROP TABLE #YourTable
An example would be
In Oracle you can use LISTAGG aggregate function.
Original records
name type
------------
name1 type1
name2 type2
name2 type3
Sql
SELECT name, LISTAGG(type, '; ') WITHIN GROUP(ORDER BY name)
FROM table
GROUP BY name
Result in
name type
------------
name1 type1
name2 type2; type3
This kind of question is asked here very often, and the solution is going to depend a lot on the underlying requirements:
https://stackoverflow.com/search?q=sql+pivot
and
https://stackoverflow.com/search?q=sql+concatenate
Typically, there is no SQL-only way to do this without either dynamic sql, a user-defined function, or a cursor.
Just to add to what Cade said, this is usually a front-end display thing and should therefore be handled there. I know that sometimes it's easier to write something 100% in SQL for things like file export or other "SQL only" solutions, but most of the times this concatenation should be handled in your display layer.
Don't need a cursor... a while loop is sufficient.
------------------------------
-- Setup
------------------------------
DECLARE #Source TABLE
(
id int,
Name varchar(30),
Value int
)
DECLARE #Target TABLE
(
id int,
Result varchar(max)
)
INSERT INTO #Source(id, Name, Value) SELECT 1, 'A', 4
INSERT INTO #Source(id, Name, Value) SELECT 1, 'B', 8
INSERT INTO #Source(id, Name, Value) SELECT 2, 'C', 9
------------------------------
-- Technique
------------------------------
INSERT INTO #Target (id)
SELECT id
FROM #Source
GROUP BY id
DECLARE #id int, #Result varchar(max)
SET #id = (SELECT MIN(id) FROM #Target)
WHILE #id is not null
BEGIN
SET #Result = null
SELECT #Result =
CASE
WHEN #Result is null
THEN ''
ELSE #Result + ', '
END + s.Name + ':' + convert(varchar(30),s.Value)
FROM #Source s
WHERE id = #id
UPDATE #Target
SET Result = #Result
WHERE id = #id
SET #id = (SELECT MIN(id) FROM #Target WHERE #id < id)
END
SELECT *
FROM #Target
Let's get very simple:
SELECT stuff(
(
select ', ' + x from (SELECT 'xxx' x union select 'yyyy') tb
FOR XML PATH('')
)
, 1, 2, '')
Replace this line:
select ', ' + x from (SELECT 'xxx' x union select 'yyyy') tb
With your query.
You can improve performance significant the following way if group by contains mostly one item:
SELECT
[ID],
CASE WHEN MAX( [Name]) = MIN( [Name]) THEN
MAX( [Name]) NameValues
ELSE
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
END
FROM #YourTable Results
GROUP BY ID
didn't see any cross apply answers, also no need for xml extraction. Here is a slightly different version of what Kevin Fairchild wrote. It's faster and easier to use in more complex queries:
select T.ID
,MAX(X.cl) NameValues
from #YourTable T
CROSS APPLY
(select STUFF((
SELECT ', ' + [Name] + ':' + CAST([Value] AS VARCHAR(MAX))
FROM #YourTable
WHERE (ID = T.ID)
FOR XML PATH(''))
,1,2,'') [cl]) X
GROUP BY T.ID
Using the Stuff and for xml path operator to concatenate rows to string :Group By two columns -->
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 (1,'B',5)
INSERT INTO #YourTable ([ID],[Name],[Value]) VALUES (2,'C',9)
-- retrieve each unique id and name columns and concatonate the values into one column
SELECT
[ID],
STUFF((
SELECT ', ' + [Name] + ':' + CAST([Value] AS VARCHAR(MAX)) -- CONCATONATES EACH APPLICATION : VALUE SET
FROM #YourTable
WHERE (ID = Results.ID and Name = results.[name] )
FOR XML PATH(''),TYPE).value('(./text())[1]','VARCHAR(MAX)')
,1,2,'') AS NameValues
FROM #YourTable Results
GROUP BY ID
SELECT
[ID],[Name] , --these are acting as the group by clause
STUFF((
SELECT ', '+ CAST([Value] AS VARCHAR(MAX)) -- CONCATONATES THE VALUES FOR EACH ID NAME COMBINATION
FROM #YourTable
WHERE (ID = Results.ID and Name = results.[name] )
FOR XML PATH(''),TYPE).value('(./text())[1]','VARCHAR(MAX)')
,1,2,'') AS NameValues
FROM #YourTable Results
GROUP BY ID, name
DROP TABLE #YourTable
Using Replace Function and FOR JSON PATH
SELECT T3.DEPT, REPLACE(REPLACE(T3.ENAME,'{"ENAME":"',''),'"}','') AS ENAME_LIST
FROM (
SELECT DEPT, (SELECT ENAME AS [ENAME]
FROM EMPLOYEE T2
WHERE T2.DEPT=T1.DEPT
FOR JSON PATH,WITHOUT_ARRAY_WRAPPER) ENAME
FROM EMPLOYEE T1
GROUP BY DEPT) T3
For sample data and more ways click here
If you have clr enabled you could use the Group_Concat library from GitHub
Another example without the garbage: ",TYPE).value('(./text())[1]','VARCHAR(MAX)')"
WITH t AS (
SELECT 1 n, 1 g, 1 v
UNION ALL
SELECT 2 n, 1 g, 2 v
UNION ALL
SELECT 3 n, 2 g, 3 v
)
SELECT g
, STUFF (
(
SELECT ', ' + CAST(v AS VARCHAR(MAX))
FROM t sub_t
WHERE sub_t.g = main_t.g
FOR XML PATH('')
)
, 1, 2, ''
) cg
FROM t main_t
GROUP BY g
Input-output is
************************* -> *********************
* n * g * v * * g * cg *
* - * - * - * * - * - *
* 1 * 1 * 1 * * 1 * 1, 2 *
* 2 * 1 * 2 * * 2 * 3 *
* 3 * 2 * 3 * *********************
*************************
I used this approach which may be easier to grasp. Get a root element, then concat to choices any item with the same ID but not the 'official' name
Declare #IdxList as Table(id int, choices varchar(max),AisName varchar(255))
Insert into #IdxLIst(id,choices,AisName)
Select IdxId,''''+Max(Title)+'''',Max(Title) From [dbo].[dta_Alias]
where IdxId is not null group by IdxId
Update #IdxLIst
set choices=choices +','''+Title+''''
From #IdxLIst JOIN [dta_Alias] ON id=IdxId And Title <> AisName
where IdxId is not null
Select * from #IdxList where choices like '%,%'
For all my healthcare folks out there:
SELECT
s.NOTE_ID
,STUFF ((
SELECT
[note_text] + ' '
FROM
HNO_NOTE_TEXT s1
WHERE
(s1.NOTE_ID = s.NOTE_ID)
ORDER BY [line] ASC
FOR XML PATH(''),TYPE).value('(./text())[1]','VARCHAR(MAX)')
,
1,
2,
'') AS NOTE_TEXT_CONCATINATED
FROM
HNO_NOTE_TEXT s
GROUP BY NOTE_ID