SQL Concatenate and group - sql

I need a TSQL version of group_concat
Simmilar to the example found here:
Policy Destination ID
-------------------------
PolA DestA 1
PolA DestA 2
PolB DestB 3
PolB DestB 4
PolC DestC 5
PolC DestC 6
PolC DestD 7
The output should look like this:
PolA DestA 1,2
PolB DestB 3,4
PolC DestC 5,6
PolC DestD 7
The grouping is on the uniqueness of both the first 2 columns, and then a concatenated output on the third.
I found this link but it only take into account 2 columns
Any help would be appreciated.

You can try this :
SELECT G.Policy, G.Destination,
stuff(
(select cast(',' as varchar(max)) + U.ID
from yourtable U
WHERE U.Policy = G.Policy and U.Destination = G.Destination
order by U.Policy
for xml path('')
), 1, 1, '') AS IDs
FROM yourtable G group BY G.Policy, G.Destination

In MSSQL Synax:
SELECT Policy, Destination, STRING_AGG ( [ID], ',' ) IDs
FROM Table

I just create the PolA example table for you, just replace the CTE as your table, try below:
WITH ABC
as
(
select 'PolA' as Policy,'DestA' as Destination,'1' as ID
UNION ALL
select 'PolA','DestA','2'
)
SELECT Policy, Destination,
STUFF((SELECT ',' + A.ID FROM ABC as A WHERE A.Policy = B.Policy FOR XML PATH('')),1,1,'') as ID
FROM ABC as B
GROUP BY B.policy, B.Destination

Related

compare 2 text columns and show difference in the third cell using sql

