Skip to Main Content
  • Questions
  • CONNECT BY with Subquery in START WITH

Breadcrumb

Question and Answer

Chris Saxon

Thanks for the question, Tobias.

Asked: September 10, 2018 - 10:50 am UTC

Last updated: September 11, 2018 - 12:55 pm UTC

Version: 12.2.0.1

Viewed 10K+ times! This question is

You Asked

Hi Tom, ehm, Chris of course,

after migration our production database to 12.2.0.1, I have found out that there is a problem regarding CONNECT BY with a subquery in the START WITH clause.

FYI: LiveSQL Link didn't work when I copied it, here is the correct one:
https://livesql.oracle.com/apex/livesql/s/g8qg94z3f0vxh7o70gi2u5gxk


Oracle Database 12c Standard Edition Release 12.2.0.1.0 - 64bit Production



I have a table TW_GROUP containing articles and their product group. Then I have a hierarchical table TW_GROUP_TREE of all the product groups with columns GRP, PARENT and CODE. Product group 0 is the root group. For my testcase I just need 2 levels, in our system we have some more, of course.

CREATE TABLE TW_GROUP (Article NUMBER, Subgrp NUMBER);
INSERT INTO TW_GROUP VALUES (1, 20);
INSERT INTO TW_GROUP VALUES (2, 23);


CREATE TABLE TW_GROUP_TREE (Grp NUMBER, Parent NUMBER, Code NUMBER);
INSERT INTO TW_GROUP_TREE VALUES (20, 0, 6);
INSERT INTO TW_GROUP_TREE VALUES (23, 0, NULL);
INSERT INTO TW_GROUP_TREE VALUES (0, NULL, 99);


The column CODE can contain a value in each hierarchy, but it is not a must, as you can see at product group 23. So I have to use CONNECT BY to look after it. I want to use the first value I can find in the hierarchy starting with the subgroup that is in TW_GROUP.

In the START WITH clause I use a subquery to get the product group of the article.

This works fine for Article 1:

SELECT Article,
       (SELECT FIRST_VALUE(CODE) OVER(ORDER BY Level)
          FROM TW_GROUP_TREE
         WHERE Code IS NOT NULL
           AND RowNum = 1
    CONNECT BY PRIOR Parent = Grp
    START WITH Grp  = (SELECT SubGrp
                         FROM TW_GROUP
                        WHERE Article  = u1.Article)) AS Code_with_Subquery
  FROM (SELECT 1 AS Article FROM DUAL) u1;


   ARTICLE CODE_WITH_SUBQUERY
---------- ------------------
         1                  6
1 row selected.



It works fine for Article 2, too.

SELECT Article,
       (SELECT FIRST_VALUE(CODE) OVER(ORDER BY Level)
          FROM TW_GROUP_TREE
         WHERE Code IS NOT NULL
           AND RowNum = 1
    CONNECT BY PRIOR Parent = Grp
    START WITH Grp  = (SELECT SubGrp
                         FROM TW_GROUP
                        WHERE Article  = u1.Article)) AS Code_with_Subquery
  FROM (SELECT 2 AS Article FROM DUAL) u1;


   ARTICLE CODE_WITH_SUBQUERY
---------- ------------------
         2                 99
1 row selected.


Here Code 99 is chosen from the root product group, because product group 23 doesn't have a Code.


But it does not work correctly for both articles at the same time:

SELECT Article,
       (SELECT FIRST_VALUE(CODE) OVER(ORDER BY Level)
          FROM TW_GROUP_TREE
         WHERE Code IS NOT NULL
           AND RowNum = 1
    CONNECT BY PRIOR Parent = Grp
    START WITH Grp  = (SELECT SubGrp
                         FROM TW_GROUP
                        WHERE Article  = u1.Article)) AS Code_with_Subquery
  FROM (SELECT 1 Article FROM DUAL UNION ALL SELECT 2 Article FROM DUAL) u1;


   ARTICLE CODE_WITH_SUBQUERY
---------- ------------------
         1                  6
         2                  6
2 rows selected.


The result for both articles here is 6, which is definitely wrong for article 2, as shown above.

When I remove the subquery from the START WITH clause and put it into the query u1, it works correctly.

SELECT Article,
       (SELECT FIRST_VALUE(CODE) OVER(ORDER BY Level)
          FROM TW_GROUP_TREE
         WHERE Code IS NOT NULL
           AND RowNum = 1
    CONNECT BY PRIOR Parent = Grp
    START WITH Grp  = u1.SubGrp) AS Code_without_Subquery
  FROM (SELECT 1 AS Article,
               (SELECT SubGrp
                 FROM TW_GROUP
                WHERE Article  = 1) AS SubGrp
          FROM DUAL
        UNION ALL
        SELECT 2 AS Article,
               (SELECT SubGrp
                 FROM TW_GROUP
                WHERE Article  = 2) AS SubGrp
          FROM DUAL) u1;


   ARTICLE CODE_WITHOUT_SUBQUERY
---------- ---------------------
         1                     6
         2                    99

2 rows selected.


Both versions in the same SQL:

