1

Here is my query which is in Oracle PL/SQL syntax, How can I Change it to SQL server format?

Any alternatives for Connect_by_isleaf?

(
select PARTY_KEY, ltrim(sys_connect_by_path(alt_name, '|'), '|')  AS     alt_name_list
from 
     (select PARTY_KEY, alt_name, row_number() over(partition by PARTY_KEY      order by alt_name) rno
     from (
             select party_key, (select alt_name_type_desc      from "CRMS"."PRJ_APP_ALT_NAME_TYPE"  where alt_name_type_cd = alt_name_type)       || ' - ' || alt_name as alt_name
             from  "CDD_PROFILES"."PRJ_PRF_ALT_NAME" order by party_key,      alt_name_type 
           ) alt
 )
  where connect_by_isleaf = 1
 connect by PARTY_KEY = prior PARTY_KEY
  and rno = prior rno+1
start with rno = 1
)

tried to use With AS clause but it is not working somehow.

Thanks in advance

Barbaros Özhan
  • 59,113
  • 10
  • 31
  • 55
Addy
  • 36
  • 4

2 Answers2

1

The equivalent in SQL Server is called a "recursive CTE".

You can read about it here: https://learn.microsoft.com/en-us/sql/t-sql/queries/with-common-table-expression-transact-sql?view=sql-server-2017

Conor Cunningham MSFT
  • 4,151
  • 1
  • 15
  • 21
1

Oracle Hierarchical queries can be rewritten as recursive CTE statements in databases that support them (SQL Server included). A classic set of hierarchical data would be an organization hierarchy such as the one below:

SQL Fiddle

MS SQL Server 2017 Schema Setup:

CREATE TABLE ORGANIZATIONS
    ([ID] int primary key
     , [ORG_NAME] varchar(30)
     , [ORG_TYPE] varchar(30)
     , [PARENT_ID] int foreign key references organizations)
;

INSERT INTO ORGANIZATIONS
    ([ID], [ORG_NAME], [ORG_TYPE], [PARENT_ID])
VALUES
    (1, 'ACME Corp', 'Company', NULL),
    (2, 'Finance', 'Division', 1),
    (6, 'Accounts Payable', 'Department', 2),
    (7, 'Accounts Receivables', 'Department', 2),
    (8, 'Payroll', 'Department', 2),
    (3, 'Operations', 'Division', 1),
    (4, 'Human Resources', 'Division', 1),
    (10, 'Benefits Admin', 'Department', 4),
    (5, 'Marketing', 'Division', 1),
    (9, 'Sales', 'Department', 5)
;

In the recursive t1 below the select statement before the union all is the anchor query and the select statement after the union all is the recursive part. The recursive part has exactly one reference to t1 in its from clause. The org_path column simulates oracles sys_connect_by_path function concatenating the org_names together. The level column simulates oracles LEVEL pseudo column and is utilized in the output query to determine the leaf status (is_leaf column) similar to oracles connect_by_isleaf pseudo column:

with t1(id, org_name, org_type, parent_id, org_path, level) as (
select o.*
     , cast('|' + org_name as varchar(max))
     , 1
  from organizations o
 where parent_id is null
union all
select o.*
     , t1.org_path+cast('|'+o.org_name as varchar(max))
     , t1.level+1
  from organizations o
  join t1
    on t1.id = o.parent_id
)
select t1.*
     , case when t1.level < lead(t1.level) over (order by org_path) then 0 else 1 end is_leaf
  from t1 order by org_path

Results:

| id | org_name             | org_type   | parent_id | org_path                                  | level | is_leaf |
|----|----------------------|------------|-----------|-------------------------------------------|-------|---------|
|  1 | ACME Corp            | Company    |    (null) | |ACME Corp                                |     1 |       0 |
|  2 | Finance              | Division   |         1 | |ACME Corp|Finance                        |     2 |       0 |
|  6 | Accounts Payable     | Department |         2 | |ACME Corp|Finance|Accounts Payable       |     3 |       1 |
|  7 | Accounts Receivables | Department |         2 | |ACME Corp|Finance|Accounts Receivables   |     3 |       1 |
|  8 | Payroll              | Department |         2 | |ACME Corp|Finance|Payroll                |     3 |       1 |
|  4 | Human Resources      | Division   |         1 | |ACME Corp|Human Resources                |     2 |       0 |
| 10 | Benefits Admin       | Department |         4 | |ACME Corp|Human Resources|Benefits Admin |     3 |       1 |
|  5 | Marketing            | Division   |         1 | |ACME Corp|Marketing                      |     2 |       0 |
|  9 | Sales                | Department |         5 | |ACME Corp|Marketing|Sales                |     3 |       1 |
|  3 | Operations           | Division   |         1 | |ACME Corp|Operations                     |     2 |       1 |

