Reputation: 11255
I have a table in which based on the type, I want to aggregate values using either the arithmetic mean - AVG(value)
- or the geometric mean - EXP(AVG(LN(value)))
. My attempt is with a CASE WHEN
statement but the issue is that some values can be 0 which means the LN(0)
part will fail.
WITH tbl AS
(
SELECT 0 as val, 'avg' as calc_type FROM DUAL
UNION ALL
SELECT 1 as val, 'geom_mean' FROM DUAL
)
SELECT CASE WHEN max(calc_type) = 'avg' THEN AVG(val)
ELSE EXP(AVG(LN(val))) END as res
FROM tbl
GROUP BY calc_type
ORA-01428: argument '0' is out of range
Based on Oracle's documentation, it appears this is expected as the return_exprs
are evaluated for type stability.
For both simple and searched CASE expressions, all of the return_exprs must either have the same datatype (CHAR, VARCHAR2, NCHAR, or NVARCHAR2, NUMBER, BINARY_FLOAT, or BINARY_DOUBLE) or must all have a numeric datatype.
While I could do a filter for each calculation type and then UNION ALL
the results, it would be ideal to do it in one query as I have to join a lot of tables to get to this step.
Upvotes: 0
Views: 487
Reputation: 690
Since for LN to work, value must be greater than 0. How is this:
WITH tbl AS
(
SELECT 0 as val, 'avg' as calc_type FROM DUAL
UNION ALL
SELECT 1 as val, 'geom_mean' FROM DUAL
)
SELECT CASE WHEN max(calc_type) = 'avg' THEN AVG(val)
WHEN max(calc_type) = 'geom_mean' AND val >0 THEN EXP(AVG(LN(val))) ELSE NULL END as res
FROM tbl
GROUP BY calc_type
Upvotes: 2
Reputation: 1270091
Oracle does process the CASE
expressions in order. Unfortunately, because the aggregation, it also needs to evaluate all the aggregation expressions before it gets around to the CASE
. And that is where the error is occurring. Note: I'm not sure where this is documented for Oracle, but I've seen the problem in other databases.
The solution is to use another CASE
expression to avoid errors:
WITH tbl AS (
SELECT 0 as val, 'avg' as calc_type FROM DUAL
UNION ALL
SELECT 1 as val, 'geom_mean' FROM DUAL
)
SELECT CASE WHEN max(calc_type) = 'avg' THEN AVG(val)
ELSE EXP(AVG(LN(CASE WHEN val > 0 THEN val END))) END as res
FROM tbl
GROUP BY calc_type
Upvotes: 2