SELECT Article,
       (SELECT FIRST_VALUE(CODE) OVER(ORDER BY Level)
          FROM TW_GROUP_TREE
         WHERE Code IS NOT NULL
           AND RowNum = 1
    CONNECT BY PRIOR Parent = Grp
    START WITH Grp  = (SELECT SubGrp
                         FROM TW_GROUP
                        WHERE Article  = u1.Article)) AS Code_with_Subquery,
       (SELECT FIRST_VALUE(CODE) OVER(ORDER BY Level)
          FROM TW_GROUP_TREE
         WHERE Code IS NOT NULL
           AND RowNum = 1
    CONNECT BY PRIOR Parent = Grp
    START WITH Grp  = u1.SubGrp) AS Code_without_Subquery
  FROM (SELECT 1 AS Article,
               (SELECT SubGrp
                 FROM TW_GROUP
                WHERE Article  = 1) SubGrp
          FROM DUAL d
        UNION ALL
        SELECT 2 AS Article,
               (SELECT SubGrp
                 FROM TW_GROUP
                WHERE Article  = 2)
          FROM DUAL) u1;


And that's my result in 12.2.0.1:

   ARTICLE CODE_WITH_SUBQUERY CODE_WITHOUT_SUBQUERY
---------- ------------------ ---------------------
         1                  6                     6
         2                  6                    99

2 rows selected.


As you can see in my test case on livesql.oracle.com, the problem doesn't appear in 18.0.0.0 anymore.

Correct link again: https://livesql.oracle.com/apex/livesql/s/g8qg94z3f0vxh7o70gi2u5gxk



And now my questions:

Is this a known bug in 12.2.0.1? Is there maybe a patch that can solve this?
I don't want to check manually all the SQLs in our system that use CONNECT BY, because the results may not be correct.


Best regards, Tobias

with LiveSQL Test Case:

and Chris said...

There's certainly a bug of some sort. I can reproduce the behaviour on 12.2 and the fix on 18.1.

I'm not able to track down the root cause though. There's a lot going on in your query. You can use a subquery in your start with clause fine (from at least 11.2.0.4):

SELECT t.*,
       connect_by_root grp,
       level
FROM   TW_GROUP_TREE t
CONNECT BY  PRIOR Parent = Grp
START WITH Grp in (
  SELECT t.SubGrp
  FROM   TW_GROUP t
);

GRP   PARENT   CODE     CONNECT_BY_ROOTGRP   LEVEL   
   20        0        6                   20       1 
    0   <null>       99                   20       2 
   23        0   <null>                   23       1 
    0   <null>       99                   23       2


And if you're looking to find the first non-null value for code in the hierarchy, you could do something like:

with tree as (
  select t.*,
         connect_by_root grp root,
         level lvl
  from   tw_group_tree t
  connect by  prior parent = grp
  start with grp in (
    select t.subgrp
    from   tw_group t
  )
)
  select t.*, 
         first_value ( code ) ignore nulls over ( 
           partition by root 
           order by lvl rows between unbounded preceding and unbounded following
         ) first_code_val
  from   tree t;

GRP   PARENT   CODE     ROOT   LVL   FIRST_CODE_VAL   
   20        0        6     20     1                6 
    0   <null>       99     20     2                6 
   23        0   <null>     23     1               99 
    0   <null>       99     23     2               99


Which again, works from at least 11.2.0.4 (the oldest version I tested). So if you find you get this I'm sure there's a way to rewrite the query to avoid the issue.

If you want more certainty, contact support to see about a patch.

Rating

  (1 rating)

Is this answer out of date? If it is, please let us know via a Comment

Comments

Krzysztof Helbin, September 11, 2018 - 12:40 pm UTC

I changed 'START WITH Grp =' with 'START WITH Grp IN' and the select returns expected results on Oracle 12c 12.2.0.1:

   Oracle Database 12c Enterprise Edition Release 12.2.0.1.0 - 64bit Production

SQL>     SELECT ARTICLE,
  2             (
  3                  SELECT FIRST_VALUE(TW_GROUP_TREE.CODE) OVER(ORDER BY LEVEL)
  4                    FROM TW_GROUP_TREE
  5                   WHERE TW_GROUP_TREE.CODE IS NOT NULL
  6                     AND ROWNUM = 1
  7              CONNECT BY PRIOR TW_GROUP_TREE.PARENT = TW_GROUP_TREE.GRP
  8              START WITH TW_GROUP_TREE.GRP IN (
  9                              SELECT TW_GROUP.SUBGRP
 10                                FROM TW_GROUP
 11                               WHERE TW_GROUP.ARTICLE = U1.ARTICLE
 12                         )
 13             ) AS CODE_WITH_SUBQUERY
 14        FROM (
 15                  SELECT 1 ARTICLE
 16                    FROM DUAL
 17               UNION ALL
 18                  SELECT 2 ARTICLE
 19                    FROM DUAL
 20             ) U1;

   ARTICLE CODE_WITH_SUBQUERY
---------- ------------------
         1                  6
         2                 99

Chris Saxon
September 11, 2018 - 12:55 pm UTC

Works for me too; thanks for sharing.

More to Explore

Analytics

Analytic SQL got you confused? Check out Connor McDonald's complete video course.