Thanks Chris, but...
Steve, June 24, 2019 - 4:40 pm UTC
--- Chris, I was able to repeat the behaviour using a table I created:
drop table my_tab;
create table my_tab as select object_id from all_objects fetch first 100 rows only;
select * from my_tab;
with
function
getrand(pval in number) return number is
begin
return round(dbms_random.value(0,20));
end;
my_rnd as
(
select object_id, getrand(object_id) getrnd from my_tab
)
select object_id, getrnd from my_rnd where getrnd = 10;
June 25, 2019 - 7:47 am UTC
Nice investigation, thanks for sharing this.
My LiveSQL link for code using a table
Steve, June 24, 2019 - 4:41 pm UTC
Good observation
siva, June 25, 2019 - 1:43 pm UTC
Hi Steve/Chris,
I am adding my observations here .
1.Adding some character to function result , we are getting wrong result .
with
function
getrand(pval in number) return number is
begin
return round(dbms_random.value(0,20));
end;
my_rnd as
(
select object_id, getrand(object_id)||'_x' getrnd from my_tab
)
select object_id, getrnd from my_rnd where getrnd = '10_x';
2.Adding analytical function to the function . Here we are getting the expected result.
with
function
getrand(pval in number) return number is
begin
return round(dbms_random.value(0,20));
end;
my_rnd as
(
select object_id, max( getrand(object_id)) over ( partition by object_id ) getrnd from my_tab
)
select getrnd , count(*) from my_rnd --where getrnd = 10;
group by cube(getrnd)
Use /*+ MATERIALIZE */ hint
siva, June 25, 2019 - 2:48 pm UTC
Hi Steve ,
By using /*+ MATERIALIZE */ hint we can get the desired result .
I think this is a feature . While using procedure and function in with clause we have to use this hint.
with
function
getrand(pval in number) return number is
begin
return round(dbms_random.value(0,20));
end;
my_rnd as
(
select /*+ MATERIALIZE */ object_id, getrand(object_id) getrnd from my_tab
)
select object_id, getrnd from my_rnd where getrnd = 10
June 25, 2019 - 3:29 pm UTC
Yes, materializing the results forces the database to compute the subquery first. Then filter on the results.
So the database can't push the predicate inside the view and end up calling the function twice for one row.
Just one more thing
Steve, June 25, 2019 - 3:12 pm UTC
Thanks for your contributions Siva. Your analytics code shows that all records are being processed. I too discovered this when, after removing the getrnd = 10 constraint, I get 100 records back (this is using the my_tab table I created). So, I submit that the constraint actually works - that the few records we get back when using the constraint are *actually* the ones where the result of function call is in fact 10. Problem is that the column value displayed doesn't show 10.
It makes some sense that materializing the result before displaying would help. However, the original problem remains. I think the next thing to investigate is if the problem exists only with call to dbms_random.value. I tried two other things and they worked fine - returning a hard coded value, and doing a for loop to sum the digits of a number. I wonder if a call to another package function or procedure could result in similar behaviour.
Thanks again!
June 25, 2019 - 3:34 pm UTC
I've amended my answer. The problem is that dbms_random is non-deterministic. And - because the database pushes the where clause inside the subquery - it calls it twice for each row.
Once in the where clause. And again in the select clause. These can (and as you've found do) give different results.
Materializing the subquery avoids this problem by forcing the database to compute the function for each row and storing the output. Then applying the where on its output.
The upshot is: whenever you call a non-deterministic function in SQL, unexpected things can happen!
Ah, Thanks Chris
Steve, June 25, 2019 - 3:35 pm UTC
I just saw your update. Yes, it's weird. Being non-deterministic explains why the different results when the filter is processed, but it doesn't explain why the filter calls the function again. Why does that happen?
Yes, materializing the result before filtering makes sense, but it feels like a workaround. I told Oracle to filter on the result, and it didn't. Instead, it's like it used the "definition" of the getrnd column, "getrand(object_id)", to process the filter. I just tried to cast the column to number in the WITH table - no help.
Being non-deterministic explains the different display values. But what explains the second call to the function when I told it to use the result of the first call? Sure feels like a bug. Either way, I'll file it in my gotchas file.
Thanks Chris and Siva!
June 25, 2019 - 3:55 pm UTC
The database processes the where clause before the select clause. So it's going:
where random_fn = val
Then for the rows that pass this check, you have:
select random_fn
So that's two separate invocations of the function.
I told Oracle to filter on the result, and it didn't
No. A subquery is NOT a way of telling the database "do this first". It's just a logical construct to make the statement easier to write/understand.
The optimizer's free to rearrange your SQL statements to what it assumes are logically equivalent statements. But this only holds for deterministic functions!
IMHO, Non-deterministic is not the problem
Steve, June 25, 2019 - 3:53 pm UTC
It's exactly why I told O to use the result column getrnd. I knew calling the function again would give a different result. Using materialize works, but I might object on a very large result set.
So pushing the predicate is why the function gets called again? I tried all the applicable hints, esp. NO_PUSH_PRED, to no avail. Ugh!
Still, I appreciate your help. Bug or not, it's a gotcha in my book.
June 25, 2019 - 3:56 pm UTC
So pushing the predicate is why the function gets called again?
Yep.
siva, June 27, 2019 - 10:50 am UTC
Hi Steve ,
Below query is also working.
with
function
getrand(pval in number) return number is
begin
return round(dbms_random.value(0,20));
end;
my_rnd as
(
select object_id, (
select getrand(object_id) from dual ) getrnd from my_tab
)
select object_id, getrnd from my_rnd where getrnd = 11;
The similar situation Connor explained in below link .
https://connor-mcdonald.com/2018/06/07/call-me-many-many-times/