Subquery as CASE WHEN condition - oracle

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
);

Related

List all tables,numrows,numcol and primary key for table if any in oracle

I want to QUERY to generate a table like below:
Tablename||noofrows||noofcolumns||PRIMARKEYCOL(IF ANY for the table)
xyz. 590. 11. xyz_id
bcd. 934 15 null
...
...
So far I was able to do this until now in 2 query:
Query 1:
select a.table_name,count_rows(a.table_name) total_rows,count(b.column_name) total_cols from user_tables a,
,user_tab_columns b
where a.table_name =b.table_name
and a.table_name not like('amp%')
group by a.table_name;
note:Count_rows() is function to calculate rows as stats are not up to date
query 2:
select b.table_name b.column_name PRIMKEY_COL FROM user_constraints a,user_cons_columns b
where
a.constraint_type = 'P'
and a.constraint_name=b.constraint_name
and a.table_name=b.table_name
and b.table_name not like ('amp%');
Problem
I need to merge this table to one query (as shown in example above) so that I can represent the data in one table. My issue in clubbing the table is, with joins and how to make sure table without any primary keys are represent because if I just directly give constraint type ='p' in the where clause of the join I see that it only shows table with Primarykeys I am not able to figure this out.
Primary key can have more than just a single column, so - if you'd want to return them all, you'd either have to "aggregate" them, somehow (listagg looks like a natural choice, but - in 12c - you can't fetch distinct list of columns which you might need because of joining those tables duplicates appear).
As you already use a function to return number of rows (you'd probably rather regularly collect statistics, though, but yes - if tables are frequently modified (inserts and deletes), counting on-the-fly is the way to go.
So, alternative approach with 2 functions and 1 simple query. Yes, I know - context switching and stuff, but - see if it does any good.
This is what you already have (see if your and my code look similar):
SQL> create or replace function count_rows (par_table_name in varchar2)
2 return number
3 is
4 -- return number of rows in PAR_TABLE_NAME
5 retval number;
6 begin
7 execute immediate 'select count(*) from ' ||
8 dbms_assert.sql_object_name(par_table_name) into retval;
9 return retval;
10 end;
11 /
Function created.
This is new (to simplify fetching distinct list of primary key columns):
SQL> create or replace function pk_cols (par_table_name in varchar2)
2 return varchar2
3 is
4 -- return list of primary key columns for PAR_TABLE_NAME, sorted by column's
5 -- position within the primary key constraint
6 retval varchar2(100);
7 begin
8 select listagg(b.column_name, ', ') within group (order by b.position)
9 into retval
10 from user_constraints a join user_cons_columns b on b.constraint_name = a.constraint_name
11 where upper(a.table_name) = dbms_assert.sql_object_name(upper(par_table_name))
12 and a.constraint_type = 'P';
13 return retval;
14 end;
15 /
Function created.
Finally, that simple query I mentioned:
SQL> select a.table_name,
2 count_rows(a.table_name) total_rows,
3 max(a.column_id) total_cols,
4 pk_cols(a.table_name) primkey_cols
5 from user_tab_columns a
6 group by a.table_name;
TABLE_NAME TOTAL_ROWS TOTAL_COLS PRIMKEY_COLS
--------------- ---------- ---------- -------------------------
DEPT 4 3 DEPTNO
SO_TEST 0 1
TEST 7 3 ORD, TERMREGIONAL
EMP 14 8 EMPNO
TRAINING 0 5 TRAINING_PLACE_ID
TEMP_SE 10 3
<snip>
Just join the two queries:
select tab_cols.*, tab_keys.* from
(select a.table_name,count_rows(a.table_name) total_rows,count(b.column_name) total_cols from user_tables a,
,user_tab_columns b
where a.table_name =b.table_name
and a.table_name not like('amp%')
group by a.table_name) tab_cols,
(select b.table_name b.column_name PRIMKEY_COL FROM user_constraints a,user_cons_columns b
where
a.constraint_type = 'P'
and a.constraint_name=b.constraint_name
and a.table_name=b.table_name
and b.table_name not like ('amp%')) tab_keys
where tab_cols.table_name = tab_keys.table_name(+);

count returns <<air>> when there's nothing to group by

Here's my SQL:
select
count(*)
from
sysdba.dw_cmap_arf_tmp
left join SYSDBA.TABLE1 rrc
on rrc.part_work_order = pwo
left join sysdba.TABLE2 R
on rrc.run_number = R.RUN_NUMBER
where
upper(run_type) like '%FEE%'
group by
pwo;
When there's nothing to group by, count returns --air--. It's null nor is it blank. I have modified the above SQL as the following to prove.
select
'>>' || count(*) || '<<' as blah
from
sysdba.dw_cmap_arf_tmp
left join SYSDBA.TABLE1 rrc
on rrc.part_work_order = pwo
left join sysdba.TABLE2 R
on rrc.run_number = R.RUN_NUMBER
where
upper(run_type) like '%ANNEAL%'
group by
pwo;
When I, however, write a statement above to perform an update, I got null. So tried coalesce, but got the same thing.
Does anyone know what I can do to replace --air-- with null or 0? Thanks!
P.S. I have did something research, but couldn't find anything...apologize in advance, if there's already a similar question out there.
Thanks!
This is what you currently have (based on Scott's EMP table): as there's no department 50, you get no rows selected (which is the air you're talking about, I presume).
SQL> with your_current_query as
2 (select count(*) cnt
3 from emp
4 where deptno = &deptno
5 group by job
6 )
7 select cnt
8 from your_current_query;
Enter value for deptno: 50
no rows selected
Just to show that it actually returns something if there are some data there:
SQL> /
Enter value for deptno: 30
CNT
----------
4
1
1
SQL>
OK; now, to do something with the situation where there are no rows selected, use union with a "dummy" row selected from the DUAL table:
SQL> with your_current_query as
2 (select count(*) cnt
3 from emp
4 where deptno = &deptno
5 group by job
6 )
7 select cnt
8 from your_current_query
9 -- add this: if YOUR_CURRENT_QUERY doesn't return anything, union it with
10 -- a select from dual
11 union all
12 select 0
13 from dual
14 where 0 = (select count(*) from your_current_query);
Enter value for deptno: 50
CNT
----------
0
SQL>
So: even though there are no employees in department 50, you got 0 as the result.
Again, to show what happens when there are some rows:
SQL> /
Enter value for deptno: 30
CNT
----------
4
1
1
SQL>
Finally, your query - rewritten - would look like this:
with your_current_query as
(select
count(*) cnt
from
sysdba.dw_cmap_arf_tmp
left join SYSDBA.TABLE1 rrc
on rrc.part_work_order = pwo
left join sysdba.TABLE2 R
on rrc.run_number = R.RUN_NUMBER
where
upper(run_type) like '%FEE%'
group by
pwo
)
select cnt from your_current_query
union all
select 0
from dual
where 0 = (select count(*) from your_Current_query);
See if it helps.

Ora-00932 - expected NUMBER got -

I have been running the below query without issue:
with Nums (NN) as
(
select 0 as NN
from dual
union all
select NN+1 -- (1)
from Nums
where NN < 30
)
select null as errormsg, trunc(sysdate)-NN as the_date, count(id) as the_count
from Nums
left join
(
SELECT c1.id, trunc(c1.c_date) as c_date
FROM table1 c1
where c1.c_date > trunc(sysdate) - 30
UNION
SELECT c2.id, trunc(c2.c_date)
FROM table2 c2
where c2.c_date > trunc(sysdate) -30
) x1
on x1.c_date = trunc(sysdate)-Nums.NN
group by trunc(sysdate)-Nums.NN
However, when I try to pop this in a proc for SSRS use:
procedure pr_do_the_thing (RefCur out sys_refcursor)
is
oops varchar2(100);
begin
open RefCur for
-- see above query --
;
end pr_do_the_thing;
I get
Error(): PL/SQL: ORA-00932: inconsistent datatypes: expected NUMBER got -
Any thoughts? Like I said above, as a query, there is no issue. As a proc, the error appears at note (1) int eh query.
This seems to be bug 18139621 (see MOS Doc ID 2003626.1). There is a patch available, but if this is the only place you encounter this, it might be simpler to switch to a hierarchical query:
with Nums (NN) as
(
select level - 1
from dual
connect by level <= 31
)
...
You could also calculate the dates inside the CTE (which also fails with a recursive CTE):
with Dates (DD) as
(
select trunc(sysdate) - level + 1
from dual
connect by level <= 31
)
select null as errormsg, DD as the_date, count(id) as the_count
from Dates
left join
(
SELECT c1.id, trunc(c1.c_date) as c_date
FROM table1 c1
where c1.c_date > trunc(sysdate) - 30
UNION
SELECT c2.id, trunc(c2.c_date)
FROM table2 c2
where c2.c_date > trunc(sysdate) -30
) x1
on x1.c_date = DD
group by DD;
I'd probably organise it slightly differently, so the subquery doesn't limit the date range directly:
with dates (dd) as
(
select trunc(sysdate) - level + 1
from dual
connect by level <= 31
)
select errormsg, the_date, count(id) as the_count
from (
select null as errormsg, d.dd as the_date, c1.id
from dates d
left join table1 c1 on c1.c_date >= d.dd and c1.c_date < d.dd + 1
union all
select null as errormsg, d.dd as the_date, c2.id
from dates d
left join table2 c2 on c2.c_date >= d.dd and c2.c_date < d.dd + 1
)
group by errormsg, the_date;
but as always with these things, check the performance of each approach...
Also notice that I've switched from union to union all. If an ID could appear more than once on the same day, in the same table or across both tables, then the counts will be different - you need to decide whether you want to count them once or as many times as they appear. That applies to your original query too.

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.

how to make selecting random rows in oracle faster with table with millions of rows

Is there a way to make selecting random rows faster in oracle with a table that has million of rows. I tried to use sample(x) and dbms_random.value and its taking a long time to run.
Thanks!
Using appropriate values of sample(x) is the fastest way you can. It's block-random and row-random within blocks, so if you only want one random row:
select dbms_rowid.rowid_relative_fno(rowid) as fileno,
dbms_rowid.rowid_block_number(rowid) as blockno,
dbms_rowid.rowid_row_number(rowid) as offset
from (select rowid from [my_big_table] sample (.01))
where rownum = 1
I'm using a subpartitioned table, and I'm getting pretty good randomness even grabbing multiple rows:
select dbms_rowid.rowid_relative_fno(rowid) as fileno,
dbms_rowid.rowid_block_number(rowid) as blockno,
dbms_rowid.rowid_row_number(rowid) as offset
from (select rowid from [my_big_table] sample (.01))
where rownum <= 5
FILENO BLOCKNO OFFSET
---------- ---------- ----------
152 2454936 11
152 2463140 32
152 2335208 2
152 2429207 23
152 2746125 28
I suspect you should probably tune your SAMPLE clause to use an appropriate sample size for what you're fetching.
Start with Adam's answer first, but if SAMPLE just isn't fast enough, even with the ROWNUM optimization, you can use block samples:
....FROM [table] SAMPLE BLOCK (0.01)
This applies the sampling at the block level instead of for each row. This does mean that it can skip large swathes of data from the table so the sample percent will be very rough. It's not unusual for a SAMPLE BLOCK with a low percentage to return zero rows.
Here's the same question on AskTom:
http://asktom.oracle.com/pls/apex/f?p=100:11:0::::P11_QUESTION_ID:6075151195522
If you know how big your table is, use sample block as described above. If you don't, you can modify the routine below to get however many rows you want.
Copied from: http://asktom.oracle.com/pls/apex/f?p=100:11:0::::P11_QUESTION_ID:6075151195522#56174726207861
create or replace function get_random_rowid
( table_name varchar2
) return urowid
as
sql_v varchar2(100);
urowid_t dbms_sql.urowid_table;
cursor_v integer;
status_v integer;
rows_v integer;
begin
for exp_v in -6..2 loop
exit when (urowid_t.count > 0);
if (exp_v < 2) then
sql_v := 'select rowid from ' || table_name
|| ' sample block (' || power(10, exp_v) || ')';
else
sql_v := 'select rowid from ' || table_name;
end if;
cursor_v := dbms_sql.open_cursor;
dbms_sql.parse(cursor_v, sql_v, dbms_sql.native);
dbms_sql.define_array(cursor_v, 1, urowid_t, 100, 0);
status_v := dbms_sql.execute(cursor_v);
loop
rows_v := dbms_sql.fetch_rows(cursor_v);
dbms_sql.column_value(cursor_v, 1, urowid_t);
exit when rows_v != 100;
end loop;
dbms_sql.close_cursor(cursor_v);
end loop;
if (urowid_t.count > 0) then
return urowid_t(trunc(dbms_random.value(0, urowid_t.count)));
end if;
return null;
exception when others then
if (dbms_sql.is_open(cursor_v)) then
dbms_sql.close_cursor(cursor_v);
end if;
raise;
end;
/
show errors
Below Solution to this question is not the exact answer but in many scenarios you try to select a row and try to use it for some purpose and then update its status with "used" or "done" so that you do not select it again.
Solution:
Below query is useful but that way if your table is large, I just tried and see that you definitely face performance problem with this query.
SELECT * FROM
( SELECT * FROM table
ORDER BY dbms_random.value )
WHERE rownum = 1
So if you set a rownum like below then you can work around the performance problem. By incrementing rownum you can reduce the possiblities. But in this case you will always get rows from the same 1000 rows. If you get a row from 1000 and update its status with "USED", you will almost get different row everytime you query with "ACTIVE"
SELECT * FROM
( SELECT * FROM table
where rownum < 1000
and status = 'ACTIVE'
ORDER BY dbms_random.value )
WHERE rownum = 1
update the rows status after selecting it, If you can not update that means another transaction has already used it. Then You should try to get a new row and update its status. By the way, getting the same row by two different transaction possibility is 0.001 since rownum is 1000.
Someone told sample(x) is the fastest way you can.
But for me this method works slightly faster than sample(x) method.
It should take fraction of the second (0.2 in my case) no matter what is the size of the table. If it takes longer try to use hints (--+ leading(e) use_nl(e t) rowid(t)) can help
SELECT *
FROM My_User.My_Table
WHERE ROWID = (SELECT MAX(t.ROWID) KEEP(DENSE_RANK FIRST ORDER BY dbms_random.value)
FROM (SELECT o.Data_Object_Id,
e.Relative_Fno,
e.Block_Id + TRUNC(Dbms_Random.Value(0, e.Blocks)) AS Block_Id
FROM Dba_Extents e
JOIN Dba_Objects o ON o.Owner = e.Owner AND o.Object_Type = e.Segment_Type AND o.Object_Name = e.Segment_Name
WHERE e.Segment_Name = 'MY_TABLE'
AND(e.Segment_Type, e.Owner, e.Extent_Id) =
(SELECT MAX(e.Segment_Type) AS Segment_Type,
MAX(e.Owner) AS Owner,
MAX(e.Extent_Id) KEEP(DENSE_RANK FIRST ORDER BY Dbms_Random.Value) AS Extent_Id
FROM Dba_Extents e
WHERE e.Segment_Name = 'MY_TABLE'
AND e.Owner = 'MY_USER'
AND e.Segment_Type = 'TABLE')) e
JOIN My_User.My_Table t
ON t.Rowid BETWEEN Dbms_Rowid.Rowid_Create(1, Data_Object_Id, Relative_Fno, Block_Id, 0)
AND Dbms_Rowid.Rowid_Create(1, Data_Object_Id, Relative_Fno, Block_Id, 32767))
Version with retries when no rows returned:
WITH gen AS ((SELECT --+ inline leading(e) use_nl(e t) rowid(t)
MAX(t.ROWID) KEEP(DENSE_RANK FIRST ORDER BY dbms_random.value) Row_Id
FROM (SELECT o.Data_Object_Id,
e.Relative_Fno,
e.Block_Id + TRUNC(Dbms_Random.Value(0, e.Blocks)) AS Block_Id
FROM Dba_Extents e
JOIN Dba_Objects o ON o.Owner = e.Owner AND o.Object_Type = e.Segment_Type AND o.Object_Name = e.Segment_Name
WHERE e.Segment_Name = 'MY_TABLE'
AND(e.Segment_Type, e.Owner, e.Extent_Id) =
(SELECT MAX(e.Segment_Type) AS Segment_Type,
MAX(e.Owner) AS Owner,
MAX(e.Extent_Id) KEEP(DENSE_RANK FIRST ORDER BY Dbms_Random.Value) AS Extent_Id
FROM Dba_Extents e
WHERE e.Segment_Name = 'MY_TABLE'
AND e.Owner = 'MY_USER'
AND e.Segment_Type = 'TABLE')) e
JOIN MY_USER.MY_TABLE t ON t.ROWID BETWEEN Dbms_Rowid.Rowid_Create(1, Data_Object_Id, Relative_Fno, Block_Id, 0)
AND Dbms_Rowid.Rowid_Create(1, Data_Object_Id, Relative_Fno, Block_Id, 32767))),
Retries(Cnt, Row_Id) AS (SELECT 1, gen.Row_Id
FROM Dual
LEFT JOIN gen ON 1=1
UNION ALL
SELECT Cnt + 1, gen.Row_Id
FROM Retries
LEFT JOIN gen ON 1=1
WHERE Retries.Row_Id IS NULL AND Retries.Cnt < 10)
SELECT *
FROM MY_USER.MY_TABLE
WHERE ROWID = (SELECT Row_Id
FROM Retries
WHERE Row_Id IS NOT NULL)
Can you use pseudorandom rows?
select * from (
select * from ... where... order by ora_hash(rowid)
) where rownum<100

Resources