Implementation of "simple" case-when expressions broken for expressions that are not constant per row
Details
| Detail name | Value |
|---|---|
| Changelog Number | 6857 |
| Type | Bug |
| Status | Resolved |
| Affected Versions | Exasol 6.2.0, Exasol 7.0.0 |
| Fix Versions | Exasol 7.0.7, Exasol 6.2.14 |
| Resolution Date | 2021-02-02 |
Background:
In some situations, expressions that are used in more than one place may get calculated independently in those places to allow local optimizations to take place.
Problem
If affected expressions are not deterministic (esp. UDF and the RANDOM function), this may lead to unexpected or wrong results.
Example
SELECT
CASE FLOOR(RANDOM() * 4)
WHEN 0 THEN 'A'
WHEN 1 THEN 'B'
WHEN 2 THEN 'C'
WHEN 3 THEN 'D'
ELSE 'WRONG'
END;
returns 'WRONG' with quite high probability.
The problem is that internally the optimizer replaces this query with
SELECT
CASE
WHEN FLOOR(RANDOM() * 4) = 0 THEN 'A'
WHEN FLOOR(RANDOM() * 4) = 1 THEN 'B'
WHEN FLOOR(RANDOM() * 4) = 2 THEN 'C'
WHEN FLOOR(RANDOM() * 4) = 3 THEN 'D'
ELSE 'WRONG'
END;
As each RANDOM call is handled independently, all of the case conditions may become false.
Workaround(s)
For the simple scalar case it is possible to use a scalar subselect as workaround:
SELECT
CASE (select FLOOR(RANDOM() * 4))
WHEN 0 THEN 'A'
WHEN 1 THEN 'B'
WHEN 2 THEN 'C'
WHEN 3 THEN 'D'
ELSE 'WRONG'
END;
In general, it is quite tricky to get right and usually requires a pre-materialization of the non-deterministic expression.
Assume we want to evaluate the CASE expression for each for of values 1,2,3, here is a way to do it:
SELECT
CASE rnd
WHEN 0 THEN 'A'
WHEN 1 THEN 'B'
WHEN 2 THEN 'C'
WHEN 3 THEN 'D'
ELSE 'WRONG'
END v
from (
select
x
, floor(random()*4) as rnd
from (
values 1,2,3 as p(x)
)
-- PRE-MATERIALIZE
order by false
);
The idea is to add an extra "random" column (rnd) to each row.