Skip to Main Content
  • Questions
  • Strange behavior dbms_random.value in sub-select and select is higher

Breadcrumb

Question and Answer

Chris Saxon

Thanks for the question, Artem.

Asked: May 25, 2022 - 10:26 am UTC

Last updated: May 27, 2022 - 5:17 pm UTC

Version: Oracle Database 18c

Viewed 1000+ times

You Asked

Hi, Tom.
I noticed strange behavior dbms_random.value using in sub-select.
In LiveSQL example dbms_random.value is not unique for rows in sub-select.

Oracle, under a certain meaningless condition (or 1!=0), transfers the ration column to the main select

Because of this, one entry from w_table1 can be returned for four entries from w_table2
And also , records are inexplicably disappearing from w_table1

I found three ways to solve the problem:
1) MATERIALIZE for w_table1
2) Hint ORDERED in main select
3) Create a table using dbms_random before the select

Is this some kind of bug? Or did I miss something when writing the query?

with LiveSQL Test Case:

and Chris said...

I must admit: I've no idea what you're trying to achieve with this query.

The reason the queries give different results is due to the different plans.

Adding OR V.ROUTE_ID IS NOT NULL prevents the optimizer from using a hash join. So it uses nested loops to join the tables. This is because there's no longer a guaranteed equijoin (an = comparison) between the tables.

Without this predicate it can use a hash join to combine them. This also applies to conditions that are guaranteed to be false such as 1 = 0. The optimizer can safely remove this because it has no effect.

So why does this matter?

With a hash join the database reads each table once. So every row from TMP_TABLE_TEST has the same random value because this is only executed once.

When executing nested loops, the database queries the inner table once for each row from the outer query. W_TABLE2 returns four rows, so TMP_TABLE_TEST is accessed four times. This means there are four calls to dbms_random, each of which can return a different value.

This means you'll get different results from these. Here's a simplified example:

create table tmp_table_test as 
    select rownum as rid, 
           1 as operator_id, 
           cast(null as number) as route_id 
      from dual t 
   connect by level <= 5;
   
with w_table1 as  ( 
  select t.rid, 
         t.operator_id, 
         t.route_id, 
         trunc(dbms_random.value, 2) as ratio 
    from tmp_table_test t 
  ), w_table2 as ( 
    select 1 as operator_id,  
           level as route_id, 
           case when mod(level,2) = 0 then 0 else 0.5 end as ratio_from, 
           case when mod(level,2) = 0 then 0.4999 else 1 end as ratio_to, 
           case when level > 2 then 1 else 2 end gate 
    from dual 
    connect by level <= 4 
) 
select *
from   ( 
  select ratio,
         v.rid, 
         v.operator_id, 
         s.gate, 
         s.route_id, 
         row_number() over(partition by v.rid order by v.operator_id) rn 
  from w_table1 v 
  inner join w_table2 s 
  on  v.ratio between s.ratio_from and s.ratio_to 
  and (s.operator_id = v.operator_id or v.route_id is not null ) 
) tb;  

     RATIO        RID OPERATOR_ID       GATE   ROUTE_ID         RN
---------- ---------- ----------- ---------- ---------- ----------
       .58          1           1          2          1          1
       .83          1           1          1          3          2
       .15          2           1          1          4          1
       .98          2           1          1          3          2
       .98          3           1          2          1          1
       .44          3           1          2          2          2
       .22          4           1          2          2          1
       .91          5           1          1          3          1
       .39          5           1          1          4          2
       .43          5           1          2          2          3

select * 
from   dbms_xplan.display_cursor(null, null, 'ROWSTATS LAST');

--------------------------------------------------------------------------------------
| Id  | Operation                        | Name           | Starts | E-Rows | A-Rows |
--------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                 |                |      1 |        |     10 |
|   1 |  VIEW                            |                |      1 |      1 |     10 |
|   2 |   WINDOW SORT                    |                |      1 |      1 |     10 |
|   3 |    NESTED LOOPS                  |                |      1 |      1 |     10 |
|   4 |     VIEW                         |                |      1 |      1 |      4 |
|   5 |      CONNECT BY WITHOUT FILTERING|                |      1 |        |      4 |
|   6 |       FAST DUAL                  |                |      1 |      1 |      1 |
|*  7 |     VIEW                         |                |      4 |      1 |     10 |
|   8 |      TABLE ACCESS FULL           | TMP_TABLE_TEST |      4 |      5 |     20 |
--------------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   7 - filter(("V"."RATIO">="S"."RATIO_FROM" AND "V"."RATIO"<="S"."RATIO_TO" 
              AND ("S"."OPERATOR_ID"="V"."OPERATOR_ID" OR "V"."ROUTE_ID" IS NOT NULL)))

