Get nth level on self-referencing table - sql

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

Related

Join on different tables depending on column associated with value being joined on?

I am trying to create a view that provides different tables depending on a column associated with the value being joined on.
Example:
SomeTable
+-----+------+
| ID | Type |
+-----+------+
| 123 | 1 |
| 124 | 2 |
| 125 | 1 |
| 126 | 2 |
+-----+------+
TableA
+---------+---------------+-----+------------+
| Item_ID | Serial_Number | ID | LocationID |
+---------+---------------+-----+------------+
| 1 | 19-001 | 124 | 4 |
| 2 | 19-002 | 126 | 17 |
+---------+---------------+-----+------------+
TableB
+-----+------------+----------+
| ID | LocationID | Quantity |
+-----+------------+----------+
| 123 | 7 | 15 |
| 125 | 12 | 10 |
+-----+------------+----------+
SELECT t.ID, v.LocationID
FROM SomeTable t
FULL JOIN NewView v ON t.ID = v.ID
WHERE t.ID = 123
If the ID the view is being joined on is Type 1 then the View selects Table A. If the ID is Type 2, then select Table B.
The expected result for ID 123 would be:
+-----+------------+
| ID | LocationID |
+-----+------------+
| 123 | 7 |
+-----+------------+
And the expected result for ID 124 would be:
+-----+------------+
| ID | LocationID |
+-----+------------+
| 124 | 4 |
+-----+------------+
I know I could do this by using a function and passing a parameter like this:
SELECT t.ID, f.LocationID
FROM SomeTable t
FULL JOIN NewFunction(123) f ON t.ID = f.ID
WHERE t.ID = 123
And here is the function:
CREATE FUNCTION NewFunction (#ID)
RETURNS #ReturnTable TABLE (ID INT, LocationID INT)
BEGIN
DECLARE #Type INT
SET #Type = (SELECT Type FROM SomeTable WHERE ID = #ID)
IF #Type = 1
INSERT INTO #ReturnTable (ID, LocationID)
SELECT t.ID, a.LocationID
FROM SomeTable t
FULL JOIN TableA a
WHERE t.ID = #ID
ELSE
INSERT INTO #ReturnTable (ID, LocationID)
SELECT t.ID, b.LocationID
FROM SomeTable t
FULL JOIN TableB b
WHERE t.ID = #ID
RETURN
END
The problem is that passing a parameter to a function like this will require making changes to the application. I would prefer not to have to implement these changes, so it would be ideal if I could recreate the functionality in the example above either with a view or a function that does not require a parameter. Any ideas?
Why not create a view for all three tables?
select t.id, coalesce(a.locationid, b.locationid) as locationid
from sometable t left join
a
on t.id = a.id and t.type = 1 left join
b
on t.id = b.id and t.type = 2;
You need the type to get the right table, so it seems that all three tables should be used together.
You can't send a parameter into a VIEW, so it isn't possible to make it do different things depending on the content of the select query.
What you could do, is to use a UNION query assuming that Table A and Table B have the same columns, then use the Type column in your WHERE clause

Multiple queries resulting in minimum number of occurrences

I need to find the smallest number of documents retrieved by any subject based on exp_condition. Exp_condition from the subjects table contains a '1' and a '2' column.
Here are the tables:
subjects table:
+-----------------+--------------+
| Field | Type |
+-----------------+--------------+
| username | varchar(255) |
| user_type | varchar(10) |
| years | int |
| low_grade | int |
| high_grade | int |
| on_line | varchar(10) |
| on_line_sources | varchar(255) |
| location | varchar(5) |
| exp_condition | int |
+-----------------+--------------+
tasks table:
+------------+--------------+
| Field | Type |
+------------+--------------+
| username | varchar(255) |
| task | varchar(5) |
| confidence | int |
| sim_helpd | int |
+------------+--------------+
docs table:
+--------------+--------------+
| Field | Type |
+--------------+--------------+
| username | varchar(255) |
| task | varchar(5) |
| doc_type | varchar(10) |
| used_tool | int |
| relevant | int |
| motivational | int |
| concepts | int |
| background | int |
| grade_level | int |
| hands_on | int |
| attachments | int |
+--------------+--------------+
I'm able to generate the number of subjects and number of documents for both exp_condition values. I'm allowed to use multiple queries, but I'm not sure how.
Code for generating number of subjects for exp_condition 1 and 2:
select count(distinct(t2.username))
from tasks as t1
inner join subjects as t2
on t1.username = t2.username group by exp_condition;
Code for generating number of documents for exp_condition 1 and 2:
select count(*), exp_condition
from docs as t1
left join subjects as t2
on t1.username = t2.username
group by exp_condition;
Expected output: two separate numbers for smallest number of documents retrieved by any subject based on exp_condition.
Thanks in advance.
You can use a subquery or a CTE
SubQuery
SELECT exp_condition, MIN(A) as Tasks, MIN(B) as Docs FROM (
SELECT exp_condition, COUNT(DISTINCT t2.username) A, COUNT(DISTINCT (t3.username) B
FROM subjects s
LEFT JOIN tasks T2 ON s.username = t2.username
LEFT JOIN docs T3 ON s.username = t3.username
GROUP BY exp_condition
) A
GROUP BY ex_condition
CTE
;WITH CTE AS (
SELECT exp_condition, COUNT(DISTINCT t2.username) A, COUNT(DISTINCT (t3.username) B
FROM subjects s
LEFT JOIN tasks T2 ON s.username = t2.username
LEFT JOIN docs T3 ON s.username = t3.username
GROUP BY exp_condition
)
SELECT exp_condition, MIN(A) as Tasks, MIN(B) as Docs
FROM CTE
GROUP BY ex_condition

Joining two tables + third table as a conditional column

If i have three tables called warehouse , warehouse_order and table called warehouse_fulfillment. A warehouse order is created by warehouse admin, initially has 0 fulfillment records, can have many warehouse_fulfillment records with failed/rejected statuses, and only one success state (it's done):
-- Warehouse
+---------------------------------------+-----------+----------+
| id | name | location |
+---------------------------------------+-----------+----------+
| 9bcae08e-ad36-4d97-b9ec-4857714e902a | "big" | "MLB" |
+---------------------------------------+-----------+----------+
| b442e783-4725-41e9-af83-f75004ee1b38 | "bigger" | "MLB" |
+---------------------------------------+-----------+----------+
| 986d5aa9-0523-42d8-b183-dfd546d3e682 | "biggest" | "MLB" |
+---------------------------------------+-----------+----------+
-- Warehouse_order Table
+---------------------------------------+--------------------------------------+--------+----------+
| id | warehouse_id | type | quantity |
+---------------------------------------+--------------------------------------+--------+----------+
| 9cb99fd9-9e5e-4240-8162-d28747be01cd | b442e783-4725-41e9-af83-f75004ee1b38 | BN_100 | 100 |
+---------------------------------------+-------------------------------------+--------+-----------+
| eceb0b5a-5afa-40e4-ac62-efb686e3bdae | 9bcae08e-ad36-4d97-b9ec-4857714e902a | BN_200 | 400 |
+---------------------------------------+--------------------------------------+--------+----------+
| 13370467-cf0c-47f2-8fea-a215500607e6 | 986d5aa9-0523-42d8-b183-dfd546d3e68 | BN_300 | 10 |
+---------------------------------------+--------------------------------------+--------+----------+
-- Warhouse_fulfillment Table
+---------------------------------------+---------------------------------------+------------+
| id | order_id | status |
+---------------------------------------+---------------------------------------+------------+
| 8a69edde-2346-48b8-96d0-6c4e25527f38 | 9cb99fd9-9e5e-4240-8162-d28747be01cd | "FAILLED" |
+---------------------------------------+---------------------------------------+------------+
| a2006a64-9bdc-4bfa-ba14-a44769aeb4a2 | 9cb99fd9-9e5e-4240-8162-d28747be01cd | "REJECTED" |
+---------------------------------------+---------------------------------------+------------+
| bf0aa1fc-6dfc-4fd0-ba20-be101b1985d1 | 9cb99fd9-9e5e-4240-8162-d28747be01cd | "FAILED" |
+---------------------------------------+---------------------------------------+------------+
| 48c7d747-2f9b-4535-8f27-210a43cf5c30 | 9cb99fd9-9e5e-4240-8162-d28747be01cd | "SUCCESS" |
+---------------------------------------+---------------------------------------+------------+
| 7f8e18c9-4322-428a-9370-9ecd1c5ef286 | 13370467-cf0c-47f2-8fea-a215500607e6 | "FAILED" |
+---------------------------------------+---------------------------------------+------------+
I want to query the above records in such a way that result looks like so:
+--------------------------------------+-----------+----------+---------------------------------------+------------+----------------+--------------------------------------+
| id | name | location | order_id | order_type | order_quantity | fulfillment_id |
+--------------------------------------+-----------+----------+---------------------------------------+------------+----------------+--------------------------------------+
| 9bcae08e-ad36-4d97-b9ec-4857714e902a | "big" | "MLB" | eceb0b5a-5afa-40e4-ac62-efb686e3bdae | "BN_100" | 100 | NULL |
+--------------------------------------+-----------+----------+---------------------------------------+------------+----------------+--------------------------------------+
| b442e783-4725-41e9-af83-f75004ee1b38 | "bigger" | "MLB" | 9cb99fd9-9e5e-4240-8162-d28747be01cd | "BN_200" | 400 | 48c7d747-2f9b-4535-8f27-210a43cf5c30 |
+--------------------------------------+-----------+----------+---------------------------------------+------------+----------------+--------------------------------------+
| 986d5aa9-0523-42d8-b183-dfd546d3e682 | "biggest" | "MLB" | 13370467-cf0c-47f2-8fea-a215500607e6 | "BN_300" | 10 | NULL |
+--------------------------------------+-----------+----------+---------------------------------------+------------+----------------+--------------------------------------+
I couldn't do this without the repeated rows in cases where an order has multiple failed statuses.
Did you try SELECT DISTINCT? Because you don't have the status column (which causes the duplicates) in the select list, this should work.
SELECT DISTINCT W.id, W.name, W.location, WO.id order_id, WO.type order_type, WO.quantity order_quantity, WF.id fulfillment_id
FROM warehouse W
LEFT JOIN warehouse_order WO ON W.id = WO.warehouse_id
LEFT JOIN warehouse_fulfillment WF on WF.order_id = WO.id
Otherwise, I would need to know what DBMS the SQL is for, but every flavor I've worked with has some way to rank/order results with a partition so that you can take just the first record based on some key, for example:
SELECT id, name, location, order_id, order_type, order_quantity
FROM (
SELECT W.id, W.name, W.location, WO.id order_id, WO.type order_type, WO.quantity order_quantity, WF.id fulfillment_id, ROW_NUMBER() OVER (PARTITION BY WO.id ORDER BY WF.ID) rNum
FROM warehouse W
LEFT JOIN warehouse_order WO ON W.id = WO.warehouse_id
LEFT JOIN warehouse_fulfillment WF on WF.order_id = WO.id
) A
WHERE rNum = 1
It would be better to order by date DESC or something like that to get the most recent record.
Without some reliable method within the fulfillment table to determine "latest status", such as a timestamp, you will need to choose some arbitrary method to arrive at a priority order amongst the possible status values. Below I have used a case expression within the over clause so that "success" will be get the row number 1 if that status exists for an order. Adjust the case expression as you see fit for the other possible values of that column.
When the subquery containing the row number is joined to the main query that join includes and rn=1 so only fulfillment row per order will be possible.
Please note that in the sample data there is a missing warehouse row so I had to use a left join, but I expect it would be an inner join in the real db.
SQL Fiddle Demo
CREATE TABLE Warehouse
(ID varchar(36), Name varchar(9), Location varchar(5))
;
INSERT INTO Warehouse
("id", "name", "location")
VALUES
('9bcae08e-ad36-4d97-b9ec-4857714e902a', 'big', 'MLB'),
('b442e783-4725-41e9-af83-f75004ee1b38', 'bigger', 'MLB'),
('986d5aa9-0523-42d8-b183-dfd546d3e682', 'biggest', 'MLB')
;
CREATE TABLE Warehouse_order
(ID varchar(36), Warehouse_id varchar(36), type varchar(6), quantity int)
;
INSERT INTO Warehouse_order
("id", "warehouse_id", "type", "quantity")
VALUES
('9cb99fd9-9e5e-4240-8162-d28747be01cd', 'b442e783-4725-41e9-af83-f75004ee1b38', 'BN_100', 100),
('eceb0b5a-5afa-40e4-ac62-efb686e3bdae', '9bcae08e-ad36-4d97-b9ec-4857714e902a', 'BN_200', 400),
('13370467-cf0c-47f2-8fea-a215500607e6', '986d5aa9-0523-42d8-b183-dfd546d3e68', 'BN_300', 10)
;
CREATE TABLE Warehouse_fulfillment
(ID varchar(36), Order_id varchar(36), Status varchar(10))
;
INSERT INTO Warehouse_fulfillment
("id", "order_id", "status")
VALUES
('8a69edde-2346-48b8-96d0-6c4e25527f38', '9cb99fd9-9e5e-4240-8162-d28747be01cd', 'FAILLED'),
('a2006a64-9bdc-4bfa-ba14-a44769aeb4a2', '9cb99fd9-9e5e-4240-8162-d28747be01cd', 'REJECTED'),
('bf0aa1fc-6dfc-4fd0-ba20-be101b1985d1', '9cb99fd9-9e5e-4240-8162-d28747be01cd', 'FAILED'),
('48c7d747-2f9b-4535-8f27-210a43cf5c30', '9cb99fd9-9e5e-4240-8162-d28747be01cd', 'SUCCESS'),
('7f8e18c9-4322-428a-9370-9ecd1c5ef286', '13370467-cf0c-47f2-8fea-a215500607e6', 'FAILED')
;
Query 1:
select
o.*, w.name, s.status, s.rn
from Warehouse_order o
left join Warehouse w on o.Warehouse_id = w.id
left join (
select id, order_id, status
, row_number() over(partition by order_id
order by case when status = 'SUCCESS' then 1
when status = 'FAILED' then 2
when status = 'REJECTED' then 3
else 4 end) as rn
from Warehouse_fulfillment
) s on o.id = s.Order_id and rn=1
Results:
| id | warehouse_id | type | quantity | name | status | rn |
|--------------------------------------|--------------------------------------|--------|----------|--------|---------|--------|
| eceb0b5a-5afa-40e4-ac62-efb686e3bdae | 9bcae08e-ad36-4d97-b9ec-4857714e902a | BN_200 | 400 | big | (null) | (null) |
| 9cb99fd9-9e5e-4240-8162-d28747be01cd | b442e783-4725-41e9-af83-f75004ee1b38 | BN_100 | 100 | bigger | SUCCESS | 1 |
| 13370467-cf0c-47f2-8fea-a215500607e6 | 986d5aa9-0523-42d8-b183-dfd546d3e68 | BN_300 | 10 | (null) | FAILED | 1 |
I'm not entirely sure on this, but it sounds like you just want a left join on the order_fulfillment table with the "success" condition in the join:
select
w.id, w.name, w.location,
o.id as order_id, o.type as order_type,
o.quantity as order_quantity,
f.id as fulfillment_id
from
warehouse w
join warehouse_order o on
w.id = o.warehouse_id
left join warhouse_fulfillment f on
o.id = f.order_id and
f.status = 'SUCCESS'
Since you don't seem to care about non-successful records, and there is guarantee that there will only be one 'SUCCESS' fulfillment record, this should avoid any duplicates.

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 |

Create New Table From Other Table After Grouping

How can I insert to a table a value from "grouping" other table?
That means I have 2 table with different structure.
The table ORDRE with existed DATA
Table ORDRE:
ORDRE ID | CODE_DEST |
-------------------------
1 | a |
2 | b |
3 | c |
4 | a |
5 | a |
6 | b |
7 | g |
I want to INSERT the value FROM Table ORDRE INTO TABLE VOIT:
ID_VOIT | ORDRE ID | CODE_DEST |
---------------------------------------
1 | 1 | a |
1 | 4 | a |
1 | 5 | a |
2 | 2 | b |
2 | 6 | b |
3 | 3 | c |
4 | 7 | g |
This is my best guess on what you need using only the info available.
declare #Ordre table
(
ordre_id int,
code_dest char(1)
)
declare #Voit table
(
id_voit int,
ordre_id int,
code_dest char(1)
)
insert into #Ordre values
(1,'a'),
(2,'b'),
(3,'c'),
(4,'a'),
(5,'a'),
(6,'b'),
(7,'g')
insert into #Voit
select id_voit, ordre_id, rsOrdre.code_dest
from #Ordre rsOrdre
inner join
(
select code_dest, ROW_NUMBER() over (order by code_dest) as id_voit
from #Ordre
group by code_dest
) rsVoit on rsVoit.code_dest = rsOrdre.code_dest
order by id_voit, ordre_id
select * from #Voit
Working Example.
For the specific data you give as an example, this works:
insert into VOIT
select
case code_dest
when 'a' then 1
when 'b' then 2
when 'c' then 3
when 'g' then 4
else 0
end, orderId, code_dest from ORDRE order by code_dest, orderId
But it kind of sucks because it requires hard-coding in a huge case statement.
Test is here - https://data.stackexchange.com/stackoverflow/q/119442/
What I like more is moving the VOIT ID / Code_Dest associations to a new table, so then you could do an inner join instead.
insert into VOIT
select voit_id, orderId, t.code_dest
from ORDRE t
join Voit_CodeDest t2 on t.code_dest = t2.code_dest
order by code_dest, orderId
Working example of that here - https://data.stackexchange.com/stackoverflow/q/119443/