ORA-00932 if collection used in recursive CTE in where clause - oracle

I have recursive CTE with column of collection type (sys.ku$_vcnt is used here because it is built-in, problem can be reproduced for any collection type). When the collection column is used in recursive part of CTE in where clause, query fails with ORA-00932: inconsistent datatypes: expected UDT got SYS.KU$_VCNT error.
This is minimized example, in real case the collection content is inspected in where clause. Any occurence of collection seems enough for query to fail - for instance not null check as shown in following example:
with r (l, dummy_coll, b) as (
select 1 as l, sys.ku$_vcnt(), null from dual
union all
select l + 1
, r.dummy_coll
, case when r.dummy_coll is not null then 'not null' else 'null' end as b
from r
where l < 5 and r.dummy_coll is not null
)
select * from r;
If and r.dummy_coll is not null is removed from where clause, the query succeeds. Occurence of collection in select clause is not problem (the b column shows the collection is actually not null).
Why does not it work and how to force Oracle to see collection column from previous recursion level in where clause?
Reproduced in Oracle 11 and Oracle 18 (dbfiddle).
Thanks!

Yeah that looks like a bug to me. A scalar select seems to be a workaround. Would that work in your case?
SQL> with r (l, dummy_coll, b) as (
2 select 1 as l, sys.ku$_vcnt(), null from dual
3 union all
4 select l + 1
5 , r.dummy_coll
6 , case when r.dummy_coll is not null then 'not null' else 'null' end as b
7 from r
8 where l < 5 and ( select r.dummy_coll from dual ) is not null
9 )
10 select * from r;
L,DUMMY_COLL,B
1,KU$_VCNT(),
2,KU$_VCNT(),not null
3,KU$_VCNT(),not null
4,KU$_VCNT(),not null
5,KU$_VCNT(),not null

Related

How to call the result of an aggregation function within a pivot block