with w_table1 as  ( 
  select t.rid, 
         t.operator_id, 
         t.route_id, 
         trunc(dbms_random.value, 2) as ratio 
    from tmp_table_test t 
  ), w_table2 as ( 
    select 1 as operator_id,  
           level as route_id, 
           case when mod(level,2) = 0 then 0 else 0.5 end as ratio_from, 
           case when mod(level,2) = 0 then 0.4999 else 1 end as ratio_to, 
           case when level > 2 then 1 else 2 end gate 
    from dual 
    connect by level <= 4 
) 
select *
from   ( 
  select ratio,
         v.rid, 
         v.operator_id, 
         s.gate, 
         s.route_id, 
         row_number() over(partition by v.rid order by v.operator_id) rn 
  from w_table1 v 
  inner join w_table2 s 
  on  v.ratio between s.ratio_from and s.ratio_to 
  and (s.operator_id = v.operator_id or 1=0) 
) tb;

     RATIO        RID OPERATOR_ID       GATE   ROUTE_ID         RN
---------- ---------- ----------- ---------- ---------- ----------
       .63          1           1          2          1          1
       .63          1           1          1          3          2
       .16          2           1          2          2          1
       .16          2           1          1          4          2
       .78          3           1          2          1          1
       .78          3           1          1          3          2
       .82          4           1          2          1          1
       .82          4           1          1          3          2
       .62          5           1          2          1          1
       .62          5           1          1          3          2

select * 
from   dbms_xplan.display_cursor(null, null, 'ROWSTATS LAST');

--------------------------------------------------------------------------------------
| Id  | Operation                        | Name           | Starts | E-Rows | A-Rows |
--------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                 |                |      1 |        |     10 |
|   1 |  VIEW                            |                |      1 |      1 |     10 |
|   2 |   WINDOW SORT                    |                |      1 |      1 |     10 |
|*  3 |    HASH JOIN                     |                |      1 |      1 |     10 |
|   4 |     VIEW                         |                |      1 |      1 |      4 |
|   5 |      CONNECT BY WITHOUT FILTERING|                |      1 |        |      4 |
|   6 |       FAST DUAL                  |                |      1 |      1 |      1 |
|   7 |     VIEW                         |                |      1 |      5 |      5 |
|   8 |      TABLE ACCESS FULL           | TMP_TABLE_TEST |      1 |      5 |      5 |
--------------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   3 - access("S"."OPERATOR_ID"="V"."OPERATOR_ID")
       filter(("V"."RATIO">="S"."RATIO_FROM" AND "V"."RATIO"<="S"."RATIO_TO"))


If you explain what you're trying to do we'll see how we can help rewrite the query to avoid this problem.

Rating

  (1 rating)

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

Comments

Zero rows

Artem, May 27, 2022 - 12:12 pm UTC

Thanks, I figured out what happens when more rows are returned than expected.
And now while I was writing an example where less returns than expected, I also understood the reason for this.
Oracle first joins the tables, and then filters by the dbms_random between condition (because of this, 0 rows are sometimes returned)

create table tmp_table_test as 
    select 1 as route_id 
      from dual t 
   connect by level <= 100;

with w_table1 as  ( 
    select rownum as rnum
      from tmp_table_test
  ), w_table2 as ( 
  select 1 as route_id, 0 as ratio_from, 0.49 as ratio_to
  from dual
  union all 
  select 2 as route_id, 0.5 as ratio_from,1 as ratio_to
  from dual
)
select count(1) cnt
from (
  select s.route_id, 
         row_number() over(partition by v.rnum order by v.rnum) rn 
  from w_table1 v 
  inner join w_table2 s
  on trunc(dbms_random.value, 2) between s.ratio_from and s.ratio_to 
) tb;

Thank you, I understand everything
Chris Saxon
May 27, 2022 - 5:17 pm UTC

Glad this helped

More to Explore

PL/SQL demos

Check out more PL/SQL tutorials on our LiveSQL tool.

PL/SQL docs

PL/SQL reference manual from the Oracle documentation library