SQL Server multiple REPLACE with #temp table - sql

I am trying to REPLACE multiple characters in SQL Server query and want to achieve this via #temp table instead of nested REPLACE. I got SQL code below and want to achieve result like
ABC DEF GHI
IF OBJECT_ID('tempdb..#temp') IS NOT NULL DROP TABLE #temp
IF OBJECT_ID('tempdb..#temp2') IS NOT NULL DROP TABLE #temp2
CREATE TABLE #temp
(
STRING_TO_REPLACE NVARCHAR(5)
)
INSERT INTO #temp (STRING_TO_REPLACE)
VALUES (' ')
,('/')
,('_')
CREATE TABLE #temp2
(
STRING_NAME NVARCHAR(5)
)
INSERT INTO #temp2 (STRING_NAME)
VALUES ('A BC')
,('D/EF')
,('G_HI')
SELECT REPLACE(t2.STRING_NAME,(SELECT t1.STRING_TO_REPLACE
FROM #temp t1),'')
FROM #temp2 t2
IF OBJECT_ID('tempdb..#temp') IS NOT NULL DROP TABLE #temp
IF OBJECT_ID('tempdb..#temp2') IS NOT NULL DROP TABLE #temp2
I can achieve result with nested replace
SELECT REPLACE(REPLACE(REPLACE(t2.STRING_NAME,'_',''),'/',''),' ','') FROM #temp2 t2
but would really like to do this via #temp table. Please can someone help me on this.
When I try to run my first code I get the following error
Msg 512, Level 16, State 1, Line 23 Subquery returned more than 1
value. This is not permitted when the subquery follows =, !=, <, <= ,
, >= or when the subquery is used as an expression.

Here is one way using CROSS APPLY
SELECT result
FROM #temp2 t2
CROSS apply (SELECT Replace(string_name, t1.string_to_replace, '') AS
result
FROM #temp t1) cs
WHERE result <> string_name
Result :
result
-----
ABC
DEF
GHI
Note : This will work only if the each string_name has only one string_to_replace
Update : To handle more than one string_to_replace in a single string_name here is one way using Dynamic sql
I have made one small change to the #temp table by adding a identity property to loop
IF Object_id('tempdb..#temp') IS NOT NULL
DROP TABLE #temp
IF Object_id('tempdb..#temp2') IS NOT NULL
DROP TABLE #temp2
CREATE TABLE #temp
(
id INT IDENTITY(1, 1),
string_to_replace NVARCHAR(5)
)
INSERT INTO #temp
(string_to_replace)
VALUES (' '),
('/'),
('_')
CREATE TABLE #temp2
(
string_name NVARCHAR(5)
)
INSERT INTO #temp2
(string_name)
VALUES ('A BC'),
('D/EF'),
('G_HI'),
('A BD_')
DECLARE #col_list VARCHAR(8000)= '',
#sql VARCHAR(max),
#cntr INT,
#inr INT =1,
#STRING_TO_REPLACE NVARCHAR(5)
SELECT #cntr = Max(id)
FROM #temp
SET #sql = 'select '
WHILE #inr < = #cntr
BEGIN
SELECT #STRING_TO_REPLACE = string_to_replace
FROM #temp
WHERE id = #inr
IF #inr = 1
SET #col_list = 'replace (STRING_NAME,'''
+ #STRING_TO_REPLACE + ''','''')'
ELSE
SET #col_list = 'replace (' + #col_list + ','''
+ #STRING_TO_REPLACE + ''','''')'
SET #inr+=1
END
SET #sql += ' from #temp2'
--print #col_list
SET #sql = 'select ' + #col_list + ' as Result from #temp2'
--print #sql
EXEC (#sql)
Result :
Result
------
ABC
DEF
GHI
ABD

The multiple replace can be achieved via a recursive CTE as per following example:
IF OBJECT_ID('tempdb..#temp') IS NOT NULL DROP TABLE #temp
IF OBJECT_ID('tempdb..#temp2') IS NOT NULL DROP TABLE #temp2
CREATE TABLE #temp
(
STRING_TO_REPLACE NVARCHAR(10)
,Pattern NVARCHAR(10)
)
INSERT INTO #temp (STRING_TO_REPLACE, Pattern)
VALUES (' ', '% %')
,('/', '%/%')
,('_', '%[_]%') ;
CREATE TABLE #temp2
(
STRING_NAME NVARCHAR(10)
);
INSERT INTO #temp2 (STRING_NAME)
VALUES ('A BC')
,('D/EF_F E')
,('G_HI')
,('XYZ');
WITH CTE_Replace AS
(
SELECT STRING_NAME AS OriginalString
,CAST(STRING_NAME AS NVARCHAR(10)) AS ReplacedString
,CAST('' AS NVARCHAR(10)) AS StringToReplace
,1 AS ReplaceCount
FROM #temp2 ancor
UNION ALL
SELECT CTE_Replace.OriginalString
,CAST(REPLACE(CTE_Replace.ReplacedString, rep.STRING_TO_REPLACE, '') AS NVARCHAR(10)) AS ReplacedString
,CAST(rep.STRING_TO_REPLACE AS NVARCHAR(10)) AS StringToReplace
,CTE_Replace.ReplaceCount + 1 AS ReplaceCount
FROM #temp rep
INNER JOIN CTE_Replace ON CTE_Replace.ReplacedString LIKE rep.Pattern
)
,CTE_FinalReplacedString AS
(
SELECT OriginalString
,ReplacedString
,ReplaceCount
,ROW_NUMBER() OVER (PARTITION BY OriginalString ORDER BY ReplaceCount DESC) AS [Rank]
FROM CTE_Replace
)
SELECT *
FROM CTE_FinalReplacedString
WHERE [Rank] = 1
Note that #temp table was updated to include an extra column called Pattern, this column contains the search pattern to use in order to find the specific strings that has to be replaced. This was also done to simplify the join statement in the recursive CTE. Also note that in order to find the _ character the search pattern had to be updated as '%[_]%'. The reason for this is because SQL Server will interpret the _ character as a wild character instead of a specific character we are trying to find.

replace in the table is probably easier here
update t2
set t2.string_name = Replace(t2.string_name, t1.string_to_replace, '')
from #temp2 t2
cross join #temp1 t1

A simple way to deal with this is to download a copy of PatExclude8K, a T-SQL function designed for exactly this type of task. Here's a couple examples:
-- remove all non-aplphabetical characters
SELECT NewString FROM #temp2 CROSS APPLY dbo.PatExclude8K(STRING_NAME,'[^A-Z]');
-- remove all spaces, forward slashes and underscores
SELECT NewString FROM #temp2 CROSS APPLY dbo.PatExclude8K(STRING_NAME,'[ /_]');
Both queries produce this result set:
NewString
------------
ABC
DEF
GHI

I've found below code on stackoverflow which seems more near to what I'm trying to achieve but am struggling that how can I use this with my code
declare #String varchar(max) = '(N_100-(6858)*(6858)*N_100/0_2)%N_35'
--table containing values to be replaced
create table #Replace
(
StringToReplace varchar(100) not null primary key clustered
,ReplacementString varchar(100) not null
)
insert into #Replace (StringToReplace, ReplacementString)
values ('+', '~')
,('-', '~')
,('*', '~')
,('/', '~')
,('%', '~')
,('(', '~')
,(')', '~')
select #String = replace(#String, StringToReplace, ReplacementString)
from #Replace a
select #String
drop table #Replace
EDIT by gofr1
CREATE FUNCTION replacement
(
#String nvarchar(max)
)
RETURNS nvarchar(max)
AS
BEGIN
DECLARE #Replace TABLE (
StringToReplace nvarchar(100),
ReplacementString nvarchar(100)
)
INSERT INTO #Replace (StringToReplace, ReplacementString)
VALUES ('+', '~')
,('-', '~')
,('*', '~')
,('/', '~')
,('%', '~')
,('(', '~')
,(')', '~')
SELECT #String = replace(#String, StringToReplace, ReplacementString)
FROM #Replace
RETURN #String
END
GO
Then call it:
SELECT dbo.replacement ('A B-C/d')
Output:
A B~C~d

Another way with recursive CTE (full batch below):
--Create a sample table, you should use YourTable
CREATE TABLE #temp2 (
STRING_NAME NVARCHAR(max)
)
INSERT INTO #temp2 (STRING_NAME)
VALUES ('A BC'),('D/EF'),('G_HI'),('J_K/L_'),('MNO')
--I add some more objects here
The main query:
;WITH replacement AS (
SELECT *
FROM (VALUES (' '),('/'),('_')
) as t(STRING_TO_REPLACE)
), cte AS (
SELECT STRING_NAME,
STRING_NAME as OriginalString,
ROW_NUMBER() OVER (ORDER BY STRING_NAME) as rn,
1 as [Level]
FROM #temp2 t2
UNION ALL
SELECT REPLACE(c.STRING_NAME,t.STRING_TO_REPLACE,'~'),
c.OriginalString,
c.rn,
[Level]+1
FROM cte c
INNER JOIN replacement t
ON CHARINDEX(t.STRING_TO_REPLACE,c.STRING_NAME,0) > 0
)
SELECT TOP 1 WITH TIES OriginalString,
STRING_NAME
FROM cte
ORDER BY ROW_NUMBER() OVER (PARTITION BY rn ORDER BY [Level] DESC)
OPTION (MAXRECURSION 0)
Output:
OriginalString STRING_NAME
A BC A~BC
D/EF D~EF
J_K/L_ J~K~L~
G_HI G~HI
MNO MNO

Related

How to Build select Query split Temp Value to two column one Per Number And Another to Text when Flag Allow 1?

I work on a query for SQL Server 2012. I have an issue: I can't build select
Query split Column Temp value to two Column When row in the temp table #nonparametric has the flag Allow = 1,
it must split column Temp value from #nonparametric to two column when the flag Allow = 1 .
suppose column Temp value has value 50.40 kg it must split to two column
First column with number so it will have 50.40 and it will be same Name as Parametric .
Second column with Text so it will have kg and it will be same Name as Parametric + 'Units'.
meaning Name will be ParametricUnit .
I need to build query that split this on two column when Flag Allow =1 .
create table #nonparametricdata
(
PART_ID nvarchar(50) ,
CompanyName nvarchar(50),
PartNumber nvarchar(50),
DKFeatureName nvarchar(100),
Tempvalue nvarchar(50),
FlagAllow bit
)
insert into #nonparametricdata
values
('1222','Honda','silicon','package','15.50Am',0),
('1900','MERCEIS','GLASS','family','90.00Am',1),--Build select query split data because FlagAllow=1
('5000','TOYOTA','alominia','source','70.20kg',0),
('8000','MACDA','motor','parametric','50.40kg',1),----Build select query split data because FlagAllow=1
('8900','JEB','mirror','noparametric','75.35kg',0)
create table #FinalTable
(
DKFeatureName nvarchar(50),
DisplayOrder int
)
insert into #FinalTable (DKFeatureName,DisplayOrder)
values
('package',3),
('family',4),
('source',5),
('parametric',2),
('noparametric',1)
what I try is below :
DECLARE #SelectqueryData NVARCHAR(MAX)
SELECT
#SelectqueryData = STUFF(
(
SELECT ', ' + case when B.FlagAllow = 1 then '['+A.DKFeatureName+'],['+A.DKFeatureName+'Unit]' else quotename(A.DKFeatureName) end
FROM #FinalTable A
join (Select distinct DKFeatureName,FlagAllow
From #nonparametricdata
) B on A.DKFeatureName=B.DKFeatureName
ORDER BY DisplayOrder
FOR XML PATH ('')
),1,2,''
)
select #SelectqueryData
--select #SelectqueryData from table
Expected Result is :
[noparametric], [parametric]--QueryGetNumber,[parametricUnit]--QueryGetUnitOfMeasure
, [package], [family]--QueryGetNumber,[familyUnit]--QueryGetUnitOfMeasure, [source]
when make query above it must give me result as image(for Explain Only) :
You're looking for a DYNAMIC PIVOT
Example
DECLARE #SelectqueryData NVARCHAR(MAX)
SELECT #SelectqueryData = STUFF( (
SELECT ', ' + case when B.FlagAllow = 1 then '['+A.DKFeatureName+'],['+A.DKFeatureName+'Unit]' else quotename(A.DKFeatureName) end
FROM #FinalTable A
join (Select distinct DKFeatureName,FlagAllow
From #nonparametricdata
) B on A.DKFeatureName=B.DKFeatureName
ORDER BY DisplayOrder
FOR XML PATH ('')
),1,2,''
)
Declare #SQL varchar(max) = '
Select *
From (
Select A.Part_ID
,A.PartNumber
,A.CompanyName
,B.*
From #nonparametricdata A
Cross Apply ( values ( DKFeatureName ,case when FlagAllow=1 then left(TempValue,patindex(''%[A-Z]%'',TempValue+''A'')-1) else TempValue end )
,( DKFeatureName+''Unit'',case when FlagAllow=1 then substring(TempValue,patindex(''%[A-Z]%'',TempValue+''A''),10) else null end )
) B(Item,Value)
) src
Pivot (max(value) for Item in ('+#SelectqueryData+') ) pvt
'
--Print #SQL
Exec(#SQL)
Returns

SQL Server map rows with columns of different table

I am trying to create a result based on row and column value mapping. This is an example
First table (#column_table) has colunm_id and column_name.
Second table #column_value has values for the rows in First Table.
I want to create a dataset that looks like below:
How do I achieve that?
Here are the temp tables for data setup.
create table #column_table (column_id int, column_name varchar(50) )
insert into #column_table
values
(1, 'FirstName'),
(2, 'LastName'),
(3, 'Address'),
(4, 'Phone')
create table #column_value(FirstName varchar(50), LastName varchar(50), Phone varchar(50),)
insert into #column_value
values
('John','Smith','1234567')
select * from #column_table
select * from #column_value
UNPIVOT would be more performant, but if you need a more dynamic approach without actually using Dynamic SQL
Example
Select D.Column_ID
,Column_Value = C.Value
From #column_value A
Cross Apply (values (convert(xml,(Select A.* For XML Raw)))) B(XMLData)
Cross Apply (
Select Item = xAttr.value('local-name(.)', 'varchar(100)')
,Value = xAttr.value('.','varchar(max)')
From XMLData.nodes('//#*') xNode(xAttr)
) C
Join #column_table D on C.Item=D.Column_Name
Returns
Column_ID Column_Value
1 John
2 Smith
4 1234567
You can achieve this using UnPivot which is more performant, but as #JohnCappelleti mentioned you need to implement a Dynamic SQL logic:
Side Notes: i used like '#column_value%' not = '#column_value' because the example is handling a temp table. Also, You should replace #column_value and #column_table by corresponding tables names. Also, i used tempdb.information_schema because temp tables are stored in tempdb database.
--Create Tables and insert values
create table #column_table (column_id int, column_name varchar(50) )
insert into #column_table
values
(1, 'FirstName'),
(2, 'LastName'),
(3, 'Address'),
(4, 'Phone')
create table #column_value(FirstName varchar(50), LastName varchar(50), Phone varchar(50),)
insert into #column_value
values
('John','Smith','1234567')
select * from #column_table
select * from #column_value
--Get columns found in both tables
SELECT t2.column_id,t2.column_name
INTO #tblTemp
from tempdb.information_schema.columns t1
inner join #column_table t2 on t1.COLUMN_NAME = t2.column_name
where t1.table_name like '#column_value%'
--Building Dynamic Query
DECLARE #strQuery VARCHAR(4000) = 'SELECT * FROM (SELECT '
DECLARE #strUnPivot as varchar(max) = ' UNPIVOT ([Value] for [Column_ID] IN ('
SELECT #strUnPivot = ISNULL(#strUnPivot,'') + '[' + CAST(column_id as varchar(10)) + '] ,' From #tblTemp
SELECT #strQuery = #strQuery + '[' + column_name + '] AS "' + CAST(column_id as varchar(10)) + '",' From #tblTemp
SELECT #strQuery = SUBSTRING(#strQuery,1,LEN(#strQuery) - 1) + ' FROM #column_value) AS p ' + SUBSTRING(#strUnPivot,1,LEN(#strUnPivot) - 1) + ')) AS unpvt '
--Execute Query
EXEC(#strQuery)
Result
References
How to convert a column header and its value into row in sql?
Select non-empty columns using SQL Server
With CASE and a cross join:
select
ct.column_id,
case ct.column_id
when 1 then cv.FirstName
when 2 then cv.LastName
when 4 then cv.Phone
end column_value
from #column_table ct cross join #column_value cv
where ct.column_id <> 3
See the demo

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

Splitting a string then pivoting result

If have a string passed from a .Net application that looks like the below
2023|F66451,1684|648521,1684|600271,2137|019592
I have started to parse out the string using the method below but I need to Pivot the data returned from the Split ( surrounded by *'s) function in order to insert into the #tmpExceptions table
DECLARE #ExceptionsList as nvarchar(MAX)
SET #ExceptionsList = '2023|F66451,1684|648521,1684|600271,2137|019592'
SET NOCOUNT ON;
DECLARE #CurrentLineItem as nvarchar(255)
CREATE TABLE #ParsePassOne
(
LineItem nvarchar(255)
)
CREATE TABLE #tmpExceptions
(
AccountNumber int,
ClaimNumber nvarchar(50)
)
INSERT INTO #ParsePassOne
SELECT value FROM Split( ',' ,#ExceptionsList)
WHILE EXISTS(SELECT LineItem FROM #ParsePassOne)
BEGIN
SELECT TOP 1 #CurrentLineItem = LineItem FROM #ParsePassOne
*******
SELECT value FROM Split( '|' ,#CurrentLineItem)
*******
DELETE FROM #ParsePassOne WHERE LineItem = #CurrentLineItem
END
SELECT * FROM #tmpExceptions
DROP TABLE #ParsePassOne
DROP TABLE #tmpExceptions
So far the data returned looks as below. I just need to pivot the data to columns so I can insert it. How do I go about this?
Split Function
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
--Creates an 'InLine' Table Valued Function (TVF)
ALTER FUNCTION [dbo].[Split]
( #Delimiter varchar(5),
#List varchar(8000)
)
RETURNS #TableOfValues table
( RowID smallint IDENTITY(1,1),
[Value] varchar(50)
)
AS
BEGIN
DECLARE #LenString int
WHILE len( #List ) > 0
BEGIN
SELECT #LenString =
(CASE charindex( #Delimiter, #List )
WHEN 0 THEN len( #List )
ELSE ( charindex( #Delimiter, #List ) -1 )
END
)
INSERT INTO #TableOfValues
SELECT substring( #List, 1, #LenString )
SELECT #List =
(CASE ( len( #List ) - #LenString )
WHEN 0 THEN ''
ELSE right( #List, len( #List ) - #LenString - 1 )
END
)
END
RETURN
END
If you are using SQL Server 2016 you can use String_Split() function and use cross apply/pivot to get into single row
create table #t (v varchar(50), i int)
insert into #t (v, i) values ('2023|F66451',1)
,('1684|648521',2), ('1684|600271', 3), ('2137|019592', 4)
--Inorder to get into same row -pivoting the data
select * from (
select * from #t t cross apply (select RowN=Row_Number() over (Order by (SELECT NULL)), value from string_split(t.v, '|') ) d) src
pivot (max(value) for src.RowN in([1],[2])) p
You can replace your WHILE EXISTS(SELECT LineItem FROM #ParsePassOne) loop with
select *
from
(
select * from #parsepassone
cross apply dbo.Split( '|' ,lineitem)
) src
pivot
(max(value) for rowid in ([1],[2]))p
Or replace the whole thing with
insert #tmpExceptions (AccountNumber, ClaimNumber)
select [1],[2]
from
(
select e.rowid e, p.* from dbo.Split( ',' ,#ExceptionsList) e
cross apply dbo.Split( '|' ,e.value) p ) s
pivot
(max(value) for rowid in ([1],[2]))p
I had a similar problem, where I needed to split and pivot the values in a column, and I needed to have it all in a view.
I came up with the following code (for SQL 2016 and up)
/* Table */
DECLARE #data TABLE (id INT IDENTITY(1,1), [Name] VARCHAR(128) NOT NULL, [Selection] VARCHAR(512) NULL)
INSERT INTO #data ([Name],Selection)
VALUES('Bob','PC; Mouse; Keyboard; Network; Smartphone'),
('Mo','Violin; Hammer'),
('Jon','Magic; Blood; Teleporter'),
('Mhary','Vampire')
/* Pivot */
;WITH Data_RowNumber AS (
SELECT
id,
[name],
split.value,
ROW_NUMBER() OVER (PARTITION BY id ORDER BY id) AS RowNumber
FROM #data
CROSS APPLY STRING_SPLIT([Selection],';') AS split
)
SELECT
id,
[name],
[1],[2],[3],[4],[5],[6]
FROM Data_RowNumber
PIVOT (
MAX([value])
FOR [RowNumber] IN ([1],[2],[3],[4],[5],[6])
) AS p

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