Join Vertical & Horizontal table in SQL Server using Pivot - sql

I want to join two tables and combine into one but problem is one table is in Horizontal Format and other in Vertical format.
below is the table structure and join will be on Employeeid:
Table 1 : EmpDetail
ID | CODE | Name
-- |--------| ---
1 | 1008M | ABC
2 | 1039E | XYZ
3 | 1040E | TYS
Table 2 : EmpCustomeDetail
EmpID | FiledName | FieldValue
-- |-------- | ---
1 | FlD1 | temp1
1 | FlD2 | temp2
1 | FlD3 | temp3
2 | FlD1 | temp1
3 | FLD4 | temp6
Desired Output Required :
EmpID | Code | Name | Fld1 | Fld2 | Fld3 | Fld4
-- |---- | ------| --- | ---- |---- |----
1 | 1008M | ABC | temp1 | temp2 | temp3 | null
2 | 1039E | XYZ | temp1 | null | null | null
3 | 1040E | TYS | null | null | null | temp6
I had tried using Pivot Query but it is not giving exact output which i requried
Below is the query so far i have tried
SELECT A.*
FROM (
SELECT
e.Id,
e.code,
e.Fname,
FROM EmpDetail e
LEFT JOIN (
SELECT *
FROM (
SELECT
d.CustomeFieldName
, c.ComboValue
, d.EmployeeId
FROM EmpCustomeDetail d
) src
PIVOT (
MAX(FieldValue)
) src2
) c ON e.Id = c.EmployeeId
) A