I am trying to compare 2 columns and I have to get the only difference for example
select * from table1
Column_1 column_2
---------------- ------------------
Swetha working Swetha is working in Chennai
Raju 10th Raju is studying 10th std
ranjith Ranjith played yesterday
how to play how to play Cricket
My name is my name is john
Output:
If words come in between it should also remove like row 1 and 2
Column_1 column_2 column_3
---------------- ------------------ ------------------------
Swetha working Swetha is working in Chennai is in Chennai
Raju 10th Raju is studying 10th std is studying std
ranjith Ranjith played yesterday played yesterday
how to play how to play Cricket Cricket
My name is my name is john john
This is much more complicated than your previous question. You can break the first column into words and then substitute them individually in the second column. To do that, though, you need a recursive CTE:
with words as (
select t.*, s.*,
max(s.seqnum) over (partition by t.id) as max_seqnum
from t cross apply
(select s.value as word,
row_number() over (order by (select null)) as seqnum
from string_split(col1, ' ') s
) s
),
cte as (
select id, col1, col2,
replace(' ' + col2 + ' ', ' ' + word + ' ', ' ') as result,
word, seqnum, max_seqnum
from words
where seqnum = 1
union all
select cte.id, cte.col1, cte.col2,
replace(cte.result, ' ' + w.word + ' ', ' '),
w.word, w.seqnum, cte.max_seqnum
from cte join
words w
on w.id = cte.id and w.seqnum = cte.seqnum + 1
)
select id, col1, col2, ltrim(rtrim(result)) as result
from cte
where max_seqnum = seqnum
order by id;
Here is a db<>fiddle.
I added an id so each row is uniquely defined. If your version of SQL Server doesn't have the built-in string_split() function, you can easily find a version that does the same thing.
One trick that this uses is for handling the first and last words in the second column. The code adds spaces at the beginning and end. That way, all words in the string are surrounded by spaces, making it easier to replace only complete words.
SQL 2016 definitely has string split. This approach appends an extra space to either side of the split word from Column 2.
Data
drop table if exists #strings;
go
create table #strings(
Id int,
Column_1 varchar(200),
Column_2 varchar(200));
go
insert #strings(Id, Column_1, Column_2) values
(1, 'Swetha', 'Swetha is working in Chennai'),
(2, 'Raju', 'Raju is studying 10 std'),
(3, 'Swetha working', 'Swetha is working in Chennai'),
(4, 'Raju 10th', 'Raju is studying 10th std');
Query
declare
#add_delim char(1)=' ';
;with
c1_cte(split_str) as (
select ltrim(rtrim(s.[value]))
from
#strings st
cross apply
string_split(st.Column_1, ' ') s),
c2_cte(Id, ndx, split_str) as (
select Id, charindex(#add_delim + s.[value] + #add_delim, #add_delim + st.Column_2 + #add_delim), s.[value]
from
#strings st
cross apply
string_split(st.Column_2, ' ') s
where
st.Column_2 not like '% %')
select
Id, stuff((select ' ' + c.split_str
from c2_cte c
where c.Id = c2.Id and not exists(select 1
from c1_cte c1
where c.split_str=c1.split_str)
order by c.ndx FOR XML PATH('')), 1, 1, '') [new_str]
from c2_cte c2
group by Id;
Results
Id new_str
1 is in Chennai
2 is studying 10 std
3 is in Chennai
4 is studying std
Here is the solution using STRING_SPLIT and STRING_AGG
DBFIDDLE working link
;WITH split_words
AS (
SELECT *
FROM dbo.Strings
CROSS APPLY (
SELECT VALUE
FROM STRING_SPLIT(column_2, ' ')
WHERE VALUE NOT IN (
SELECT VALUE
FROM STRING_SPLIT(column_1, ' ')
)
) a
)
SELECT *
,(
SELECT sw.VALUE + ' ' [text()]
FROM split_words sw
WHERE sw.Column_1 = s.Column_1
AND sw.Column_2 = s.Column_2
FOR XML PATH('')
,TYPE
).value('.', 'NVARCHAR(MAX)') [difference]
FROM dbo.Strings s
For SQL version 2017+ where STRING_AGG is supported
SELECT b.Column_1
,b.Column_2
,STRING_AGG(b.VALUE, ' ')
FROM (
SELECT *
FROM dbo.Strings
CROSS APPLY (
SELECT VALUE
FROM STRING_SPLIT(column_2, ' ')
WHERE VALUE NOT IN (
SELECT VALUE
FROM STRING_SPLIT(column_1, ' ')
)
) a
) b
GROUP BY b.Column_1
,b.Column_2
Results:
WITH
-- your input
input(column_1,column_2,column_3) AS (
SELECT 'Swetha working','Swetha is working in Chennai','is in Chennai'
UNION ALL SELECT 'Raju 10th','Raju is studying 10th std','is studying std'
UNION ALL SELECT 'ranjith','Rantith played yesterday','played yesterday'
UNION ALL SELECT 'how to play','how to play Cricket','Cricket'
UNION ALL SELECT 'My name is','my name is john','john'
)
,
-- need a series of integers
-- you can also try to play with the STRING_SPLIT() function
i(i) AS (
SELECT 1
UNION ALL SELECT 2
UNION ALL SELECT 3
UNION ALL SELECT 4
UNION ALL SELECT 5
)
,
-- you can also try to play with the STRING_SPLIT() function
unfound_tokens AS (
SELECT
i
, column_1
, column_2
, TOKEN(column_2,' ',i) AS token
FROM input CROSS JOIN i
WHERE TOKEN(column_2,' ',i) <> ''
AND CHARINDEX(
UPPER(TOKEN(column_2,' ',i))
, UPPER(column_1)
) = 0
)
SELECT
column_1
, column_2
, STRING_AGG(token ,' ') AS column_3
FROM unfound_tokens
GROUP BY
column_1
, column_2
-- out column_1 | column_2 | column_3
-- out ----------------+------------------------------+--------------------------
-- out My name is | my name is john | john
-- out Swetha working | Swetha is working in Chennai | is Chennai
-- out how to play | how to play Cricket | Cricket
-- out Raju 10th | Raju is studying 10th std | is studying std
-- out ranjith | Rantith played yesterday | Rantith played yesterday
I am not sure that the results, while using STRING_AGG or STRING_SPLIT, will preserve the ordering of the words...
Just look over this query that give a different ordering :
WITH
SS1 AS
(SELECT Id, SS.value AS COL1
FROM #strings
CROSS APPLY STRING_SPLIT(Column_1, ' ') AS SS
),
SS2 AS
(SELECT Id, SS.value AS COL2
FROM #strings
CROSS APPLY STRING_SPLIT(Column_2, ' ') AS SS
),
DIF AS
(
SELECT Id, COL2 AS COL
FROM SS2
EXCEPT
SELECT Id, COL1
FROM SS1
)
SELECT DIF.Id, Column_1, Column_2, STRING_AGG(COL, ' ')
FROM DIF
JOIN #strings AS S ON S.Id = DIF.Id
GROUP BY DIF.Id, Column_1, Column_2;
You must try with a very huge amount of data to see if the queries that have been given, will not have a side effect like the unconsistent ordering (I am pretty sure that no consistent order will appear due to parallelism....)
So the only way to preserve a consistent ordering is to create a recursive query that add an indiced value of the word in the sentence...

Concatenate strings from multiple columns and multiple rows

How to concatenate the strings from multiple rows and multiple columns in SQL Server.
My table is like this:
ItemId AttributeName AttributeValue
---------- ------------- --------------
1 Website www.abc.com
1 Github github.com/abc
1 Facebook facebook.com/abc
2 Website www.123.com
2 Instagram instagram.com/123
and the desired output is:
ItemId Weblinks
---------- -------------
1 Website: www.abc.com; Github: github.com/abc; Facebook: facebook.com/abc
2 Website: www.123.com; Instagram: instagram.com/123
I read from other similar questions that XML PATH is probably required. Please could anyone guide me how to achieve this?
This can also be done with a 2 step query:
SELECT t.id, STRING_AGG(t.sites, ' ') as Weblinks
FROM (
SELECT id, CONCAT(attrName, ': ', attrValue, ';') as sites
FROM test
) as t
GROUP BY t.id
ORDER BY t.id;
The inner concatenates attribute value and name in the desired format, while the outer aggregates the results per user id. Coming from MySQL I find that simpler for some reason.
Demo: here
UPDATE: based on #Panagiotis comment
SELECT t.id, STRING_AGG(CONCAT(t.attrName, ': ', t.attrValue, ';'), ' ') as Weblinks
FROM test t
GROUP BY t.id
ORDER BY t.id;
Demo 2: here
You can use FOR XML PATH() :
SELECT DISTINCT T.ItemId, STUFF(Weblinks, 1, 1, '') AS Weblinks
FROM table T CROSS APPLY
( SELECT CONCAT(';', T1.AttributeName, ':', T1.AttributeValue)
FROM table T1
WHERE T1.ITEMID = T.ITEMID
FOR XML PATH('')
) T1(Weblinks);

SQL: Pivoting on more than one column

I have a table
Name | Period | Value1 | Value2
-----+---------+---------+-------
A 1 2 3
A 2 5 4
A 3 6 7
B 1 2 3
B 2 5 4
B 3 6 7
I need results like
Name | Value1 | Value2
-----+--------+------
A | 2,5,6 | 3,4,7
B | 2,5,6 | 3,4,7
Number of periods is dynamic but I know how to handle it so, for simplicity, let's say there are 3 periods
The query below gives me results for Value1. How can I get results for both?
I can always do them separately and then do a join but the table is really big and I need "combine" four values, not two. Can I do it in one statement?
SELECT Name,
[1]+','+ [2] + ','+ [3] ValueString
FROM (
select Name, period, cpr from #MyTable
) as s
PIVOT(SUM(Value1)
FOR period IN ([1],[2],[3])
Use conditional aggregation. Combining the values into strings is a bit tricky, requiring XML logic in SQL Server:
select n.name,
stuff((select ',' + cast(value1 as varchar(max))
from t
where n.name = t.name
order by t.period
for xml path ('')
), 1, 1, ''
) as values1,
stuff((select ',' + cast(value2 as varchar(max))
from t
where n.name = t.name
order by t.period
for xml path ('')
), 1, 1, ''
) as values2
from (select distinct name
from t
) n;
Your values look like numbers, hence the explicit cast and the lack of concern for XML special characters.
You may ask why this does the distinct in a subquery rather than in the outer query. If done in the outer query, then the SQL engine will probably do the aggregation for every row before doing the distinct. I'm not sure if the optimizer is good enough run the subqueries only once per name.
Using Group By with stuff function and get expected result
SELECT Name , STUFF((SELECT ',' + CAST(Value1 AS VARCHAR) FROM #MyTable T2 WHERE T1.Name = T2.Name FOR XML PATH('')),1,1,'') Value1
, STUFF((SELECT ',' + CAST(Value2 AS VARCHAR) FROM #MyTable T3 WHERE T1.Name = T3.Name FOR XML PATH('')),1,1,'') Value2 FROM #MyTable T1 GROUP BY Name

Reshape SQL date

following problem
I am using the command to join Names with project numbers
SELECT DDR_namen.vorname_nachname, DDR_Erfinder_final.pubnr
FROM DDR_namen
RIGHT JOIN DDR_Erfinder_final
ON DDR_namen.vorname_nachname=DDR_Erfinder_final.vorname_nachname
which gives me someting like this (small example)
vorname_nachname Pubnr (ID)
A. Heinrich 100
B. Müller 100
B. Müller 101
B. Müller 105
C. Krüger 120
C. Krüger 100
Now I want to reshape the data, so that the rows are unique and the ID are combined into a new collumn. Like this.
vorname_nachname Pubnr (ID)
A. Heinrich 100
B. Müller 100;101;105
C. Krüger 120;100
Anybody and ideas?
Try grouping like
select vorname_nachname,
(
SELECT STUFF((SELECT ';' + CAST(Pubnr AS VARCHAR(MAX))
FROM TestTable
WHERE vorname_nachname = t1.vorname_nachname
FOR XML PATH('') ), 1, 1, '')
) AS Pubnr
from TestTable t1
group by vorname_nachname
SQL Fiddle Demo : http://sqlfiddle.com/#!3/d0916/1
UPDATE : For JOIN you can use CTE like below :
;WITH CTE AS
(
SELECT DDR_namen.vorname_nachname, DDR_Erfinder_final.pubnr
FROM DDR_namen
RIGHT JOIN DDR_Erfinder_final
ON DDR_namen.vorname_nachname=DDR_Erfinder_final.vorname_nachname
)
select vorname_nachname,
(
SELECT STUFF((SELECT ';' + CAST(Pubnr AS VARCHAR(MAX))
FROM CTE
WHERE vorname_nachname = t1.vorname_nachname
FOR XML PATH('') ), 1, 1, '')
) AS Pubnr
from CTE t1
group by vorname_nachname

sql - getting the count of employee in each grade in a specific format

I am new to sql ,I have a table like this
Emp_id | Emp_NAME | EMP_GRADE
1 Test1 A1
2 Test2 A2
3 Test3 A3
4 Test4 A4
6 Test5 A1
7 Test6 A2
8 Test7 A3
I need to get the count of the employee in each grade , in which the final ouput will be
"2 - 2 - 2 - 1 " in a single column where output refers (Count of Employee in each Grade ie A1(2) - A2(2)- A3(2) -A4(1)) . can anyone give sql query for this. I hope we dont need cursor for this .
SELECT COUNT(Emp_id) FROM myTableName GROUP BY EMP_GRADE
Use:
DECLARE #Grades varchar(1000)
SELECT #Grades=coalesce(#Grades + ' ','') +Cast(COUNT(EMP_GRADE) as Varchar(2))+' -' From TableName
Group By EMP_GRADE
Select #Grades=SUBSTRING(#Grades,0,LEN(#Grades))
Select #Grades
Update:
SELECT #Grades=coalesce(#Grades + ' ','') +Cast(COUNT(t1.EMP_GRADE) as Varchar(2))+' -' From #tab1 t
Left Join #tab1 t1 On t1.EMP_GRADE= t.EMP_GRADE And t1.Emp_id= t.Emp_id
And t1.EMP_GRADE<>'A3' -- Replace conditions here
Group By t1.EMP_GRADE,t.EMP_GRADE
This should work:
SELECT EMP_GRADE, COUNT(EMP_Id) AS EMPS_COUNT
FROM TableName
GROUP BY EMP_GRADE
Hope that helps. Keep learning SQL.
SELECT STUFF((
SELECT ' - ' + CAST(COUNT(1) AS VARCHAR(max))
FROM myTable
GROUP BY EMP_GRADE
ORDER BY EMP_GRADE
FOR XML PATH('')
), 1, 3, '')
SQL Fiddle example
If you are filtering but still want to return results for every grade, you will need a self-join to get the full list of grades. Here's one way:
;WITH g AS (SELECT DISTINCT EMP_GRADE FROM myTable)
SELECT STUFF((
SELECT ' - ' + CAST(COUNT(t.Emp_id) AS VARCHAR(max))
FROM g
LEFT OUTER JOIN myTable t ON g.EMP_GRADE = t.EMP_GRADE
AND t.Emp_id % 2 = 1 --put your filter conditions here as part of the join
GROUP BY g.EMP_GRADE
ORDER BY g.EMP_GRADE
FOR XML PATH('')
), 1, 3, '')
SQL Fiddle example