Singleton
Singleton

Reputation: 92

How to replace COALESCE with recursive CTE query?

A follow-up question to Recursive CTE with three tables which helped me with CTE's in SQL Server.

What has changed from the initial question? The table MANAGERSno longer includes rows for org.units that have no manager.

The goal is to get the first non-null manager for a organizational unit. I've got it working using COALESCE and OUTER JOIN's, my question is if it is possible to use a recursive query instead?

Example code follows below.

DECLARE @ORG_PARENTS TABLE (ORG_ID INT, ORG_PARENT INT ) 
DECLARE @MANAGERS TABLE (ORG_ID INT, MANAGER VARCHAR(100))
DECLARE @ORG TABLE (ORG_ID INT, ORG_NAME VARCHAR(100))

INSERT @ORG (ORG_ID, ORG_NAME)
VALUES  (1, 'One')
,       (2, 'Two')
,       (3, 'Three')

INSERT @ORG_PARENTS (ORG_ID, ORG_PARENT)
VALUES  (1, NULL)
,       (2, 1)
,       (3, 2)

INSERT @MANAGERS (ORG_ID, MANAGER)
VALUES (1, 'John Doe')
,       (2, 'Jane Doe')

;
-- The original answer
WITH BOSS
AS
( 
SELECT      m.MANAGER, m.ORG_ID AS ORI, m.ORG_ID, p.ORG_PARENT, 1 cnt
FROM        @MANAGERS m 
INNER JOIN  @ORG_PARENTS p 
            ON p.ORG_ID = m.ORG_ID
UNION ALL

SELECT      m1.MANAGER, b.ORI, m1.ORG_ID, OP.ORG_PARENT, cnt +1
FROM        BOSS b
INNER JOIN  @ORG_PARENTS AS OP
        ON  OP.ORG_ID = b.ORG_PARENT
INNER JOIN  @MANAGERS m1 
        ON  m1.ORG_ID = OP.ORG_ID 
)

--SELECT  * 
--FROM    BOSS 
--WHERE   ORI = 3

-- The following query only returns ORG "One" and "Two"
SELECT ORG.ORG_ID as "ORGID", ORG.ORG_NAME AS "NAME", MGR.MANAGER AS "MANAGER"
FROM @ORG ORG INNER JOIN @MANAGERS MGR ON MGR.ORG_ID = ORG.ORG_ID

-- The following query returns three rows, with NULL as the MANAGER for "Three"
-- My goal is to get the manager from the first parent that is not null
SELECT ORG.ORG_ID as "ORGID", ORG.ORG_NAME AS "NAME", (SELECT MANAGER FROM     @MANAGERS M WHERE M.ORG_ID = ORG.ORG_ID) AS "MANAGER"
FROM @ORG ORG 


-- The following workaround works, I can use COALESCE and a large number of OUTER JOINs to get the desired effect.
-- Is it possible to replace the code below with a recursive query, optimally where the number of levels is not hardcoded?
SELECT ORG.ORG_ID as "ORGID", ORG.ORG_NAME AS "NAME",
COALESCE(
      (SELECT MANAGER FROM @MANAGERS M WHERE M.ORG_ID = ORG.ORG_ID),
      (SELECT MANAGER FROM @MANAGERS M WHERE M.ORG_ID = p1.ORG_PARENT),
      (SELECT MANAGER FROM @MANAGERS M WHERE M.ORG_ID = p2.ORG_PARENT)
    ) AS "MANAGER"
FROM @ORG ORG 
LEFT OUTER JOIN @ORG_PARENTS p1 ON ORG.ORG_ID = p1.ORG_ID
LEFT OUTER JOIN @ORG o1 ON p1.ORG_PARENT = o1.ORG_ID
LEFT OUTER JOIN @ORG_PARENTS p2 ON o1.ORG_ID = p2.ORG_ID
LEFT OUTER JOIN @ORG o2 ON p2.ORG_PARENT = o2.ORG_ID

Expected result

I want the MANAGER column for 3 to show "Jane Doe". For example the COALSECE (last SELECT above) does that:

ORGID   NAME    MANAGER
1       One     John Doe
2       Two     Jane Doe
3       Three   Jane Doe

Upvotes: 1

Views: 1120

Answers (1)

HoneyBadger
HoneyBadger

Reputation: 15140

@ZLK is right in his/her comment, you don't need the MANAGERS table to execute the recursion:

WITH BOSS
AS
( 
SELECT      P.ORG_ID AS ORI, P.ORG_ID, p.ORG_PARENT, 1 cnt
FROM        @ORG_PARENTS p 
UNION ALL

SELECT      b.ORI, OP.ORG_ID, OP.ORG_PARENT, cnt +1
FROM        BOSS b
INNER JOIN  @ORG_PARENTS AS OP
        ON  OP.ORG_ID = b.ORG_PARENT
)

SELECT      * 
FROM        BOSS 
LEFT JOIN   @MANAGERS m1 
        ON  m1.ORG_ID = BOSS.ORG_ID 
WHERE       ORI = 3

Result:

+-----+--------+------------+-----+--------+----------+
| ORI | ORG_ID | ORG_PARENT | cnt | ORG_ID | MANAGER  |
+-----+--------+------------+-----+--------+----------+
|   3 |      3 | 2          |   1 | NULL   | NULL     |
|   3 |      2 | 1          |   2 | 2      | Jane Doe |
|   3 |      1 | NULL       |   3 | 1      | John Doe |
+-----+--------+------------+-----+--------+----------+

Upvotes: 1

Related Questions