Check SQL SYS_REFCURSOR without changing the cursor position - sql

I have a requirement to check if a cursor was able to get some rows from table A. If yes, then do nothing else pull rows from table B.
Currently there are two stored procedures for now.
I am trying to do this in one stored procedure.
I tried using %ROWCOUNT but it doesn't work(because it will return 0) without changing the location of the cursor.
The issue is that my output is the cursor so I don't want to make any changes to.
If I do a fetch, then it shows error also that the return type has changed.
Any idea how to do this, like even if create a copy the cursor so that the fetch and row count could be done on the copy instead of the output cursor.
Pseudo Example
create or replace PROCEDURE "proc"
(
output OUT SYS_REFCURSOR,
)
.
BEGIN
.
.
OPEN output for select * from A
END
BEGIN
//check if output was empty then
OPEN output for select * from B
.
.
END
Update:
I did as suggested
....
BEGIN
...
OPEN output for
with
A as (select ...),
,B as (select ...),
,C as (select ...),
,D as (select ...)
select * from A
union
select * from B where not exists(select null from A)
union
select * from C where not exists(select null from B)
union
select * from D where not exists(select null from C)
END;
Since I know for sure that either one these tables will have data, I also tried the below
....
BEGIN
...
OPEN output for
with
A as (select ...)
,B as (select ...)
,C as (select ...),
,D as (select ...)
select * from A
union
select * from B
union
select * from C
union
select * from D
END;
But it gives me error now that
Error(64,7): PL/SQL: ORA-01789: query block has incorrect number of result columns
The table structure foe these 4 is diff. So they might return diff columns.
Would join make sense if 3 out of 4 are empty?

It's much better to implement your requirement in the same cursor, because it will use the same point-in-time read consistency: in your approach second open opens cursor for a different time than your first cursor and really data in A and B can change already.
This approach is better:
create or replace PROCEDURE "proc"( output OUT SYS_REFCURSOR,...)
....
BEGIN
...
OPEN output for
with
A as (select ...)
,B as (select ...)
select * from A
union all
select * from B where not exists(select null from A)
END;
Another possible solution is to create pipelined table instead, like:
create or replace function ... return {collection type} PIPELINED as
...flag boolean := true;
begin
....
for i in (select * from A) loop
flag:=false;
pipe row(...)
end loop;
if flag then
for i in (select * from B) loop
flag:=false;
pipe row(...)
end loop;
end if;
end;
/
But as you can see both query are opened at different time too.

Related

Reduce duplication in postgres plpgsql function with CTE

In a SQL function I can return a boolean if I do
with myquery as (delete from mytable where id = 'value1' returning 1)
select exists (select * from another_function('value2') where (select count(*) from myquery) > 0);
But in a plpgsql function it doesn't work and gives error query has no destination for result data.
In this function I want to execute another_function if any rows were actually deleted from mytable. The problem is I'm repeating the entire select exists part, because I'm using that in multiple functions.
Is it possible to move more of that logic into another_function? So that I can do something like this?
with myquery as (delete from mytable where id = 'value1' returning 1)
select * from another_function('value2', myquery)
How can I pass the CTE into a function so I don't need to repeat the select exists and where (select count(*) from myquery) > 0) every time I want to call another_function?
I would expect an auxiliary function to take an argument, such as the id being returned from the delete. It would then look like:
with d as (
delete from mytable
where id = 'value1'
returning id
)
select another_function('value2', d.id)
from d;
This operates one value at a time, but that is typically what one would want. If you wanted all ids passed in at once, you could use an array:
with d as (
delete from mytable
where id = 'value1'
returning id
)
select another_function('value2', array_agg(d.id))
from d;

Select Query in if else in Postgres Sql

something like ths
if(1=1)
select * from Table_a
else
slect * from Table_b
without using functions
I am trying something like this
DO $$
DECLARE
a integer := 10;
b integer := 20;
BEGIN
IF a >b THEN
select * from online.fandi_workflow_options ;
else
select * from online.credit_workflow_options ;
END IF;
END
$$;
Can anyone help me here
select * from online.fandi_workflow_options
where a > b
union
select * from online.credit_workflow_options
where a <= b
You can usually replace a logical "if" with a "where" clause; in your case, you're selecting from two different tables, so you have to use a union. This query only works if both tables have the same columns - if not, you can select explicit column names, and add "bogus" columns to each select statement to make them identical.

Oracle: if TABLE_A exists, return count(*), else return 0;

I want to use only one SQL statement, I have tried following one but failed:
SELECT decode(TABLE_COUNT, 0, 0, SELECT COUNT(*) FROM TABLE_A) FROM
(
SELECT COUNT(*) AS TABLE_COUNT FROM USER_TABLES WHERE TABLE_NAME = 'TABLE_A'
)
An example of the dbms_xmlgen approach that #RaymondNijland referred to in a comment:
create table table_a (id) as select level from dual connect by level <= 10;
select nvl(max(to_number(
xmlquery('/ROWSET/ROW/C/text()'
passing xmltype(dbms_xmlgen.getxml('select count(*) as c from ' || table_name))
returning content)
)), 0) as count
from user_tables
where table_name = 'TABLE_A';
COUNT
----------
10
drop table table_a purge;
select nvl(max(to_number(
xmlquery('/ROWSET/ROW/C/text()'
passing xmltype(dbms_xmlgen.getxml('select count(*) as c from ' || table_name))
returning content)
)), 0) as count
from user_tables
where table_name = 'TABLE_A';
COUNT
----------
0
You can easily extend this to query multiple tables at once, or all tables in a schema, etc., by changing the filter and adding a group-by clause.
The WITH clause may be of use in this case if the feature is available in the version of Oracle you are using. The following is what the code would look like.
NOTE: This is a SELECT statement even though it looks like PL/SQL code. The WITH clause supports the use of a PL/SQL declaration within it.
WITH
FUNCTION getCount(p_table_name IN VARCHAR2)
RETURN NUMBER IS
v_count NUMBER;
BEGIN
EXECUTE IMMEDIATE 'SELECT COUNT(*) FROM '||p_table_name INTO v_count;
RETURN mycount;
EXCEPTION
WHEN OTHERS THEN
RETURN 0;
END;
SELECT getCount('hr.employee') FROM DUAL;
The WHEN OTHERS traps the error when the table does not exist and returns 0. If the table exists, it returns the count from the table.
Hope this works for you.