To select just the leaf nodes, change the output query from above to another CTE (T2) dropping the order by clause or moving it to final output query and limiting by the is_leaf column:

with t1(id, org_name, org_type, parent_id, org_path, level) as (
select o.*
     , cast('|' + org_name as varchar(max))
     , 1
  from organizations o
 where parent_id is null
union all
select o.*
     , t1.org_path+cast('|'+o.org_name as varchar(max))
     , t1.level+1
  from organizations o
  join t1
    on t1.id = o.parent_id
), t2 as (
select t1.*
     , case when t1.level < lead(t1.level) over (order by org_path) then 0 else 1 end is_leaf
  from t1
)
select * from t2 where is_leaf = 1

Results:

| id | org_name             | org_type   | parent_id | org_path                                  | level | is_leaf |
|----|----------------------|------------|-----------|-------------------------------------------|-------|---------|
|  6 | Accounts Payable     | Department |         2 | |ACME Corp|Finance|Accounts Payable       |     3 |       1 |
|  7 | Accounts Receivables | Department |         2 | |ACME Corp|Finance|Accounts Receivables   |     3 |       1 |
|  8 | Payroll              | Department |         2 | |ACME Corp|Finance|Payroll                |     3 |       1 |
| 10 | Benefits Admin       | Department |         4 | |ACME Corp|Human Resources|Benefits Admin |     3 |       1 |
|  9 | Sales                | Department |         5 | |ACME Corp|Marketing|Sales                |     3 |       1 |
|  3 | Operations           | Division   |         1 | |ACME Corp|Operations                     |     2 |       1 |

Alternatively if you realize that leaf nodes can be identified by their lack of child nodes, you can flip this on its head and start with the leaf nodes, and search up the tree, retaining all the original record values, building out the org_path in reverse, and passing along the next parent id as next_id. In the final output, stage, selecting only those records whose next_id is null will yield the same results as the prior query:

with t1(id, org_name, org_type, parent_id, org_path, level, next_id) as (
  select o.*
       , cast('|'+org_name as varchar(max))
       , 1
       , parent_id
    from organizations o
   where not exists (select 1 from organizations c where c.parent_id = o.id)
  union all
  select t1.id
       , t1.org_name
       , t1.org_type
       , t1.parent_id
       , cast('|'+p.org_name as varchar(max))+t1.org_path
       , level+1
       , p.parent_id
    from organizations p
    join t1
      on t1.next_id = p.id
 )
 select * from t1 where next_id is null order by org_path

Results:

| id | org_name             | org_type   | parent_id | org_path                                  | level | next_id |
|----|----------------------|------------|-----------|-------------------------------------------|-------|---------|
|  6 | Accounts Payable     | Department |         2 | |ACME Corp|Finance|Accounts Payable       |     3 |  (null) |
|  7 | Accounts Receivables | Department |         2 | |ACME Corp|Finance|Accounts Receivables   |     3 |  (null) |
|  8 | Payroll              | Department |         2 | |ACME Corp|Finance|Payroll                |     3 |  (null) |
| 10 | Benefits Admin       | Department |         4 | |ACME Corp|Human Resources|Benefits Admin |     3 |  (null) |
|  9 | Sales                | Department |         5 | |ACME Corp|Marketing|Sales                |     3 |  (null) |
|  3 | Operations           | Division   |         1 | |ACME Corp|Operations                     |     2 |  (null) |

One of these two methods may prove more performant than the other, but you'll need to try them each out on your data to see which one works better.

Sentinel
  • 6,379
  • 1
  • 18
  • 23