Sql Grouping insight Xml - sql

I need to get different row data to 1 row based with id and need to group by its status.
I tried in several ways and finally found a solution using xml, but when I use xml I don't get the output I want.
This is a sample code I used to try the logic.
drop table #TestTable
-- Create table
CREATE TABLE #TestTable (id VARCHAR(100), title VARCHAR(100),
progress VARCHAR(100))
GO
-- Populate tableid
INSERT INTO #TestTable (id, title, progress)
SELECT '1', 'test1', 'inprogress'
UNION ALL
SELECT '1', 'test2', 'inprogress'
UNION ALL
SELECT '1', 'test3', 'completed'
UNION ALL
SELECT '1', 'test4', 'completed'
GO
SELECT id,progress, comments = STUFF((SELECT +' , '+ TITLE
FROM #TestTable AS x2 WHERE ID = x.ID
ORDER BY ID
FOR XML PATH(''), TYPE).value(N'(./text())[1]', N'varchar(max)'), 1, 1, '')
FROM #TestTable AS x
GROUP BY id,progress 
returned output:
id progress comments
1 completed , test1 , test2 , test3 , test4
1 inprogress , test1 , test2 , test3 , test4
expected output:
id progress comments
1 completed , test1 , test2
1 inprogress , test3 , test4

I think you are missing a condition in your join (progress = x.progress):
SELECT id,progress, comments = STUFF((SELECT +' , '+ TITLE
FROM #TestTable AS x2 WHERE ID = x.ID
and progress = x.progress -- <-- add this
ORDER BY ID
FOR XML PATH(''), TYPE).value(N'(./text())[1]', N'varchar(max)'), 1, 1, '')
FROM #TestTable AS x
GROUP BY id,progress
Output:

Related

How to Pivot on two columns in SQL Server [duplicate]