The following example is only used to understand why I want to do what I'm doing. But the second example is better to understand the problem.
I want to create a table. Each column represent a value that should be in the table.
WITH
FUNCTION f(arg INTEGER, colum_name VARCHAR2)
RETURN VARCHAR2
IS
BEGIN
IF arg = 0
THEN
RETURN 'this column doesn''t exist';
ELSE
RETURN colum_name;
END IF;
END;
t (a) AS (SELECT COLUMN_VALUE FROM sys.odcivarchar2list ('a', 'b', 'd'))
SELECT *
FROM (SELECT a FROM t)
PIVOT (f(COUNT (*),a)
FOR a
IN ('a', 'b', 'c', 'd'));
What it should return:
a
b
c
d
a
b
this column doesn' t exist
d
Because I need the name of the column, I can't make a subquery that takes the result of the aggregation function and uses f in the principal query.
Now the second example:
the first query counts every lines with the value 1 and then the value 2.
SELECT * FROM (SELECT 1 a FROM DUAL) PIVOT (COUNT (*) FOR a IN (1, 2));
It's working.
But this query doesn't work. count(*)+1 isn't considered as a aggreagation functon
SELECT *
FROM (SELECT 1 a FROM DUAL) PIVOT (COUNT (*) + 1 FOR a IN (1, 2));
[Error] Execution (40: 53): ORA-56902: expect aggregate function inside pivot operation
I can't do that ouside the pivot because: bold text in the first example.
to test the code:
https://dbfiddle.uk/?rdbms=oracle_18&fiddle=8ea8a78038fbadddb417c330f0d33314
----------------------------- This part was added after MTO gived an answer:
WITH
FUNCTION f(
arg IN INTEGER,
colum_name IN VARCHAR2
) RETURN VARCHAR2
IS
BEGIN
IF arg = 0 THEN
RETURN 'this column doesn''t exist';
ELSE
RETURN colum_name;
END IF;
END;
t (a) AS (
SELECT COLUMN_VALUE
FROM sys.odcivarchar2list ('a', 'b', 'd')
)
SELECT f(a, 'a') AS a,
f(b, 'b') AS b,
f(c, 'c') AS c,
f(d, 'd') AS d
FROM t
PIVOT (
COUNT (*)
FOR a IN (
'a' AS a,
'b' AS b,
'c' AS c,
'd' AS d
)
);
It's working but:
But In the real case I have a lot of column, I would like to avoid to rewrite them all.
And I would like to avoid making typing error. writing 2 time the same name is prone making typing error.
It will NOT work inside the PIVOT you MUST call the function outside of the PIVOT:
WITH
FUNCTION f(
arg IN INTEGER,
colum_name IN VARCHAR2
) RETURN VARCHAR2
IS
BEGIN
IF arg = 0 THEN
RETURN 'this column doesn''t exist';
ELSE
RETURN colum_name;
END IF;
END;
t (a) AS (
SELECT COLUMN_VALUE
FROM sys.odcivarchar2list ('a', 'b', 'd')
)
SELECT f(a, 'a') AS a,
f(b, 'b') AS b,
f(c, 'c') AS c,
f(d, 'd') AS d
FROM t
PIVOT (
COUNT (*)
FOR a IN (
'a' AS a,
'b' AS b,
'c' AS c,
'd' AS d
)
);
Which outputs:
A
B
C
D
a
b
this column doesn't exist
d
Addressing the comments:
Because I need the name of the column, I can't make a subquery that takes the result of the aggregation function and uses f in the principal query.
Yes, you can; exactly as the example above shows.
An SQL statement (in any dialect, not just Oracle) must have a known, fixed number of output columns before it can be compiled. Therefore, you can know all the columns that will be output from the PIVOT statement (one for each item in the FOR clause and one for each column from the original table that is not used in the PIVOT clause) and can call the function on those columns you want to.
And I would like to avoid making typing error. writing 2 time the same name is prone making typing error.
Perform a code review on your code after writing it and use testing to check that it works properly.
Your second query can be fixed using exactly the same principle (except you need quoted identifiers as, without explicitly providing identifiers, the identifier created by the pivot start with numbers and must be quoted):
SELECT "1" + 1 AS "1",
"2" + 1 AS "2"
FROM (SELECT 1 a FROM DUAL)
PIVOT (COUNT (*) FOR a IN (1, 2));
Which outputs:
1
2
2
1
db<>fiddle here
This solution make possible to get the names of elements inside the table, without having to write these elements 2 times.
The elements that are not in the table have the value null. If you want to change use something other than the null, you have to use this query as a sub-query and use the function nvl. But you have to rewrite the name of all the columns again....
This solution suits me, but it's not totally satisfactory...
with
t (a) AS (SELECT COLUMN_VALUE FROM sys.odcivarchar2list ('a', 'b', 'd'))
SELECT *
FROM t
ANY_VALUE(a)
FOR a
IN ('a', 'b', 'c', 'd'));
code

Subquery as CASE WHEN condition

