Concatenate several columns as comma-separated string - sql

The following is a starting point to concatenate several columns to one string where the values are comma separated. If the column entry is empty or NULL no comma should be used:
IF OBJECT_ID(N'tempdb..#Temp') IS NOT NULL
DROP TABLE #Temp;
CREATE TABLE #Temp
(
Id INT,
Name1 NVARCHAR(10) ,
Name2 NVARCHAR(10) ,
Name3 NVARCHAR(10)
);
INSERT INTO #Temp
SELECT 1,
N'Name1' ,
NULL ,
N'Name3'
UNION
SELECT 2,
N'Name1' ,
N'Name2' ,
N'Name3'
UNION
SELECT 3,
NULL ,
NULL ,
N'Name3'
UNION
SELECT
4,
N'' ,
N'' ,
N'Name3';
SELECT Id, STUFF(COALESCE(N',' + Name1, N'') + COALESCE(N',' + Name2, N'')
+ COALESCE(N',' + Name3, N''), 1, 1, '') AS ConcateStuff
FROM #Temp;
The current results are as follows:
Id ConcateStuff
1 Name1,Name3
2 Name1,Name2,Name3
3 Name3
4 ,,Name3
Everything work fine for NULL entries but not for empty entries. The last row's result should just be:
Name3
Is there a simple way to get this to work without using complex nested case statements (ultimately I have to concatenate more than 3 columns).

By using NULLIF you can achieve it.
SELECT Id, STUFF(COALESCE(N',' + NULLIF(Name1, ''), N'') + COALESCE(N',' + NULLIF(Name2, ''), N'')
+ COALESCE(N',' + NULLIF(Name3, ''), N''), 1, 1, '') AS ConcateStuff
FROM #Temp;
Result
Id ConcateStuff
-----------------
1 Name1,Name3
2 Name1,Name2,Name3
3 Name3
4 Name3

Related

One column into multiple columns by type, plus concatenating multiples

I have a table like this:
Customer
Number
Type
1
234.567.8910
1
1
234.234.2345
2
2
234.567.5555
1
2
151.513.5464
1
3
845.846.8486
3
I am trying to include this information with information from another table (say... address), but in separate columns by type, and I want to concatenate values that are of the same type so that the return looks like this:
Customer
Cell
Home
Work
1
234.567.8910
234.234.2345
NULL
2
234.567.5555 & 151.513.5464
NULL
NULL
3
NULL
NULL
845.846.8486
When I use the STRING_AGG function, it appends the value for each line of that customer - even if it would be null when the function isn't applied, so the customer 1 has both the cell and home numbers repeated twice.
The only workaround I can find is to select each column in a subquery and join them together. Is that the only option?
Try with FOR XML and PATH.
Query
select [Customer],
stuff((
select distinct ',' + [Number]
from [your_table_name]
where [Customer] = a.[Customer]
and [Type] = 1
for xml path (''))
, 1, 1, '') as [Cell],
stuff((
select distinct ',' + [Number]
from [your_table_name]
where [Customer] = a.[Customer]
and [Type] = 2
for xml path (''))
, 1, 1, '') as [Home],
stuff((
select distinct ',' + [Number]
from [your_table_name]
where [Customer] = a.[Customer]
and [Type] = 3
for xml path (''))
, 1, 1, '') as [Work]
from [your_table_name] as a
group by [Customer];
You need conditional aggregation:
SELECT
Customer,
STRING_AGG(CASE WHEN Type = 1 THEN Number END, ' & ') Cell,
STRING_AGG(CASE WHEN Type = 2 THEN Number END, ' & ') Home,
STRING_AGG(CASE WHEN Type = 3 THEN Number END, ' & ') Work
From table

Replace a value in a comma separated string in SQL Server database