This question already has an answer here:
TSQL PIVOT MULTIPLE COLUMNS
(1 answer)
Closed 4 years ago.
Below is the data rows I have:
ID Code OtherCol
7 Code1 NULL
7 code2 NULL
2 unk NULL
4 unk NULL
3 Code2 NULL
3 Code3 NULL
3 Code5 Other1
5 Code4 NULL
5 Code5 Other2
I am trying get this displayed as
ID name1 name2 name3 name4 name5 nameunk Othername
2 unk
3 code2 code3 code5 Other1
4 unk
5 code4 code5 Other2
7 code1 code2
I was able to pivot the first column but having a problem pivoting the second one.
And also there is a name for a given code, but the value under OtherCol are random.
I recommend conditional aggregation:
select id,
max(case when code = 'code1' then code end) as name1,
max(case when code = 'code2' then code end) as name2,
max(case when code = 'code3' then code end) as name3,
max(case when code = 'code4' then code end) as name4,
max(case when code = 'code5' then code end) as name5,
max(case when code = 'unk' then code end) as nameunk,
max(othercol) as othercol
from t
group by id;
This is full working example. You can change it a little bit to match your real data.
CREATE TABLE #DataSource
(
[ID] INT
,[Code] VARCHAR(12)
,[OtherCol] VARCHAR(12)
);
INSERT INTO #DataSource ([ID], [Code], [OtherCol])
VALUES (7, 'Code1', NULL)
,(7, 'code2', NULL)
,(2, 'Unk', NULL)
,(4, 'Unk', NULL)
,(3, 'Code2', NULL)
,(3, 'Code3', NULL)
,(3, 'Code5', 'Other1')
,(5, 'Code4', NULL)
,(5, 'Code4', 'Other2');
DECLARE #DynammicTSQLStatement NVARCHAR(MAX)
,#DynamicPIVOTColumns NVARCHAR(MAX);
SET #DynamicPIVOTColumns = STUFF
(
(
SELECT ',[' + CAST([value] AS VARCHAR(12)) + ']'
FROM
(
SELECT 0
,DENSE_RANK() OVER (ORDER BY [Code])
,REPLACE([Code], 'Code', 'name')
FROM #DataSource
WHERE [Code] IS NOT NULL
UNION
SELECT 1
,1
,'OtherCol'
) DS ([GroupID],[RowID], [value])
ORDER BY [GroupID], [RowID]
FOR XML PATH('') ,TYPE
).value('.', 'NVARCHAR(MAX)')
,1
,1
,''
);
SET #DynammicTSQLStatement = N'
SELECT *
FROM
(
SELECT [ID]
,[Code]
,REPLACE([Code], ''Code'', ''name'')
FROM #DataSource
UNION ALL
SELECT [ID]
,[OtherCol]
,''OtherCol''
FROM #DataSource
) DS ([ID], [value], [column])
PIVOT
(
MAX([value]) FOR [column] IN (' + #DynamicPIVOTColumns + ')
) PVT';
EXEC sp_executesql #DynammicTSQLStatement;
DROP TABLE #DataSource;
--PIVOT THE TABLE
select ID,[code1],[code2], [code3],[code4],[code5],[Unk]
into #resPivot
from
(
select ID, code
from tblTest
) src
pivot
(
max(code)
for code in ([code1], [code2], [code3],[code4],[code5],[Unk])
) piv;
--FIND ALL COLS WHERE OTHER COLUMN have value row 3,5 in your example
SELECT * INTO #distinct FROM tblTest where tblTest.otherCol IS NOT NULL
--PIVOTED RESULT WITH ABOVE TABLE
select distinct #resPivot.ID,[code1], [code2], [code3],[code4],[code5],[Unk],#distinct.otherCol
into #otherCol
from #resPivot inner join #distinct
on #distinct.id = #resPivot.id
--THIS IS PIVOTED RESULT WITH ALL RESULTS THAT HAS NO OTHER COL VALUE UNION with OTHER CALL VALUE
select distinct #resPivot.ID,[code1], [code2], [code3],[code4],[code5],[Unk],tblTest.otherCol
from #resPivot inner join tblTest
on tblTest.id = #resPivot.id
WHERE otherCol IS NULL and tblTest.ID NOT IN (SELECT ID FROM #otherCol)
UNION ALL
Select * from #otherCol
--DROP TEMP TABLES
Drop Table #resPivot
Drop Table #distinct
Drop Table #otherCol
A little simpler and faster version

Row result manipulation

I have an existing query that retrieves this data:
Key Type TextF
--- ---- ------
1 R NULL
1 T TEST
1 T TEST2
2 R NULL
2 T FOO
3 R NULL
Scenario:
Row type R will always have a NULL on TextF. However if the Key has a type T data existing, I should place the TextF on R data, joining them with CRLF or char(13)
Expected output based on given data:
Key Type TextF
--- ---- ----------
1 R TEST TEST2
2 R FOO
3 R NULL
How can I achieve this through a query? I'm trying to make my existing query to be a subquery but I cant seem to make it work.
SELECT T0.*, *formatting here* FROM ( [myQuery] ) T0
I don't think it's the best solution but you could use the STUFF function to achieve your desired results:
SELECT t1.[Key],
'R' [Type],
STUFF((SELECT ' ' + t2.[TextF]
FROM yourTable t2
WHERE t2.[Key] = t1.[Key]
FOR XML PATH('')), 1, 1, '') [TextF]
FROM yourTable t1
GROUP BY t1.[Key]
You can use this.
DECLARE #MyTable TABLE ([Key] INT, Type VARCHAR(5), TextF VARCHAR(100))
INSERT INTO #MyTable VALUES
(1 ,'R', NULL),
(1 ,'T', 'TEST'),
(1 ,'T', 'TEST2'),
(2 ,'R', NULL),
(2 ,'T', 'FOO'),
(3 ,'R', NULL)
SELECT
T.[Key],
T.Type,
CASE WHEN Type = 'R' THEN REPLACE(STUFF(X.TextF,1,1,''),'|', CHAR(13)) ELSE T.TextF END TextF
FROM #MyTable T
OUTER APPLY( SELECT '|' + TextF FROM #MyTable T1
WHERE T.[Key] = T1.[Key]
AND T1.Type <> 'R'
AND T1.TextF IS NOT NULL FOR XML PATH('')) X(TextF)
WHERE T.Type = 'R'
Result:
Key Type TextF
----------- ----- -------------
1 R TEST
TEST2
2 R FOO
3 R NULL
In SQL Server 2017 you can use a new built-in function STRING_AGG
SELECT T0.[Key], T0.[Type],
(SELECT STRING_AGG (T1.TextF, CHAR(13)) AS TextF
FROM [myTable] T1
WHERE T1.[Type]='T' AND T1.[Key]=T0.[Key]
) TextF
FROM [myTable] T0
WHERE T0.[Type]='R'
Slightly different from other solutions--
DECLARE #MyTable TABLE ([Key] INT, Type VARCHAR(5), TextF VARCHAR(100))
INSERT INTO #MyTable VALUES
(1 ,'R', NULL),
(1 ,'T', 'TEST'),
(1 ,'T', 'TEST2'),
(2 ,'R', NULL),
(2 ,'T', 'FOO'),
(3 ,'R', NULL)
SELECT
T.[Key],
T.Type,
STUFF
((
SELECT ' ' + TextF
FROM #MyTable a
WHERE ( a.[Key] = T.[Key] )
FOR XML PATH('')
) ,1,2,'')
AS cusr
FROM #MyTable T
WHERE T.Type = 'R'
OUTPUT
Key Type cusr
----------- ----- --------------
1 R TEST TEST2
2 R FOO
3 R NULL
(3 rows affected)

Rows to single cell

I would like to get the desired output marked in green
the data points for each id get put into a single cell
Basically take all the events that have happened with A and attach it in the same order
Use Stuff Function:
DECLARE #tblTest AS Table(
ID INT,
EVENT VARCHAR(5)
)
INSERT INTO #tblTest VALUES
(1,'A'),
(1,'A'),
(1,'C'),
(2,'A'),
(2,'B'),
(2,'C')
SELECT DISTINCT
T1.ID,
STUFF
(
(SELECT '' + convert(varchar(10), T2.EVENT, 120)
FROM #tblTest T2
where T1.ID = T2.ID
FOR XML PATH (''))
, 1, 0, '') AS EVENT
FROM #tblTest T1
You can use FOR XML:
SELECT DISTINCT
ID,
(SELECT [EVENT] +''
FROM YourTable
WHERE ID = y.ID
FOR XML PATH('')
) as [EVENT]
FROM YourTable y
Output:
ID EVENT
1 AABCD
2 AABBCC
You can use UDF to do so as follows:
CREATE TABLE t(
id INT,
col CHAR(1)
);
INSERT INTO t VALUES (1,'a');
INSERT INTO t VALUES (1,'b');
INSERT INTO t VALUES (1,'c');
INSERT INTO t VALUES (1,'d');
INSERT INTO t VALUES (2,'e');
INSERT INTO t VALUES (2,'f');
INSERT INTO t VALUES (3,'g');
INSERT INTO t VALUES (4,'h');
The UDF (User defined function) -
USE [t]
GO
CREATE FUNCTION dbo.ConcatenateCols(#Id INT)
RETURNS VARCHAR(MAX)
AS
BEGIN
DECLARE #RtnStr VARCHAR(MAX)
SELECT #RtnStr = COALESCE(#RtnStr + '','') + col
FROM dbo.t
WHERE id = #Id AND col > ''
RETURN #RtnStr
END
GO
Finally the query and result:
SELECT id, dbo.ConcatenateCols(id) AS Cols -- UDF - ConcatenateCols(id)
FROM t GROUP BY Id
CREATE TABLE #temp(Id INt,Event Nvarchar(25))
INSERT INTO #temp
SELECT 1,
'A'
UNION ALL
SELECT 1,
'A'
UNION ALL
SELECT 1,
'B'
UNION ALL
SELECT 1,
'C'
UNION ALL
SELECT 1,
'D'
UNION ALL
SELECT 2,
'A'
UNION ALL
SELECT 2,
'A'
UNION ALL
SELECT 2,
'B'
UNION ALL
SELECT 2,
'B'
UNION ALL
SELECT 2,
'C'
UNION ALL
SELECT 2,
'C'
SELECT DISTINCT ID,
(SELECT [EVENT] +''
FROM #temp
WHERE ID = y.ID
FOR XML PATH('') ) AS [EVENT]
FROM #temp y

SQL Server 2008 - Replace Text Values in Column with Values from Another Table

I've tried flexing my Google-fu to no avail so here I am! Unfortunately I cannot change anything about these tables as they are coming out of an application that I have to report out of.
In SQL Server 2008, I'm trying to replace multiple values in one text string column (Table 1) with the value from another table (Table 2).
Thanks in advance!!
Table 1
id value
-------------
1 a1, a2, a3
2 a2, a3
3 a4
Table 2
id value
---------
a1 Value1
a2 Value2
a3 Value3
a4 Value4
Desired Output
id value
-----------------------------
1 Value1, Value2, Value3
2 Value2, Value3
3 Value4
I'm sorry for this solution in advance :) It does what you need though:
create table TableA(
id int,
string varchar(255)
)
create table table2(
id varchar , text varchar(255)
)
insert into tableA values(1,'a,b,c,d')
insert into tableA values(2,'e,f')
insert into table2 values('a', 'value1')
insert into table2 values('b', 'value2')
insert into table2 values('c', 'value3')
insert into table2 values('d', 'value4')
insert into table2 values('e', 'value5')
insert into table2 values('f', 'value6')
select id, left(myConcat,len(myConcat)-1) from (
select c.id, replace(replace(CAST(CAST('<i'+stuff((select * from(
SELECT A.[id] ,
Split.a.value('.', 'VARCHAR(1000)') AS String
FROM (SELECT [id],
CAST ('<M>' + REPLACE([string], ',', '</M><M>') + '</M>' AS XML) AS String
FROM TableA) AS A CROSS APPLY String.nodes ('/M') AS Split(a)) a
inner join table2 b on a.String = b.id
where a.id = c.id
FOR XML PATH ('')
),1,2,'') AS XML).query('/text') AS VARCHAR(1000)),'<text>',''),'</text>',',') myConcat
from TableA c
group by c.id
) d
Using the DelimitedSplit8K found at http://www.sqlservercentral.com/articles/Tally+Table/72993/ as suggested by #user1221684 you might come up with something like this. Working with delimited data like this is a pain. First you have to parse the string so you can join it to the other table and then ruin by stuffing it back into a denormalized form.
Make sure that if you use this that you understand that function and what this code is doing. This is not entry level t-sql and it will be YOU supporting this at 3am when it breaks in production, not me.
if OBJECT_ID('tempdb..#table1') is not null
drop table #table1;
create table #table1
(
id int,
value varchar(50)
);
insert #table1
select 1, 'a1, a2, a3' union all
select 2, 'a2, a3' union all
select 3, 'a4';
if OBJECT_ID('tempdb..#table2') is not null
drop table #table2;
create table #table2
(
id varchar(50),
value varchar(50)
);
insert #table2
select 'a1', 'Value1' union all
select 'a2', 'Value2' union all
select 'a3', 'Value3' union all
select 'a4', 'Value4';
with parsedValues as
(
select t1.id
, t1.value
, LTRIM(x.item) as item
from #table1 t1
cross apply dbo.DelimitedSplit8K(t1.value, ',') x
)
, swappedVals as
(
select pv.id
, t2.value
from parsedValues pv
join #table2 t2 on t2.id = pv.item
)
select id
, STUFF((select ',' + value
from swappedVals sv2
where sv2.id = sv.id
order by sv2.value --need to make sure to order here so the results are in the right order
for XML path('')), 1, 1, '') as MyValues
from swappedVals sv
group by id
;
This site has a delimited text split function http://www.sqlservercentral.com/articles/Tally+Table/72993/
Use that function to split your values out into a temp table. Replace the values in your temp table with the new values. Then use STUFF..FOR XML to combine the records back together and update your table.
One query with a few cte's should be able to handle all of this after you add the function to your database.
Example using Sql Fiddle
Use this:
DECLARE #t TABLE(id int,value varchar(255))
INSERT INTO #t (id,value)
VALUES(1,'a1'),(2,'a2'),(3,'a3')....
SELECT *,STUFF((SELECT DISTINCT ','+value FROM #t WHERE id=t.id)
FOR XML PATH('')),1,2,' ')
FROM (SELECT DISTINCT ID FROM #t) t
DISTINCT in the case of same id, otherwise let it go

How to concatenate all strings from a certain column for each group

Suppose I have this table [Table1]
Name Mark
------- ------
ABC 10
DEF 10
GHI 10
JKL 20
MNO 20
PQR 30
What should be my SQL statement to retrieve a record that looks like this:
(group by [mark]).
I have done the 1 and 2 columns but don't know how to accomplish the third column (concat the [name] with the same [mark])
mark count names
---- ----- -----------
10 3 ABC,DEF,GHI
20 2 JKL,MNO
30 1 PQR
I'm using Microsoft SQL.
Please help. Thanks
If MS SQL 2005 or higher.
declare #t table([name] varchar(max), mark int)
insert #t values ('ABC', 10), ('DEF', 10), ('GHI', 10),
('JKL', 20), ('MNO', 20), ('PQR', 30)
select t.mark, COUNT(*) [count]
,STUFF((
select ',' + [name]
from #t t1
where t1.mark = t.mark
for xml path(''), type
).value('.', 'varchar(max)'), 1, 1, '') [values]
from #t t
group by t.mark
Output:
mark count values
----------- ----------- --------------
10 3 ABC,DEF,GHI
20 2 JKL,MNO
30 1 PQR
Here's a performance-related answer!
http://jerrytech.blogspot.com/2010/04/tsql-concatenate-strings-1-2-3-and.html
Using XML functions in a large query is a performance killer.
Using a CTE is a performance superstar.
Check out the link, it will explain how.
I admit the work to accomplish it is more.
But the result is milliseconds over millions of rows.
polishchuks solution is more elegant, but this is basically the same thing, we just deal with the trailing comma differently.
CREATE TABLE #Marks(Name nchar(3), Mark int)
INSERT INTO #Marks
SELECT 'ABC', 10 UNION ALL
SELECT 'DEF', 10 UNION ALL
SELECT 'GHI', 10 UNION ALL
SELECT 'JKL', 20 UNION ALL
SELECT 'MNO', 20 UNION ALL
SELECT 'PQR', 30
SELECT
mark,
[count],
CASE WHEN Len(Names) > 0 THEN LEFT(Names, LEN(Names) -1) ELSE '' END names
FROM
(
SELECT
Mark,
COUNT(Mark) AS [count],
(
SELECT DISTINCT
Name + ', '
FROM
#Marks M1
WHERE M1.Mark = M2.Mark
FOR XML PATH('')
) Names
FROM #Marks M2
GROUP BY Mark
) M
Loosely based on Itzik Ben-Gan, Inside Microsoft SQL Server 2005: T-SQL Programming, p. 215:
IF OBJECT_ID('dbo.Table1') IS NOT NULL
DROP TABLE dbo.Table1 ;
GO
CREATE TABLE dbo.Table1 ( Name VARCHAR(10), Mark INT ) ;
INSERT INTO dbo.Table1 ( Name, Mark ) VALUES ( 'ABC', 10 ) ;
INSERT INTO dbo.Table1 ( Name, Mark ) VALUES ( 'DEF', 10 ) ;
INSERT INTO dbo.Table1 ( Name, Mark ) VALUES ( 'GHI', 10 ) ;
INSERT INTO dbo.Table1 ( Name, Mark ) VALUES ( 'JKL', 20 ) ;
INSERT INTO dbo.Table1 ( Name, Mark ) VALUES ( 'MNO', 20 ) ;
INSERT INTO dbo.Table1 ( Name, Mark ) VALUES ( 'PQR', 30 ) ;
WITH DelimitedNames AS
(
SELECT Mark, T2.Count,
( SELECT Name + ',' AS [text()]
FROM dbo.Table1 AS T1
WHERE T1.Mark = T2.Mark
ORDER BY T1.Mark
FOR XML PATH('')) AS Names
FROM ( SELECT Mark, COUNT(*) AS Count FROM dbo.Table1 GROUP BY Mark ) AS T2
)
SELECT Mark, Count, LEFT(Names, LEN(NAMES) - 1) AS Names
FROM DelimitedNames ;