Below query, syntax error happens on AS PQ_COUNT
SELECT CASE WHEN
RESULTS LIKE '%PQ - Duplicate%' AND
(SELECT COUNT(*) FROM MY_TABLE WHERE ID = '998877'AND FINAL_RESULTS='FL_57') AS PQ_COUNT >= 1
THEN 'PQ count = '|| PQ_COUNT
ELSE RESULTS END AS RESULTS
If I moved AS PQ_COUNT inside select query,
(SELECT COUNT(*) AS PQ_COUNT FROM MY_TABLE WHERE ID = '998877'AND FINAL_RESULTS='FL_57') >= 1
the reference of PQ_COUNT in THEN block become invalid identifier (ORA-00904)
What might go wrong here when addressing subquery as CASE WHEN condition?
One option is to use a subquery (or a CTE, as in my example) to calculate number of rows that satisfy condition, and then - as it contains only one row - cross join it to my_table. Something like this:
SQL> WITH
2 my_table (id, final_results, results) AS
3 -- sample data
4 (SELECT '998877', 'FL_57', 'PQ - Duplicate' FROM DUAL),
5 cnt AS
6 -- calculate COUNT first ...
7 (SELECT COUNT (*) pq_count --> pq_count
8 FROM MY_TABLE
9 WHERE ID = '998877'
10 AND FINAL_RESULTS = 'FL_57')
11 -- ... then re-use it in "main" query
12 SELECT CASE
13 WHEN a.results LIKE '%PQ - Duplicate%'
14 AND b.pq_count >= 1 --> reused here
15 THEN
16 'PQ count = ' || b.PQ_COUNT --> and here
17 ELSE
18 a.results
19 END AS results
20 FROM my_table a CROSS JOIN cnt b;
RESULTS
---------------------------------------------------
PQ count = 1
SQL>
You cannot refer to an alias in the same sub-query where you create it; you need to nest sub-queries (or use a sub-query factoring clause; also called a CTE or WITH clause) and refer to it in the outer one:
SELECT CASE
WHEN results LIKE '%PQ - Duplicate%'
AND pq_count >= 1
THEN 'PQ count = '|| pq_count
ELSE results
END AS RESULTS
FROM (
SELECT results,
( SELECT COUNT(*)
FROM MY_TABLE
WHERE ID = '998877'
AND FINAL_RESULTS='FL_57'
) AS pq_count
FROM your_table
);

Oracle APEX chart - Invalid data is displayed

I have a gauge chart that displays a value label and a percentage. it is based on a query that returns VALUE and MAX_VALUE. In some cases the query returns 0 as MAX_VALUE and then the chart displays a message 'Invalid data'. I believe this is happening because of division by zero. How to prevent this message from getting displayed and return 0 as both VALUE and MAX_VALUE and 0%?
SELECT NUM_FAILED VALUE, TOTAL_NUM MAX_VALUE
FROM
(
SELECT (
SELECT COUNT(*) FROM (select t1.name, t1.run_id
from Table1 t1
LEFT JOIN Table2 t2 ON t1.ID=t2.ID
WHERE t1.START_DATE > SYSDATE - 1
GROUP BY t1.name, t1.run_id)
) AS TOTAL_NUM,
(
SELECT COUNT(*) FROM (select name, run_id
from Table1 t1
LEFT JOIN Tabe2 t2 ON t1.ID=t2.ID
where t1.run_id IN (SELECT run_id FROM Table1 where err_code > 0)
AND t1.START_DATE > SYSDATE - 1
GROUP BY t1.name, t1.run_id)
) as NUM_FAILED
FROM dual)
Thank you for posting a query.
If you want to avoid 0 as a result, how about DECODE?
SELECT decode(NUM_FAILED, 0, 1E99, NUM_FAILED) VALUE,
decode(TOTAL_NUM , 0, 1E99, TOTAL_NUM ) MAX_VALUE
FROM (the rest of your query goes here)
You didn't say what's going on afterwards (i.e. what exactly causes division by zero - is it NUM_FAILED, or TOTAL_NUM)? The idea is: instead of dividing by zero, divide by a very large value (such as 1E99) and you'll get a very small number, for example:
SQL> select 24/1E99 from dual;
24/1E99
----------
2,4000E-98
SQL>
which is, practically, zero. See if you can do something with such an approach.

Fetching Lastrow from the table in toad