I'm using a SQL Server 2014 database and I have a column that contains comma-separated values such as:
1,2,3
4,5
3,6,2
4,2,8
2
What I need to do is to replace the number 2 with the number 3 (string values) in each record and not duplicate the 3 if possible. I'm not sure that this can be done unless I use a function and I'm not sure how to do it in a function.
I think I need to split a string into a table and then loop the values and put it back together with the new value. Is there an easier way? Any help is appreciated.
Expect output would therefore be:
1,3
4,5
3,6
4,3,8
3
While it is possible, I do not encourage this:
DECLARE #old AS VARCHAR(3) = '2';
DECLARE #new AS VARCHAR(3) = '3';
WITH opdata(csv) AS (
SELECT '1,22,3' UNION ALL
SELECT '1,2,3' UNION ALL
SELECT '4,5' UNION ALL
SELECT '3,6,2' UNION ALL
SELECT '4,2,8' UNION ALL
SELECT '2'
), cte1 AS (
SELECT
csv,
CASE
WHEN ',' + csv + ',' LIKE '%,' + #old + ',%' THEN
CASE
WHEN ',' + csv + ',' LIKE '%,' + #new + ',%' THEN REPLACE(',' + csv + ',', ',' + #old + ',', ',') -- new already present so just delete old
ELSE REPLACE(',' + csv + ',', ',' + #old + ',', ',' + #new + ',') -- replace old with new
END
ELSE ',' + csv + ','
END AS tmp
FROM opdata
)
SELECT
csv,
STUFF(STUFF(tmp, 1, 1, ''), LEN(tmp) - 1, 1, '') AS res
FROM cte1
Result:
csv | res
-------+-------
1,22,3 | 1,22,3
1,2,3 | 1,3
4,5 | 4,5
3,6,2 | 3,6
4,2,8 | 4,3,8
2 | 3
Note that the plethora of ',...,' is required to avoid replacing values such as 22. If you are using SQL Server 2017 you can ditch the extra CTE + STUFF and use TRIM(',' FROM ...).
This isn't going to perform particularly well, however:
WITH CTE AS (
SELECT *
FROM (VALUES ('1,2,3'),
('4,5'),
('3,6,2'),
('4,2,8'),
('2')) V(DS))
SELECT CASE WHEN DS LIKE '%3%' THEN REPLACE(REPLACE(DS,'2,',''),',2','')
WHEN DS LIKE '%2%' THEN REPLACE(DS,'2','3')
ELSE DS
END
FROM CTE;
May be you are looking something like this.
SELECT REPLACE(CASE WHEN CHARINDEX('2', '1,2,3') > 0 THEN REPLACE('1,2,3', '2','') ELSE REPLACE('1,2,3', '2','3') END, ',,',',')
I have taken a hard coded value for demonstration. You can replace'1,2,3' with column name in the table.
To update:
DECLARE #was nvarchar(2) = 2,
#willbe nvarchar(2) = 3,
#d nvarchar(1) = ','
UPDATE strings
SET string = REVERSE(
STUFF(
REVERSE(
STUFF(
CASE WHEN CHARINDEX(#d+#willbe+#d,#d+string+#d) > 0
THEN REPLACE(#d+string+#d,#d+#was+#d,#d)
ELSE REPLACE(#d+string+#d,#d+#was+#d,#d+#willbe+#d)
END,1,1,'')
),1,1,''))
Output:
1,3
4,5
3,6
4,3,8
3

Have all Records in one Field

how is possible to have all records of one field into one field
Id, No , FDevice
1 , 1 , 'A'
2 , 1 , 'B'
3 , 1 , 'C'
4 , 2 , 'D'
5 , 2 , 'E'
I want to have
No , FDevice
1 , A-B-C
2 , D-E
Thank you for your help
use STUFF() - which inserts a string into another string.
SELECT
[No],
STUFF(
(SELECT '-' + [FDevice]
FROM TableName
WHERE [No] = a.[No]
FOR XML PATH (''))
, 1, 1, '') AS FDevice
FROM TableName AS a
GROUP BY [No]
SQLFiddle Demo
There're a well-known solution for aggregate concatenation in SQL Server, using select ... for xml path(''), but I have to say that many people using it incorrectly. Correct way to do this would be
select
a.[No],
stuff(
(
select '-' + t.[FDevice]
from TableName as t
where t.[No] = a.[No]
for xml path(''), type
).value('.', 'nvarchar(max)')
, 1, 1, '') as FDevice
from (select distinct [No] from TableName) as a;
sql fiddle demo
The main part is to use xml type inside the query and then to convert it into varchar using value function, otherwise you can end up with incorrectly converted special chars like '>', '<', '&' and so on. SQLfiddle somehow doesn't show the difference, but here's a script which can show you what can happen if you don't use xml type:
declare #TableName table
([Id] int, [No] int, [FDevice] varchar(3))
;
INSERT INTO #TableName
([Id], [No], [FDevice])
VALUES
(1, 1, 'A<'),
(2, 1, 'B'),
(3, 1, '&C'),
(4, 2, 'D'),
(5, 2, 'E')
;
SELECT
[No],
STUFF(
(SELECT '-' + [FDevice]
FROM #TableName
WHERE [No] = a.[No]
FOR XML PATH (''))
, 1, 1, '') AS FDevice
FROM #TableName AS a
GROUP BY [No];
outputs
No FDevice
--------------------
1 A<-B-&C
2 D-E
select
a.[No],
stuff(
(
select '-' + t.[FDevice]
from #TableName as t
where t.[No] = a.[No]
for xml path(''), type
).value('.', 'nvarchar(max)')
, 1, 1, '') as FDevice
from (select distinct [No] from #TableName) as a;
outputs
No FDevice
--------------------
1 A<-B-&C
2 D-E

JOIN three tables and aggregate data from multiple rows for every DISTINCT row in separate column

JOIN three tables and aggregate data from multiple rows for every DISTINCT row in separate column
i have a table where one item is mapped with multiple items.
Key 1 | Key 2
1 2
1 5
1 6
1 4
1 8
I have another table like this
Key 1 | ShortKey1Desc
1 'Desc short'
i have one more table where i have data like this
Key 1 | Description
1 'Desc a'
1 'Desc c'
1 'Desc aa'
1 'Desc tt'
i need to write a sql query for my view where table would be generated like this
Key 1 | AllKeys2ForKey1 | AllDescriptionsForKey1 | ShortKey1Desc
1 | 2;5;6;4;8 | Desc a; Desc c; Desc aa; Desc tt | Desc short
Key 1 is a string type field so i need to join them table using that string key
what i'm trying is to create a view for comfortable data access. need to create a query what will not take ages. i already tried to do it with Functions but it takes ages for load.
any help on this one would be highly appreciated. thanks a lot
Assuming that you are unable to change the data structures to make a more efficient query, this will work:
--Populate sample data
SELECT 1 as key1, 2 as key2 INTO #tbl1
UNION ALL SELECT 1, 5
UNION ALL SELECT 1, 6
UNION ALL SELECT 1, 4
UNION ALL SELECT 1, 8
SELECT 1 as key1, 'Desc short' as shortkeydesc INTO #tbl2
SELECT 1 as key1, 'Desc a' as [description] INTO #tbl3
UNION ALL SELECT 1, 'Desc c'
UNION ALL SELECT 1, 'Desc aa'
UNION ALL SELECT 1, 'Desc tt'
--Combine data into semi-colon separated lists
SELECT
key1
,STUFF(
(
SELECT
';' + CAST(t2.key2 AS VARCHAR(10))
FROM #tbl1 t2
WHERE t2.key1 = tbl1.key1
FOR XML PATH('')
), 1, 1, ''
)
,STUFF(
(
SELECT
';' + tbl2.shortkeydesc
FROM #tbl2 tbl2
WHERE tbl2.key1 = tbl1.key1
FOR XML PATH('')
), 1, 1, ''
)
,STUFF(
(
SELECT
';' + tbl3.[description]
FROM #tbl3 tbl3
WHERE tbl3.key1 = tbl1.key1
FOR XML PATH('')
), 1, 1, ''
)
FROM #tbl1 tbl1
GROUP BY tbl1.key1
to convert rows into one single result you will need to save values in a variable, below is sample code just to give you an idea
Declare #AllKeys2ForKey1 varchar(50)
set #AllKeys2ForKey1 = ''
SELECT #AllKeys2ForKey1 = #AllKeys2ForKey1 + cast([Key 2] as varchar(3)) + ','
FROM [AllKeys2ForKey1Table] where [KEY 1] = 1
Declare #AllDescriptionsForKey1 varchar(100)
set #AllDescriptionsForKey1 = ''
SELECT #AllKeys2ForKey1 = #AllKeys2ForKey1 + [Description] + ','
FROM [AllDescriptionsForKey1Table] where [KEY 1] = 1
Declare #ShortKey1Desc varchar(100)
set #ShortKey1Desc = ''
SELECT #ShortKey1Desc = #ShortKey1Desc + [ShortKey1Desc] + ','
FROM [ShortKey1DescTable] where [KEY 1] = 1
Select [KEY 1],
substring(#AllKeys2ForKey1,1,len(#AllKeys2ForKey1) - 1) as 'AllKeys2ForKey1 ',
substring(#AllDescriptionsForKey1,1,len(#AllDescriptionsForKey1) - 1) as 'AllDescriptionsForKey1',
substring(#ShortKey1Desc,1,len(#ShortKey1Desc) - 1) as 'ShortKey1Desc'
from Table where [KEY 1]= 1
You Must Write CLR Aggregate Function for Solving This Question.
for write CLR Aggregate Function :
1: Run Microsoft Visual Stadio
2: Create New Project
3: then Select Data Project
4: CLR Aggregate Function
After Create Your Aggregate Function Create Your Query Such as Below
Select A.Key1, OwnAggregateFn(B.Description), OwnAggregateFn(C.Key2), ...
From A
inner join B ON B.Key1 = A.Key1
inner join C ON C.Key1 = A.Key1
...
Group By A.Key1

SQL 'STUFF' statement advice

I needed to select all rows from a table matching a requirement, but to put these into one column seperated by a space/comma. Right now, I have this amongst my query:
((SELECT ' ' + ID
FROM Items
WHERE (Consignment = Consignments.ConsignmentNo) FOR XML PATH('')), 1, 1, '') AS Items
Problem is, it doesn't seperate the results by anything, so it all looks like one result. Where am I going wrong?
Thanks
I am guessing your ID column is a numeric column rather than a varchar. Try casting ID as a varchar or nvarchar. Your syntax looks fine, it should seperate by a space.
EX:
Without the cast:
select 1 as Item
into #test
union select 2
union select 3
union select 4
union select 5
select STUFF((SELECT ' ' + Item
FROM #test
FOR XML PATH('')), 1, 1, '')
Output:
2345
With the cast:
select 1 as Item
into #test
union select 2
union select 3
union select 4
union select 5
select STUFF((SELECT ' ' + cast(Item as nvarchar)
FROM #test
FOR XML PATH('')), 1, 1, '')
Output:
1 2 3 4 5