SQL bullk inserts if records do not exist

As part of my build I am changing some .sql files. As it is now, the queries delete particular records from the table and re-add them during the install. What we'd like to have is a check, to see if certain records exist. If they are, do nothing, but if they aren't perform ~30 inserts.
Again these are .sql files and I can't seem to get the syntax right. I think it should be
IF (SELECT COUNT(*) FROM foo WHERE x = bar) <= 0
THEN
BEGIN
Insert statements...
END
END IF;
But this doesn't seem to be working for me. I have also taken a look at IF NOT EXISTS but that as well has not been working. The MERGE command I dont believe is relevant here because I am not pulling from a separate table, just hard-coded insert statements. I am using Oracle SQL developer, am I missing something?
SOLVED
This worked for me:
insert into table (col1, col2)
select 'val1','val2'
from dual
where not exists(select *
from table
where (col1 ="val1" and col2='val2'));
Something like this (I don't have oracle handy)
INSERT INTO TABLE (PK, COl2, Col3)
SELECT 1, 'X', 'Y' FROM DUAL
WHERE NOT EXISTS (SELECT * FROM TABLE WHERE PK = 1);
The select only returns records of the record doesn't exist (matching on the PK column)
You can do one of those per record or you can do something like this:
INSERT INTO TABLE (PK, COl2, Col3)
SELECT 1, 'X', 'Y' FROM DUAL
WHERE NOT EXISTS (SELECT * FROM TABLE WHERE PK = 1)
UNION ALL
SELECT 2, 'X', 'Y' FROM DUAL
WHERE NOT EXISTS (SELECT * FROM TABLE WHERE PK = 2);
UNION ALL
SELECT 3, 'X', 'Y' FROM DUAL
WHERE NOT EXISTS (SELECT * FROM TABLE WHERE PK = 3);
This is probably OK for half a dozen records (it can be generated from Excel formulas).
We cannot use SQL if an IF statement like this:
IF (SELECT COUNT(*) FROM foo WHERE x = bar) <= 0
So what you need to do is execute the count then test it
SELECT COUNT(*)
into v_count
FROM foo WHERE x = bar;
if v_count <= 0
THEN
BEGIN
Insert statements...
END;
END IF;

Oracle SQL : Retrieving non-existing values from IN clause

Having following query:
select table_name
from user_tables
where table_name in ('A','B','C','D','E','F');
Assuming only user_tables records B,C, and F exist, I want to retrieve the non-existing values A,D and E. This is a simple example, on real world the list can be huge.
A good way to generate fake rows is with a standard collection such as sys.odcivarchar2list:
select
tables_to_check.table_name,
case when user_tables.table_name is null then 'No' else 'Yes'end table_exists
from
(
select column_value table_name
from table(sys.odcivarchar2list('does not exist', 'TEST1'))
) tables_to_check
left join user_tables
on tables_to_check.table_name = user_tables.table_name
order by tables_to_check.table_name;
TABLE_NAME TABLE_EXISTS
---------- ------------
TEST1 Yes
does not exist No
if you have list of all those tables to be checked in Table1 then you can use NOT EXISTS clause
select name
from Table1 T1
where not exists ( select 1 from
user_tables U
where T1.name = U.table_name)
Only way is to use NOT EXISTS by converting the IN clause String into a Table of values.(CTE)
This is not a clean solution though. As The maximum length of IN clause expression is going to be 4000 only, including the commas..
WITH MY_STRING(str) AS
(
SELECT q'#'A','B','C','D','E','F'#' FROM DUAL
),
VALUES_TABLE AS
(
SELECT TRIM(BOTH '''' FROM REGEXP_SUBSTR(str,'[^,]+',1,level)) as table_name FROM MY_STRING
CONNECT BY LEVEL <= REGEXP_COUNT(str,',')
)
SELECT ME.* FROM VALUES_TABLE ME
WHERE NOT EXISTS
(SELECT 'X' FROM user_tables u
WHERE u.table_name = ME.table_name);
You can't. These values have to be entered into a temporary table at the least to do the desired operation. Also Oracle's IN clause list cannot be huge (i.e, not more than 1000 values).
Are you restricted to receiving those values as a comma delimited list?
instead of creating a comma delimited list with the source values, populate an array (or a table).
pass the array into a pl/sql procedure (or pull a cursor from the table).
loop through the array(cursor) and use a dynamic cusror to select count(table_name) from user_tables where table_name = value_pulled.
insert into table B when count(table_name) = 0.
then you can select all from table B
select * from tab1;
------------------
A
B
C
D
E
F
Create or replace procedure proc1 as
cursor c is select col1 from tab1;
r tab1.col1%type;
i number;
begin
open c;
loop
fetch c into r;
exit when c%notfound;
select count(tname) into i from tab where tname = r;
if i = 0 then
v_sql := 'insert into tab2 values ('''||r||''');
execute immediate v_sql;
commit;
end if;
end loop;
close c;
end proc1;
select * from tab2;
------------------
A
D
E
if this is not a one-off, then having this proc on hand will be handy.