Here are two statements:
The first is a simple PIVOT. You can use it, in case you know all Fieldnames (btw: there's a typo in your sample) in advance.
The second is roughly the same statement, but the column names are taken dynamically. This will work with (almost) any count and with different namings.
First a mock-up-test-scenraio
CREATE TABLE DummyEmpDetail (ID INT,CODE VARCHAR(10),Name VARCHAR(100));
INSERT INTO DummyEmpDetail VALUES
(1,'1008M','ABC')
,(2,'1039E','XYZ')
,(3,'1040E','TYS');
CREATE TABLE DummyEmpCustomeDetail (EmpID INT,FiledName VARCHAR(100),FieldValue VARCHAR(100));
INSERT INTO DummyEmpCustomeDetail VALUES
(1,'FlD1','temp1')
,(1,'FlD2','temp2')
,(1,'FlD3','temp3')
,(2,'FlD1','temp1')
,(3,'FLD4','temp6');
--The static PIVOT statement
SELECT p.EmpID
,p.Name
,p.CODE
,p.Fld1
,p.Fld2
,p.Fld3
p,Fld4
FROM
(
SELECT e.CODE,e.Name,ec.*
FROM DummyEmpDetail AS e
INNER JOIN DummyEmpCustomeDetail AS ec ON e.ID=ec.EmpID
) AS tbl
PIVOT
(
MAX(FieldValue) FOR FiledName IN(Fld1,Fld2,Fld3,Fld4)
) AS p;
--The dynamic PIVOT statement
DECLARE #colNames VARCHAR(MAX)=
(
STUFF
(
(
SELECT DISTINCT ',' + QUOTENAME(FiledName) FROM DummyEmpCustomeDetail
FOR XML PATH('')
),1,1,''
)
);
DECLARE #command VARCHAR(MAX)=
'SELECT p.EmpID
,p.Name
,p.CODE
,' + #colNames +
' FROM
(
SELECT e.CODE,e.Name,ec.*
FROM DummyEmpDetail AS e
INNER JOIN DummyEmpCustomeDetail AS ec ON e.ID=ec.EmpID
) AS tbl
PIVOT
(
MAX(FieldValue) FOR FiledName IN(' + #colnames + ')
) AS p;';
EXEC (#command);
GO
DROP TABLE DummyEmpCustomeDetail;
DROP TABLE DummyEmpDetail;
Both lead to the same result...

Try like below. If values of fieldname will not be static then you should use dynamic sql.
SELECT EMPID,
CODE,
NAME,
FLD1,
FLD2,
FLD3,
FLD4
FROM EmpDetail C
JOIN (SELECT A.*
FROM EmpCustomeDetail
PIVOT ( MIN([FIELDVALUE])
FOR [FILEDNAME] IN([FLD1],
[FLD3],
[FLD2],
FLD4) )A)B
ON C.ID = B.[EMPID]

No need for sub-queries.
select e.*,FlD1,FlD2,FlD3,FlD4
from EmpDetail e
left join EmpCustomeDetail
pivot (max(FieldValue) for FiledName in (FlD1,FlD2,FlD3,FlD4)) ecd
on ecd.EmpID = e.ID
+----+-------+------+-------+-------+-------+-------+
| ID | CODE | Name | FlD1 | FlD2 | FlD3 | FlD4 |
+----+-------+------+-------+-------+-------+-------+
| 1 | 1008M | ABC | temp1 | temp2 | temp3 | NULL |
+----+-------+------+-------+-------+-------+-------+
| 2 | 1039E | XYZ | temp1 | NULL | NULL | NULL |
+----+-------+------+-------+-------+-------+-------+
| 3 | 1040E | TYS | NULL | NULL | NULL | temp6 |
+----+-------+------+-------+-------+-------+-------+

Related

SQL query to get values of schema table row value in data table's column value

There are 2 database tables as shown below, Table_1 & Table_2.
Table_1 column index is match with id value of table_2.
Table_1
| date |city_1 | city_2 | ... | city_100 |
+-----------+-------+--------+-----+----------+
| 20.02.2018| 2 | 44 | ... | 98 |
| 21.02.2018| 1 | 25 | ... | 17 |
| ... | ... | ... | ... | ... |
Table_2
| id | name |
+-------+---------+
| 1 | newyork |
| 2 | london |
| ... | ... |
| 100 | istanbul|
Expected result is below
| date | city_1 | city_2 | ... | city_100 |
+-----------+------------+------------+-------+-----------+
| 20.02.2018| london | india | ... | canada |
| 21.02.2018| newyork | srilanka | ... | austria |
| ... | ... | ... | ... | ... |
What is the SQL query to get result above?
Thanks
You have to join Table_1 with Table_2 as many as many city columns you have, like this:
SELECT
t1.date, c1.name, c2.name, c3.name, ... c100.name
FROM Table_1 AS t1
JOIN Table_2 AS c1 ON t1.city_1 = c1.id
JOIN Table_2 AS c2 ON t1.city_2 = c2.id
JOIN Table_2 AS c3 ON t1.city_3 = c3.id
...
JOIN Table_2 AS c100 ON t1.city_100 = c100.id
If you are using Postgres, you can do something like this:
select data.id,
x.names[1] as city_name_1,
x.names[2] as city_name_2,
x.names[3] as city_name_3
from data
join lateral (
select array_agg(ct.name order by e.idx) as names
from unnest(array[city_1, city_2, city_3]) with ordinality as e(id, idx)
left join cities ct on e.id = ct.id
) x on true;
You still need to list all city "names" twice: once in the array inside the derived table, and once on the outside to get each name in a separate column.
If you can also live with a comma separated list of names you could use something like this:
select d.id,
string_agg(x.name, ',' order by x.idx) as names
from data d
join lateral (
select ct.name, e.idx
from unnest(array[city_1, city_2, city_3]) with ordinality as e(id, idx)
left join cities ct on e.id = ct.id
) x on true
group by data.id;
Or you can aggregate all the names into a single JSON value, then you don't need to hardcode any column name:
select d.id, x.names
from data d
join lateral (
select jsonb_object_agg(j.col, ct.name) as names
from cities ct
left join jsonb_each_text(to_jsonb(d) - 'id') as j(col, id) on j.id::int = ct.id
where j.col is not null
) x on true;
(I replaced your date column with an id column in my example)
Online example: https://rextester.com/NEBGX64778
In SQL Server (you haven't specified a DBMS), you could do something like this.
Sample table structure and data
CREATE TABLE sample (
date date, city_1 int, city_2 int, city_3 int, city_n int
);
INSERT INTO sample
VALUES
('2018-02-20', 4, 44, 98, ..),
('2018-02-21', 1, 25, 17, ..);
CREATE TABLE names (
id int,
name varchar(50)
);
INSERT INTO names
VALUES
(1, 'NewYork'),
(4, 'London'),
(17, 'Istanbul'),
(25, 'Colombo'),
(44, 'Vienna'),
(98, 'Helsinki');
Query 01
SELECT *
FROM (SELECT t1.date, t1.city, names.NAME
FROM (SELECT date, upvt.city, upvt.id
FROM sample
UNPIVOT ( id
FOR city IN (city_1, city_2, city_3, city_n) ) upvt) t1
INNER JOIN names ON t1.id = names.id) t2
PIVOT ( Min(NAME)
FOR city IN (city_1, city_2, city_3, city_n) ) AS pvt;
Query 01: Output
+----------------------+----------+----------+----------+----------+
| date | city_1 | city_2 | city_3 | city_n |
+----------------------+----------+----------+----------+----------+
| 20/02/2018 00:00:00 | London | Vienna | Helsinki | .... |
| 21/02/2018 00:00:00 | NewYork | Colombo | Istanbul | .... |
+----------------------+----------+----------+----------+----------+
Online Demo: https://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=a14672b53b457d4ae59e6c9076cd9755
But if you don't want to write column names (city_1, city_2, city_n), then you can use this dynamic query.
Query 02: Get the column names
Example: city_1, city_2, city_n
SELECT column_name
FROM information_schema.columns
WHERE table_name = N'sample'
AND column_name LIKE 'city_%';
Query 02: Output
+-------------+
| column_name |
+-------------+
| city_1 |
| city_2 |
| city_3 |
+-------------+
Query 03: Dynamic Query
DECLARE #cols AS NVARCHAR(max),
#query AS NVARCHAR(max);
SET #cols = STUFF(( SELECT distinct ',' + QUOTENAME(column_name)
FROM information_schema.columns
WHERE table_name = N'sample' AND column_name LIKE 'city_%'
FOR XML PATH(''), TYPE ).value('.', 'NVARCHAR(MAX)') ,1,1,'');
SET #query = 'SELECT *
FROM (SELECT t1.date, t1.city, names.NAME
FROM (SELECT date, upvt.city, upvt.id
FROM sample
UNPIVOT (id
FOR city IN ('+ #cols +')) upvt) t1
INNER JOIN names ON t1.id = names.id) t2
PIVOT (Min(NAME)
FOR city IN ('+ #cols +')) AS pvt';
--select #query;
--select #cols;
execute(#query);
Query 03: Output
+----------------------+----------+----------+----------+
| date | city_1 | city_2 | city_3 |
+----------------------+----------+----------+----------+
| 20/02/2018 00:00:00 | London | Vienna | Helsinki |
| 21/02/2018 00:00:00 | NewYork | Colombo | Istanbul |
+----------------------+----------+----------+----------+
Online Demo: https://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=e2d7f10a22a3e11044fc552ff73b14c5

Insert on a child table and update FK on parent

I have a parent table with the following structure and data:
---------------------------------------------
| Id | TranslationId | Name |
---------------------------------------------
| 1 | NULL | Image1.jpg |
| 2 | NULL | Image7.jpg |
| 3 | NULL | Picture_Test.png |
---------------------------------------------
And the empty child table which holds the translated images:
-------------------------------------------------------------------------
| Id | De | Fr | En |
-------------------------------------------------------------------------
| | | | |
-------------------------------------------------------------------------
Now I'm looking for a single query statement or at least few queries which I can run without any further programming. Doing this job with scripting or programming would be easy but I have often situations where I need this kind of insert / update. And developing each time a small console app is not feasible.
At the end the two tables should look like this:
---------------------------------------------
| Id | TranslationId | Name |
---------------------------------------------
| 1 | 28 | NULL |
| 2 | 29 | NULL |
| 3 | 30 | NULL |
---------------------------------------------
-------------------------------------------------------------------------
| Id | De | Fr | En |
-------------------------------------------------------------------------
| 28 | Image1.jpg | NULL | NULL |
| 29 | Image7.jpg | NULL | NULL |
| 30 | Picture_Test.png | NULL | NULL |
-------------------------------------------------------------------------
Thank you for any advice.
You could do it something like the below :
INSERT INTO Child
(
Id
,De
,Fr
,En
)
OUTPUT Inserted.Id INTO #Temp
SELECT Id
,De
,Fr
,En
FROM #Values --If you are using a table type to insert into the Child table as a set based approach
;WITH CTE
AS
(
SELECT ROW_NUMBER() OVER(ORDER BY Id) AS Rnk
,Id
FROM #Temp
)
,CTE1 AS
(
SELECT ROW_NUMBER() OVER(ORDER BY Id) AS Rnk
,*
FROM Parent
)
UPDATE cte1
SET TranslationId = cte.Id
FROM CTE1 cte1
JOIN CTE cte ON cte.Rnk = cte1.Rnk
Demo, assuming Name is unique in the first table
create table tab1 (
id int identity
,TranslationId int null
,Name nvarchar(max) null
);
insert tab1 (Name)
values
('Image1.jpg')
,('Image7.jpg')
,('Picture_Test.png')
,(null)
create table tab2 (
id int identity (100,1)
,De nvarchar(max) null
,Fr nvarchar(max) null
,En nvarchar(max) null
);
-- Update them
declare #map table(
name nvarchar(max)
,ref int
);
insert tab2 (de)
output inserted.De, inserted.id
into #map(Name, ref)
select Name
from tab1 src
where Name is not null and not exists (select 1 from tab2 t2 where t2.De = src.Name);
update t1 set TranslationId = ref, Name = null
from tab1 t1
join #map m on t1.Name = m.Name;
select * from tab1;
select * from tab2;
I figured out in the meantime how to do it. Applied on the database, the query looks like this:
DECLARE #Temp TABLE (ImageId INT, Id INT)
MERGE INTO Translation USING
(
SELECT Image.Name AS Name, Image.Id AS ImageId
FROM Candidate
INNER JOIN Candidacy ON Candidate.Id = Candidacy.CandidateId
INNER JOIN Election ON Candidacy.ElectionId = Election.Id
INNER JOIN SmartVoteCandidate ON Candidate.Id = SmartVoteCandidate.CandidateId
INNER JOIN Image ON SmartVoteCandidate.SpiderImageId = Image.Id
WHERE Election.Id = 1575) AS temp ON 1 = 0
WHEN NOT MATCHED THEN
INSERT (De)
VALUES (temp.Name)
OUTPUT temp.ImageId, INSERTED.Id
INTO #Temp (ImageId, Id);
UPDATE Image
SET Image.TranslationId = t.Id, Name = NULL
FROM #Temp t
WHERE Image.Id = t.ImageId
The solution is heavily inspired by
Is it possible to for SQL Output clause to return a column not being inserted?
Using a join in a merge statement

Pivot Result Creation from SQL query using two tables and join

I have my first table like this, it contains platform and their platform code
+-----------+------+
| platforms | code |
+-----------+------+
| java | 1 |
| .net | 2 |
| perl | 3 |
+-----------+------+
My second table contains columns as shown below.
+-------+------+------+------+
| pname | code | year | deve |
+-------+------+------+------+
| a | 1 | 2018 | abia |
| b | 1 | 2017 | arun |
| c | 2 | 2018 | abia |
| d | 3 | 2017 | arun |
| e | 2 | 2017 | arun |
| f | 3 | 2018 | abia |
+-------+------+------+------+
Result expected in Pivot Format like this:
+-----+-------+------+------+------+
| year| deve | .net | java | perl |
+-----+-------+------+------+------+
| 2018| abia | 1 | 1 | 1 |
| 2017| arun | 1 | 1 | 1 |
+-----+-------+------+------+------+
Try this Pivot Script which meets your expectation
DECLARE #Table AS TABLE
(platforms VARCHAR(20), code INT)
INSERT INTO #Table
SELECT 'java', 1 UNION ALL
SELECT '.net', 2 UNION ALL
SELECT 'perl', 3
DECLARE #Table2 AS TABLE
(pname VARCHAR(20), code INT,[year] INT, deve VARCHAR(20))
INSERT INTO #Table2
SELECT 'a',1,2018,'abia' UNION ALL
SELECT 'b',1,2017,'arun' UNION ALL
SELECT 'c',2,2018,'abia' UNION ALL
SELECT 'd',3,2017,'arun' UNION ALL
SELECT 'e',2,2017,'arun' UNION ALL
SELECT 'f',3,2018,'abia'
SELECT [year],deve,MAX([java]) AS [java],MAX([.net]) AS [.net],MAX([perl] ) AS [perl]
FROM
(
SELECT platforms,CASE WHEN pname IS NOT NULL THEN 1 ELSE NUll END AS pname ,A.code ,deve,[year] FROM #Table2 A
INNER JOIN #Table B
ON A.code=b.code
)
AS SRC
PIVOT
(
MAX(pname) FOR platforms IN( [java],[.net],[perl] )
) AS PVT
GROUP BY [year],deve
Result
year deve java .net perl
------------------------------------
2018 abia 1 1 1
2017 arun 1 1 1
Pivot using dynamic sql approach as it fits when the columns to be added dynamically to get the specific result
CREATE TABLE #Table
(platforms VARCHAR(20), code INT)
INSERT INTO #Table
SELECT 'java', 1 UNION ALL
SELECT '.net', 2 UNION ALL
SELECT 'perl', 3
CREATE TABLE #Table2
(pname VARCHAR(20), code INT,[year] INT, deve VARCHAR(20))
INSERT INTO #Table2
SELECT 'a',1,2018,'abia' UNION ALL
SELECT 'b',1,2017,'arun' UNION ALL
SELECT 'c',2,2018,'abia' UNION ALL
SELECT 'd',3,2017,'arun' UNION ALL
SELECT 'e',2,2017,'arun' UNION ALL
SELECT 'f',3,2018,'abia'
DECLARE #Columns nvarchar(max),
#IsnullColumns nvarchar(max),
#Sql nvarchar(max)
SELECT #Columns= STUFF((SELECT ', '+QUOTENAME(platforms) FROM #Table FOR XML PATH ('')),1,1,'')
SELECT #IsnullColumns=STUFF((SELECT ', '+'MAX('+QUOTENAME(platforms)+') AS ' +QUOTENAME(platforms) FROM #Table FOR XML PATH ('')),1,1,'')
SET #Sql='
SELECT[year],deve, '+#IsnullColumns+'
FROM
( SELECT platforms,
CASE WHEN pname IS NOT NULL THEN 1 ELSE NUll END AS pname ,A.code ,deve,[year]
FROM #Table2 A
INNER JOIN #Table B
ON A.code=b.code
) AS SRC
PIVOT
(MAX(pname) FOR platforms IN('+#Columns+')
) AS PVT
GROUP BY [year],deve'
PRINT #Sql
EXEC (#Sql)
Result
year deve java .net perl
------------------------------------
2018 abia 1 1 1
2017 arun 1 1 1

Get nth level on self-referencing table

I have a self-referencing table which has at max 5 levels
groupid | parentid | detail
--------- | --------- | ---------
Group A | Highest | nope
Group B | Group A | i need this
Highest | NULL | nope
Group C | Group B | nope
Group D | Group C | nope
I have a transaction table which lookups to the groupid on the table above to retrieve the detail value where groupid = Group B. The values of the groupid on the transaction table is only between Group B to D and will never go any higher.
txnid | groupid | desired | desired
--------- | --------- | --------- | ---------
1 | Group D | Group B | i need this
2 | Group B | Group B | i need this
3 | Group C | Group B | i need this
4 | Group B | Group B | i need this
How should my T-SQL script be like to attain the desired column? I can left join to the self referencing table multiple times to get until group B it's not consistent on how many time I need to join back.
Greatly appreciate any thoughts!
Still not clear to me how do you know which is the GROUP B, I suppose it's the record where the parent of it parent is null.
create table org(groupid char(1), parentid char(1), details varchar(20));
insert into org values
('a', null, 'nope'),('b', 'a', 'I need this'),('c', 'b', 'nope'),('d', 'c', 'nope'),('e', 'd', 'nope');
create table trans(id int, groupid char(1));
insert into trans values
(1, 'b'),(2, 'c'),(3, 'c'),(4, 'd'),(5, 'e');
GO
10 rows affected
with all_levels as
(
select ob.groupid groupid_b, oc.groupid groupid_c,
od.groupid groupid_d, oe.groupid groupid_e,
ob.details
from org ob
inner join org oc
on oc.parentid = ob.groupid
inner join org od
on od.parentid = oc.groupid
inner join org oe
on oe.parentid = od.groupid
where ob.parentid is not null
) select * from all_levels;
GO
groupid_b | groupid_c | groupid_d | groupid_e | details
:-------- | :-------- | :-------- | :-------- | :----------
b | c | d | e | I need this
--= build a 4 levels row
with all_levels as
(
select ob.groupid groupid_b, oc.groupid groupid_c,
od.groupid groupid_d, oe.groupid groupid_e,
ob.details
from org ob
inner join org oc
on oc.parentid = ob.groupid
inner join org od
on od.parentid = oc.groupid
inner join org oe
on oe.parentid = od.groupid
where ob.parentid is not null
)
--= no matter what groupid returns b group details
, only_b as
(
select groupid_b as groupid, groupid_b, details from all_levels
union all
select groupid_c as groupid, groupid_b, details from all_levels
union all
select groupid_d as groupid, groupid_b, details from all_levels
union all
select groupid_e as groupid, groupid_b, details from all_levels
)
--= join with transactions table
select id, t.groupid, groupid_b, ob.details
from trans t
inner join only_b ob
on ob.groupid = t.groupid;
GO
id | groupid | groupid_b | details
-: | :------ | :-------- | :----------
1 | b | b | I need this
2 | c | b | I need this
3 | c | b | I need this
4 | d | b | I need this
5 | e | b | I need this
dbfiddle here
You can deal with a recursive function too, but I don't believe it can be better on terms of performance.
create function findDetails(#groupid char(1))
returns varchar(100)
as
begin
declare #parentid char(1) = '1';
declare #next_parentid char(1) = '1';
declare #details varchar(100) = '';
while #next_parentid is not null
begin
select #details = org.details, #parentid = org.parentid, #next_parentid = op.parentid
from org
inner join org op
on op.groupid = org.parentid
where org.groupid = #groupid
set #groupid = #parentid;
end
return #details;
end
GO
✓
select id, groupid, dbo.findDetails(groupid) as details_b
from trans;
GO
id | groupid | details_b
-: | :------ | :----------
1 | b | I need this
2 | c | I need this
3 | c | I need this
4 | d | I need this
5 | e | I need this
dbfiddle here

Recursive CTE (T-SQL) Returns Un-expected Result

I've been staring at this code for WAY too long, trying to figure out why my final query returns unexpected results.
Any help would be much appreciated. Thanks in advance.
Given the following code (running on SQL Server 2008 R2):
USE tempdb;
DECLARE #emp--loyee
TABLE (
EmployeeID int NOT NULL
,EmployeeName nvarchar(50) NOT NULL
PRIMARY KEY(EmployeeID)
)
INSERT INTO #emp
SELECT 1,'Fred'
UNION
SELECT 2,'Mary'
UNION
SELECT 3,'Joe'
UNION
SELECT 4,'Bill'
DECLARE #grp TABLE (
GroupID int NOT NULL
,GroupName nvarchar(50)
PRIMARY KEY(GroupID)
)
INSERT INTO #grp
SELECT 1,'Group 1'
UNION
SELECT 2,'Group 2'
UNION
SELECT 3,'Group 3'
DECLARE #empgrp TABLE (
EmployeeID int NOT NULL
,GroupID int NOT NULL
PRIMARY KEY (EmployeeID,GroupID)
)
INSERT INTO #empgrp
SELECT 1,1
UNION
SELECT 2,1
UNION
SELECT 3,1
UNION
SELECT 4,2
DECLARE #grpgrp TABLE (
GroupID int NOT NULL
,ParentGroupID int
,UNIQUE CLUSTERED(GroupID,ParentGroupID)
)
INSERT INTO #grpgrp
SELECT 1,2
UNION
SELECT 2,3;
WITH AllEmpGroups (EmployeeID,GroupID,RootGroupID)
AS
(
SELECT CAST(NULL as int) as EmployeeID,pgrp.GroupID,pgrp.ParentGroupID
FROM #grpgrp pgrp LEFT JOIN #grpgrp ggrp
ON pgrp.ParentGroupID = ggrp.GroupID
UNION ALL
SELECT e.EmployeeID,eg.GroupID,aeg.RootGroupID
FROM #emp e JOIN #empgrp eg
ON e.EmployeeID = eg.EmployeeID
JOIN #grpgrp ggrp
ON eg.GroupID = ggrp.GroupID
JOIN AllEmpGroups aeg
ON aeg.GroupID = ggrp.ParentGroupID
)
SELECT EmployeeID,GroupID,RootGroupID
FROM AllEmpGroups
What I get is:
+------------+---------+-------------+
| EmployeeID | GroupID | RootGroupID |
+------------+---------+-------------+
| NULL | 1 | 2 |
| NULL | 2 | 3 |
| 1 | 1 | 3 |
| 2 | 1 | 3 |
| 3 | 1 | 3 |
+------------+---------+-------------+
What I would expect/want to get is this:
+------------+---------+-------------+
| EmployeeID | GroupID | RootGroupID |
+------------+---------+-------------+
| NULL | 1 | 2 |
| NULL | 2 | 3 |
| 4 | 2 | 3 |
| 1 | 1 | 3 |
| 2 | 1 | 3 |
| 3 | 1 | 3 |
+------------+---------+-------------+
Bottom line, I want the full recursive stack of all employees beneath a given root group(s), with the root group id on every row.
What am I missing?
First:
You need a row for the root node in #grpgrp with values 3, null
The anchor (part before the union all) of your recursive cte needs to be the root node (3, null) for ancestor first recursion.
...
INSERT INTO #grpgrp
SELECT 1,2
UNION all
SELECT 2,3
UNION all
select 3, null;
WITH AllEmpGroups (EmployeeID,GroupID,RootGroupID)
AS
(
SELECT CAST(NULL as int) as EmployeeID,pgrp.GroupID, ParentGroupID = pgrp.GroupID
FROM #grpgrp pgrp LEFT JOIN #grpgrp ggrp
ON pgrp.ParentGroupID = ggrp.GroupID
where pgrp.ParentGroupId is null
UNION ALL
SELECT e.EmployeeID,eg.GroupID,aeg.RootGroupID
FROM #emp e JOIN #empgrp eg
ON e.EmployeeID = eg.EmployeeID
JOIN #grpgrp ggrp
ON eg.GroupID = ggrp.GroupID
JOIN AllEmpGroups aeg
ON aeg.GroupID = ggrp.ParentGroupID
)
SELECT EmployeeID,GroupID,RootGroupID
FROM AllEmpGroups
rextester demo: http://rextester.com/CBWY80387
returns:
+------------+---------+-------------+
| EmployeeID | GroupID | RootGroupID |
+------------+---------+-------------+
| NULL | 3 | 3 |
| 4 | 2 | 3 |
| 1 | 1 | 3 |
| 2 | 1 | 3 |
| 3 | 1 | 3 |
+------------+---------+-------------+
Beyond that, I would build the groups hierarchy first, then join the employees like so:
WITH AllEmpGroups (GroupID,ParentGroupID,RootGroupID)
AS
(
SELECT pgrp.GroupID, pgrp.ParentGroupID, RootGroupId = GroupID
FROM #grpgrp pgrp
where pgrp.ParentGroupId is null
UNION ALL
SELECT ggrp.GroupID,ggrp.ParentGroupID,aeg.RootGroupID
FROM #grpgrp ggrp
inner JOIN AllEmpGroups aeg
ON aeg.GroupID = ggrp.ParentGroupID
)
SELECT eg.EmployeeID,aeg.*
FROM AllEmpGroups aeg
left JOIN #empgrp eg
ON eg.GroupID = aeg.GroupID
rextester demo: http://rextester.com/FAK76354
returns:
+------------+---------+---------------+-------------+
| EmployeeID | GroupID | ParentGroupID | RootGroupID |
+------------+---------+---------------+-------------+
| NULL | 3 | NULL | 3 |
| 4 | 2 | 3 | 3 |
| 1 | 1 | 2 | 3 |
| 2 | 1 | 2 | 3 |
| 3 | 1 | 2 | 3 |
+------------+---------+---------------+-------------+
Start with
WITH AllGroups (RootGroupID,GroupID,ParentGroupID, level)
AS
(
SELECT GroupID RootGroupID, GroupID, Cast(NULL as int) ParentGroupID, 0 level
FROM #grp g
WHERE NOT EXISTS (SELECT 1 FROM #grpgrp gg WHERE gg.GroupID = g.GroupID)
UNION ALL
SELECT ag.RootGroupID, gg.GroupID, gg.ParentGroupID, level+1
FROM #grpgrp gg
JOIN AllGroups ag
ON ag.GroupID = gg.ParentGroupID
)
SELECT EmployeeID, ag.GroupID, ParentGroupID, RootGroupID
FROM AllGroups ag
LEFT JOIN #empgrp eg ON eg.GroupID = ag.GroupID
ORDER BY RootGroupID, level, ParentGroupID, GroupID;
Not sure why you need the row:
| NULL | 2 | 3 |