I have to fetch the first and last row of the table in Toad.
I have used the following query
select * from grade_master where rownum=(select max(rownum) from grade_master)
select * from grade_master where rownum=1
The second query works to fetch the first row. but the first not working. Anyone please help me.
Thanks in advance
Such request makes sense if you specify sort order of the results - there are no such things in database as "first" and "last" rows if sort order is not specified.
SQL> with t as (
2 select 'X' a, 1 b from dual union all
3 select 'C' , 2 from dual union all
4 select 'A' a, 3 b from dual
5 )
6 select a, b, decode(rn, 1, 'First','Last')
7 from (
8 select a, b, row_number() over(order by a) rn,
9 count(*) over() cn
10 from t
11 )
12 where rn in (1, cn)
13 order by rn
14 /
A B DECOD
- ---------- -----
A 3 First
X 1 Last
In oracle the data is not ordered until you specify the order in you sql statement.
So when you do:
select * from grade_master
oracle will give the rows in anyway it want wants.
OTOH if you do
select * from grade_master order by id desc
Then oracle will give the rows back ordered by id descending.
So to get the last row you could do this:
select *
from (select * from grade_master order by id desc)
where rownum = 1
The rownum is determined BEFORE the "order by" clause is assessed, so what this query is doing is ordering the rows descending (the inside query) and then giving this ordered set to the outer query. The outer gets the first row of the set then returns it.

Oracle: How to count null and non-null rows

I have a table with two columns that might be null (as well as some other columns). I would like to count how many rows that have column a, b, both and neither columns set to null.
Is this possible with Oracle in one query? Or would I have to create one query for each? Can't use group by or some other stuff I might not know about for example?
COUNT(expr) will count the number of rows where expr is not null, thus you can count the number of nulls with expressions like these:
SELECT count(a) nb_a_not_null,
count(b) nb_b_not_null,
count(*) - count(a) nb_a_null,
count(*) - count(b) nb_b_null,
count(case when a is not null and b is not null then 1 end)nb_a_b_not_null
count(case when a is null and b is null then 1 end) nb_a_and_b_null
FROM my_table
Something like this:
SELECT sum(case
when a is null and b is null then 1
else 0
end) as both_null_count,
sum(case
when a is null and b is not null then 1
else 0
end) as only_a_is_null_count
FROM your_table
You can extend that for other combinations of null/not null
select sum(decode(a,null,0,1)) as "NotNullCount", sum(decode(a,null,1,0)) as "NullCount"
from myTable;
Repeat for as many fields as you like.
It can be accomplished in Oracle just in 1 row:
SELECT COUNT(NVL(potential_null_column, 0)) FROM table;
Function NVL checks if first argument is null and treats it as value from second argument.
This worked well for me for counting getting the total count for blank cells on a group of columns in a table in oracle: I added the trim to count empty spaces as null
SELECT (sum(case
when trim(a) is null Then 1
else 0
end)) +
(sum(case
when trim(b) is null
else 0
end)) +
(sum(case
when trim(c) is null
else 0
end)) as NullCount
FROM your_table
Hope this helps
Cheers.
SQL>CREATE TABLE SAMPLE_TAB (COL1 NUMBER NOT NULL, COL2 DATE DEFAULT SYSDATE, COL3 VARCHAR2(20));
SQL>INSERT INTO SAMPLE_TAB(COL1,COL2,COL3) VALUES(121,SYSDATE-2,'SAMPLE DATA');
SQL>INSERT INTO SAMPLE_TAB(COL1,COL2,COL3) VALUES(122,NULL,NULL); --ASSIGN NULL TO COL2
SQL>INSERT INTO SAMPLE_TAB(COL1,COL3) VALUES(123,'SAMPLE DATA RECORD 3');--COL2 DEFAULT VALUE ASSIGN AS SYSDDATE AS PER STRUCTURE.
SQL>COMMIT;
SQL> SELECT * FROM SAMPLE_TAB;
SQL> SELECT *
FROM USER_TAB_COLUMNS U
WHERE 1=1
AND TABLE_NAME='SAMPLE_TAB'
AND NUM_NULLS!=0;
SQL> ANALYZE TABLE SAMPLE_TAB COMPUTE STATISTICS;
SQL> SELECT *
FROM USER_TAB_COLUMNS U
WHERE 1=1
AND TABLE_NAME='SAMPLE_TAB'
AND NUM_NULLS!=0;
One way to do it would be:
select count(*) from table group by nvl2(a, 0, 1), nvl2(b, 0, 1) having nvl2(a,0,1) = nvl2(b,0,1);

Resources