SQL: Merging rows with comma separated values in columns - sql

We have a table like this:
Group | User | Team
-------------------
Grp1 | U1 | T1,T2
Grp1 | U2 | T1,T2,T3
Grp1 | U3 | T4
Grp2 | U4 | T2,T4
Grp2 | U5 | T5
I want to create a view which has the data like this:
Group | Teams
-------------
Grp1 | T1,T2,T3,T4
Grp2 | T2,T4,T5
Can somebody please help me? I tried doing few trail and errors and finally I am at a state where I am not even sure where to start from now

Here is my approach.
I am using cursor but you can do it in while loop also.
The only issue is that teams in new table are not distinct but I think you can play with string commands and it will sort the issue.
CREATE TABLE #tbl1 ([GROUP] varchar(20), [USER] VARCHAR(20), team VARCHAR(20))
INSERT INTO #tbl1 ( [GROUP], [USER],team )
VALUES ( 'Grp1', 'U1', 'T1,T2'), ( 'Grp1', 'U2', 'T3,T4'), ( 'Grp1', 'U3', 'T4'),
( 'Grp2', 'U1', 'T1,T2'), ( 'Grp2', 'U2', 'T3,T4')
CREATE TABLE #tbl2 ([GROUP] varchar(20), team VARCHAR(20))
DECLARE #ListOfteams VARCHAR(max)
DECLARE #grp VARCHAR(20)
DECLARE Curs CURSOR FAST_FORWARD
FOR
SELECT DISTINCT [GROUP]
FROM #tbl1 AS T
OPEN Curs
FETCH NEXT FROM Curs INTO #grp
WHILE ##FETCH_STATUS = 0
BEGIN
SET #ListOfteams= ''
SELECT #ListOfteams= #ListOfteams+ [Team] + ',' FROM #tbl1 AS CL WHERE CL.[GROUP] = #grp ORDER BY [CL].[USER]
INSERT INTO #tbl2 ( [GROUP],team )
SELECT DISTINCT #grp,
SUBSTRING(#ListOfteams, 1, LEN(#ListOfteams)-1)
FROM #tbl1 AS T
WHERE T.[GROUP] = #grp;
FETCH NEXT FROM Curs INTO #grp
END
CLOSE Curs
DEALLOCATE Curs;
SELECT *
FROM #tbl2 AS T
Here is ow it works: http://rextester.com/BYK57259
+-------+----------------+
| GROUP | team |
+-------+----------------+
| Grp1 | T1,T2,T3,T4,T4 |
| Grp2 | T1,T2,T3,T4 |
+-------+----------------+

Here is how am doing this, first convert the comma seperated team names to individual rows based on their groups and then take the unique team grp and team names. Now apply the listagg function for the unique team names to achieve the result.
with t as
(
select 'Grp1' grp,'U1' user1,'T1,T2' team from dual
union all
select 'Grp1','U2','T1,T2,T3' from dual
union all
select 'Grp1','U3','T4' from dual
union all
select 'Grp2','U4','T2,T4' from dual
union all
select 'Grp2','U5','T5' from dual
),
g as
(
select distinct grp, regexp_substr(team,'[^,]+',1,level) team1
from t
connect by level <= LENGTH(REGEXP_REPLACE(team, '[^,]+')) + 1
--group by grp
)
select grp,listagg(team1,',') within group (order by team1) from g
group by grp;
THis is the output I got
+----+-------------------------------------------+
|GRP ||Teams
+----+-------------------------------------------+
|Grp1|T1,T2,T3,T4 |
|Grp2|T2,T4,T5 |
+----+-------------------------------------------+

Related

How to divide a name into rows in sql server?

I want a name to be passed as a parameter to a stored procedure and when i execute this stored procedure, it should divide the name into rows.
For example if pass 'Peter', output should be
P
e
t
e
r
The fastest method to do this would be with a Tally and SUBSTRING. I assume that the name won't be longer than 100 characters, and that (as it's a name) it's an nvarchar rather than a varchar. You can then do something like this:
DECLARE #Name nvarchar(100) = N'Peter';
WITH N AS(
SELECT N
FROM (VALUES(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL))N(N)),
Tally AS(
SELECT TOP(DATALENGTH(#Name) / 2) --If a varchar, remove the / 2
ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS I
FROM N N1, N N2)
SELECT SUBSTRING(#Name,T.I,1) AS C
FROM Tally T;
declare #input varchar(max);
set #input = 'Peter'
declare #table TABLE (char varchar(1));
while (LEN(#input)> 0)
begin
insert into #table select substring(#input,1,1)
select #input = RIGHT(#input,Len(#input)-1)
end
select * from #table
GO
| char |
| :--- |
| P |
| e |
| t |
| e |
| r |
declare #T table
(
ID int identity,
names varchar(10)
)
insert into #T
select 'Peter'
;with cte as
(
select ID,
left(names, 1) as Data,
stuff(names, 1, 1, '') as remains
from #T
where len(names) > 0
union all
select ID,
left(remains, 1) as Data,
stuff(remains, 1, 1, '') as remains
from cte
where len(remains) > 0
)
select ID,
Data
from cte
order by ID
ID | Data
-: | :---
1 | P
1 | e
1 | t
1 | e
1 | r
db<>fiddle here

How to get child of master entry from sql table

I have a table for ledgers. It consists of MasterID as unique ID and a parentID for joining.
Model of my table
As in the image my parent ledger is 'A'. 'B' is direct child of 'A'. 'C' is direct child of 'B' and 'D' is direct child of 'C'.
I want a select query to select all childs of 'A'. i.e, result will be B,C,D.
I am beginner in SQL.
I tried some while loop for this, but only direct child was accessible. I am not able make a logic for the requirement.
Thank you in advance.
That'as a typical hierarchical query. Consider the solution that makes use of a recursive cte:
with cte as (
select masterID rootID, masterID, name, parentid
from mytable
where parentID is null
union all
select c.rootID, t.masterID, t.name, t.parentID
from mytable t
inner join cte c on c.masterID = t.parentID
)
select * from cte
As commented by The Impaler, you can change starting condition where parentID is null to the id of another node if needed.
With your sample data, this yields:
rootID | masterID | name | parentid
-----: | -------: | :--- | -------:
1 | 1 | A | null
1 | 2 | B | 1
1 | 3 | C | 2
1 | 4 | D | 3
Note that I kept track of the id of the root object, so it is easier to understand what is going on if there are several roots in your data.
You can also use the root to generate a flat list of children:
with cte as (
select masterID rootID, masterID, name, parentid
from mytable
where parentID is null
union all
select c.rootID, t.masterID, t.name, t.parentID
from mytable t
inner join cte c on c.masterID = t.parentID
)
select rootID, string_agg(masterID, ',') childrenID from cte group by rootID
rootID | childrenID
-----: | :---------
1 | 1,2,3,4
Demo on DB Fiddle
Here is an option that uses HierarchyID
The range keys (R1 / R2) in the final select are optional.
Example
Declare #YourTable table (MasterID int ,name varchar(50), ParentID int);
Insert Into #YourTable values
( 1, 'A', null),
( 2, 'B', 1),
( 3, 'C', 2),
( 4, 'D', 3)
Declare #Top int = 2 --<< Sets top of Hier Try NULL for entire hier
Declare #Nest varchar(25) = '|-----' --<< Optional: Added for readability
;with cteP as (
Select MasterID
,ParentID
,Name
,HierID = convert(hierarchyid,concat('/',MasterID,'/'))
From #YourTable
Where IsNull(#Top,-1) = case when #Top is null then isnull(ParentID ,-1) else MasterID end
Union All
Select MasterID = r.MasterID
,ParentID = r.ParentID
,Name = r.Name
,HierID = convert(hierarchyid,concat(p.HierID.ToString(),r.MasterID,'/'))
From #YourTable r
Join cteP p on r.ParentID = p.MasterID)
Select R1 = Row_Number() over (Order By HierID)
,R2 = (Select count(*) From cteP where HierID.ToString() like A.HierID.ToString()+'%')
,Lvl = HierID.GetLevel()
,MasterID
,ParentID
,Name = Replicate(#Nest,HierID.GetLevel()-1) + Name
,HierID
,HierID_String = HierID.ToString()
From cteP A
Order By A.HierID
Returns

How to populate given SQL records to all UserID

I have a table in this form:
id | firstname | lastname | userid
---+-----------+------------------------
1 | john | smith | 545868-5434-343435-35353
2 | adam | finger | 545868-5434-343435-35353
3 | teri | marti | 545868-5434-343435-35353
4 | pei | port | 545868-5434-343435-35353
In the DB i have many userid i need to populate the very same firstname and lastname to all userid found in the Database
Here is my SQl Query
SELECT
cID, c.firstname,c.lastname,
[s].UserID,c.OwnerID
FROM
Customer INNER JOIN [s] ON c.OwnerID = [s].UserID AND c.AssignedtoID =
[s].UserID AND c.CreatedByUserID = [s].UserID
AssignedtoID is the same as UserID
is this helpful for you.?
Create table #tmpCustomer (id int, firstname VARCHAR(50),lastname VARCHAR(50),userid VARCHAR(100))
INSERT INTO #tmpCustomer
SELECT 1, 'john','smith','545868-5434-343435-35353'
union
SELECT 2,'adam','finger','545868-5434-343435-35353'
union
SELECT 3,'teri','marti','545868-5434-343435-35353'
union
SELECT 4, 'pei','port','545868-5434-343435-35353'
union
SELECT 5, 'abc','xyz','545868-5434-343435-35354'
union
SELECT 6, 'mno','ert','545868-5434-343435-35354'
--select * from #tmpCustomer
;with cte1 AS(Select row_number()over(partition by userid order by id) rn,* from #tmpCustomer ),
cte2 AS (select * from cte1 where rn=1 )
update t
set t.firstname=c.firstname
from #tmpCustomer t
JOIN cte2 c on t.userid=c.userid
select * from #tmpCustomer
drop table #tmpCustomer
i don't know if i good understand your question, try solution posted below
DECLARE #cust as table (firstname varchar(20),lastname Varchar(20))
Insert #cust
values
('Suzan','Smith')
declare #id as table (id int identity,anything varchar(20),row_inserted datetime2 default (cast(sysdatetime() as datetime2)))
INSERT #id
(anything,row_inserted)
SELECT 'x' ,'20180305'
union all
select 'y','20180305'
union all
select 'z','20180305'
select s.id,c.firstname,
c.lastname
from #id as s
cross join #cust as c

Remove duplicate rows from joined table

I have following sql query
SELECT m.School, c.avgscore
FROM postswithratings c
join ZEntrycriteria on c.fk_postID= m.schoolcode
Which provide following result
School| avgscore
xyz | 5
xyz | 5
xyz | 5
abc | 3
abc | 3
kkk | 1
My question is how to remove those duplicates and get only following.
School| avgscore
xyz | 5
abc | 3
kkk | 1
I tried with
SELECT m.School, c.avgscore
FROM postswithratings c
join ZEntrycriteria on c.fk_postID= m.schoolcode
group by m.School
But it gives me following error
"Column 'postswithratings.avgscore' is invalid in the select list
because it is not contained in either an aggregate function or the
GROUP BY clause."
No need to make things complicated. Just go with:
SELECT m.School, c.avgscore
FROM postswithratings c
join ZEntrycriteria on c.fk_postID= m.schoolcode
group by m.School, c.avgscore
or
SELECT DISTINCT m.School, c.avgscore
FROM postswithratings c
join ZEntrycriteria on c.fk_postID= m.schoolcode
You have to only add distinct keyword like this :-
SELECT DISTINCT m.School, c.avgscore
FROM postswithratings c
join ZEntrycriteria on c.fk_postID= m.schoolcode
CREATE TABLE #Table2
([School] varchar(3), [avgscore] int)
INSERT INTO #Table2
([School], [avgscore])
VALUES
('xyz', 5),
('xyz', 5),
('xyz', 5),
('abc', 3),
('abc', 3),
('kkk', 1)
;
SELECT SCHOOL,AVGSCORE FROM (SELECT *,ROW_NUMBER() OVER( PARTITION BY [AVGSCORE] ORDER BY (SELECT NULL)) AS RN FROM #TABLE2)A
WHERE RN=1
ORDER BY AVGSCORE
-------
;WITH CTE AS
(SELECT *,ROW_NUMBER() OVER( PARTITION BY [AVGSCORE] ORDER BY (SELECT NULL)) AS RN FROM #TABLE2)
SELECT SCHOOL,AVGSCORE FROM CTE WHERE RN=1
output
SCHOOL AVGSCORE
kkk 1
abc 3
xyz 5
Using the DISTINCT keyword will make sql use sets instead of multisets. So values only appear once
This will delete the Duplicate rows (Only Duplicate)
Schema:
CREATE TABLE #TAB (School varchar(5) , avgscore int)
INSERT INTO #TAB
SELECT 'xyz', 5
UNION ALL
SELECT 'xyz', 5
UNION ALL
SELECT 'xyz', 5
UNION ALL
SELECT 'abc', 3
UNION ALL
SELECT 'abc', 3
UNION ALL
SELECT 'kkk', 1
Now use CTE as your Tempprary View and delete the data.
;WITH CTE AS(
SELECT ROW_NUMBER() OVER (PARTITION BY School,avgscore ORDER BY (SELECT 1)) DUP_C,
School, avgscore FROM #TAB
)
DELETE FROM CTE WHERE DUP_C>1
Now do check #TAB, the data will be
+--------+----------+
| School | avgscore |
+--------+----------+
| xyz | 5 |
| abc | 3 |
| kkk | 1 |
+--------+----------+
you only use group by if you're using aggregated function, eg. max. sum, avg
in that case,
SELECT Distinct(m.School), c.avgscore
FROM postswithratings c
join ZEntrycriteria on c.fk_postID= m.schoolcode

How do you order a group of records then insert their order placement too?

I have a table of logs that contain a ID and TIMESTAMP. I want to ORDER BY ID and then TIMESTAMP.
For example, this is what the result set would look like:
12345 05:40
12345 05:50
12345 06:22
12345 07:55
12345 08:33
Once that's done, I want to INSERT a order value in a third column that signifies it's placement in the group from earliest to latest.
So, you would have something like this:
12345 05:40 1 <---First entry
12345 05:50 2
12345 06:22 3
12345 07:55 4
12345 08:33 5 <---Last entry
How can I do that in a SQL statement? I can select the data and ORDER BY ID, TIMESTAMP. But, I can't seem to INSERT a order value based on the groupings. :(
Try this update not an insert:
Fiddle demo here:
;with cte as(
select id, yourdate, row_number() over(order by id,yourdate) rn
from yourTable
)
Update ut Set thirdCol = rn
From yourTable ut join cte on ut.Id = cte.id and ut.yourdate = cte.yourdate
NOTE: if you need to get the thirdColumn updated per id basis, please partition your rownumber by using row_number() over (partition by id, order by order by id,yourdate)
Results:
| ID | YOURDATE | THIRDCOL |
|-------|----------|----------|
| 12345 | 05:40 | 1 |
| 12345 | 05:50 | 2 |
| 12345 | 06:22 | 3 |
| 12345 | 07:55 | 4 |
| 12345 | 08:33 | 5 |
Using a derived table and an update.
IF OBJECT_ID('tempdb..#TableOne') IS NOT NULL
begin
drop table #TableOne
end
CREATE TABLE #TableOne
(
SomeColumnA int ,
LetterOfAlphabet varchar(12) ,
PositionOrdinal int not null default 0
)
INSERT INTO #TableOne ( SomeColumnA , LetterOfAlphabet )
select 123 , 'x'
union all select 123 , 'b'
union all select 123 , 'z'
union all select 123 , 't'
union all select 123 , 'c'
union all select 123 , 'd'
union all select 123 , 'e'
union all select 123 , 'a'
Select 'pre' as SpaceTimeContinium , * from #TableOne order by LetterOfAlphabet
Update
#TableOne
Set PositionOrdinal = derived1.rowid
From
( select SomeColumnA , LetterOfAlphabet , rowid = row_number() over (order by LetterOfAlphabet asc) from #TableOne innerT1 )
as derived1
join #TableOne t1
on t1.LetterOfAlphabet = derived1.LetterOfAlphabet and t1.SomeColumnA = derived1.SomeColumnA
Select 'post' as SpaceTimeContinium, * from #TableOne order by LetterOfAlphabet
IF OBJECT_ID('tempdb..#TableOne') IS NOT NULL
begin
drop table #TableOne
end
To get the order you desire without doing an insert and an update, you can set your clustered index to handle it for you. The example below creates a clustered primary key.
To do this you must remove any clustered index that you already have on the table because you can only have one clustered index per table.
CREATE TABLE dbo.Table_1
(
ID int NOT NULL,
DTStamp datetime NOT NULL
)
ALTER TABLE dbo.Table_1 ADD CONSTRAINT
PK_Table_1 PRIMARY KEY CLUSTERED
(
ID,
DTStamp
)
Insert some random data to test with...
INSERT INTO [dbo].[Table_1]([ID],[DTStamp])VALUES(12346,getdate());
INSERT INTO [dbo].[Table_1]([ID],[DTStamp])VALUES(12346,dateadd(mi,1,getdate()));
INSERT INTO [dbo].[Table_1]([ID],[DTStamp])VALUES(12346,dateadd(mi,2,getdate()));
INSERT INTO [dbo].[Table_1]([ID],[DTStamp])VALUES(12346,dateadd(mi,3,getdate()));
INSERT INTO [dbo].[Table_1]([ID],[DTStamp])VALUES(12346,dateadd(mi,4,getdate()));
INSERT INTO [dbo].[Table_1]([ID],[DTStamp])VALUES(12340,dateadd(mi,5,getdate()));
INSERT INTO [dbo].[Table_1]([ID],[DTStamp])VALUES(12340,dateadd(mi,6,getdate()));
INSERT INTO [dbo].[Table_1]([ID],[DTStamp])VALUES(12340,dateadd(mi,7,getdate()));
INSERT INTO [dbo].[Table_1]([ID],[DTStamp])VALUES(12340,dateadd(mi,8,getdate()));
INSERT INTO [dbo].[Table_1]([ID],[DTStamp])VALUES(12344,dateadd(mi,1,getdate()));
INSERT INTO [dbo].[Table_1]([ID],[DTStamp])VALUES(12344,dateadd(mi,2,getdate()));
INSERT INTO [dbo].[Table_1]([ID],[DTStamp])VALUES(12344,dateadd(mi,3,getdate()));
INSERT INTO [dbo].[Table_1]([ID],[DTStamp])VALUES(12344,dateadd(mi,4,getdate()));
INSERT INTO [dbo].[Table_1]([ID],[DTStamp])VALUES(12344,dateadd(mi,5,getdate()));
INSERT INTO [dbo].[Table_1]([ID],[DTStamp])VALUES(12344,dateadd(mi,6,getdate()));
INSERT INTO [dbo].[Table_1]([ID],[DTStamp])VALUES(12344,dateadd(mi,7,getdate()));
INSERT INTO [dbo].[Table_1]([ID],[DTStamp])VALUES(12344,dateadd(mi,8,getdate()));
Now query your table and check out the order...
SELECT [ID] ,[DTStamp] FROM [Table_1]
If you need the order to display in a query, you can add the row number with an over clause.
SELECT [ID] ,[DTStamp],row_number() over (partition by [ID] order by [ID] ,[DTStamp]) as SortOdr FROM [Table_1]