Execute select/insert statement within an IF clause Oracle - oracle

I need to execute some statements within the IF clause only if a table exists.
But the issue I am facing is, even when the condition is false, the statements are getting executed.
DECLARE
count_matching_row NUMBER := 0;
count_matching_tbl NUMBER := 0;
BEGIN
SELECT COUNT(*)
INTO count_matching_tbl
FROM user_tables
WHERE LOWER(table_name) = 'tab1';
IF(count_matching_tbl = 1)
THEN
SELECT COUNT (*)
INTO count_matching_row
FROM test1
WHERE ID IN (SELECT ID FROM tab1);
IF(count_matching_row = 0)
THEN
INSERT INTO review_case
SELECT
DISTINCT ID, d,e
FROM tab1
WHERE ID IS NOT NULL;
INSERT INTO review_case_payer
SELECT
a,b,c
FROM tab1
WHERE a IS NOT NULL;
COMMIT;
END IF;
END IF;
END;
/
Whenever I execute these statements, if the table 'tab1' exists it works fine.
If the table tab1 does not exist I get the error
"ORA-06550: line 13, column 14:
PL/SQL: ORA-00942: table or view does not exist"
I get similar errors for each line where I try to access table "tab1"
I tried with ref cursor but still the same, I cannot use it for insert statements.

Your error is due to the fact that you're using a table that may not exist; this error is thrown because the script has compile problems, not data problems, so the way you try to use the IF is not enough to handle your situation.
You need to use some dynamic SQL to handle an object that could not exist; for example, see the following.
If the table does not exist, nothing will be done:
SQL> select * from tab1;
select * from tab1
*
ERROR at line 1:
ORA-00942: table or view does not exist
SQL> declare
2 vCountTab number;
3 begin
4 select count(1)
5 into vCountTab
6 from user_tables
7 where table_name = 'TAB1';
8
9 if vCountTab = 1 then
10 execute immediate 'insert into TAB1 values (1, 2)';
11 end if;
12 end;
13 /
PL/SQL procedure successfully completed.
If the table exists, the insert will be done:
SQL> create table tab1(a number, b number);
Table created.
SQL> declare
2 vCountTab number;
3 begin
4 select count(1)
5 into vCountTab
6 from user_tables
7 where table_name = 'TAB1';
8
9 if vCountTab = 1 then
10 execute immediate 'insert into TAB1 values (1, 2)';
11 end if;
12 end;
13 /
PL/SQL procedure successfully completed.
SQL> select * from tab1;
A B
---------- ----------
1 2
SQL>

Related

Update second column

I have a table "TEST_TABLE" with two columns TABLE_NAME and RECORD_COUNT.
enter image description here
We want to update the column RECORD_COUNT by taking the total records in table specified in TABLE_NAME.
You could do this with dynamic SQL but why be complicated?
Create a view
Create view my_tables as
Select
'table_1' as "table name",
count(*) as "rows"
From table_1
Add the following for each table
Union all
Select 'table_2',count(*) From table_2
You can then use the view like a table:
Select * from my_tables;
OK, but - why wouldn't you use information Oracle already provides for you? You should regularly gather statistics anyway, so:
SQL> execute dbms_stats.gather_schema_stats(ownname => 'SCOTT', estimate_percent => null);
PL/SQL procedure successfully completed.
and then fetch num_rows from user_tables:
SQL> select table_name, num_rows from user_tables where rownum <= 10;
TABLE_NAME NUM_ROWS
-------------------- ----------
EMP 14
DEPT 4
BONUS 0
SALGRADE 5
DUMMY 1
TBL_ERROR 1
AUDIT_TAB 2
SOURCE_DET 3
EXAMPLE 1
FLIGHT 0
10 rows selected.
SQL>
It can't be much worse than your attempt (due to possible frequent changes to tables' contents; inserts and deletes) because you'd collect your own data periodically (how often?) anyway.
If it has to be your way, then you'd use dynamic SQL, looping through all tables in your schema and actually count number of rows:
SQL> create table test_table
2 (table_name varchar2(30),
3 num_rows number);
Table created.
SQL> create or replace procedure p_test as
2 l_cnt number;
3 begin
4 execute immediate 'truncate table test_table';
5 for cur_R in (select table_name from user_tables) loop
6 execute immediate 'select count(*) from ' ||cur_R.table_name into l_Cnt;
7 insert into test_table (table_name, num_rows) values (cur_r.table_name, l_cnt);
8 end loop;
9 end;
10 /
Procedure created.
Running the procedure and viewing the result:
SQL> exec p_test;
PL/SQL procedure successfully completed.
SQL> select * From test_Table where rownum <= 10;
TABLE_NAME NUM_ROWS
-------------------- ----------
EMP 14
DEPT 4
BONUS 0
SALGRADE 5
DUMMY 1
TBL_ERROR 1
AUDIT_TAB 2
SOURCE_DET 3
EXAMPLE 1
FLIGHT 0
10 rows selected.
SQL>
Note that performance will suffer as number of tables and number of rows stored in them grows.
If I were you, I'd go for the first option.

Avoiding "select into" error in PL/SQL when filtering a table with a variable

In the following minimal PL/SQL query example, I can't get the query to work, in order to filter records from a table based on a certain date, which is stored in a variable.
The goal obviously is to end up with different query results, by changing the date variable to some other moment, different from sysdate.
This query does succeed in printing the calculation date variable's content, but the select query afterwards fails with ORA-06550: line 6, column 3 (so on 'select *' below) : PLS-00428: an INTO clause is expected in this SELECT statement.
DECLARE
v_calculation_date date := sysdate;
BEGIN
dbms_output.put_line(v_calculation_date);
select *
from t
WHERE to_number(to_char(trunc(t.some_date), 'YYYYMM'))
BETWEEN to_number(to_char(add_months(v_calculation_date,-24), 'YYYYMM'))
AND to_number(to_char(add_months(v_calculation_date,-1), 'YYYYMM'));
END;
OK, with a SELECT INTO then... This again prints the calculation date variable's content just fine, but the select query afterwards again fails, with the same error:
DECLARE
v_calculation_date date;
BEGIN
select sysdate into v_calculation_date from dual; -- dual is a dummy table with only one row and one column, just what we need here.
dbms_output.put_line(v_calculation_date);
select *
from t
WHERE to_number(to_char(trunc(t.some_date), 'YYYYMM'))
BETWEEN to_number(to_char(add_months(v_calculation_date,-24), 'YYYYMM'))
AND to_number(to_char(add_months(v_calculation_date,-1), 'YYYYMM'));
END;
I already have a SELECT INTO, why is the normal SELECT, which follows afterwards, then refused?
For the sake of completeness: this is a minimal query example for easy reproduction of the problem. My actual query is more complex, I eventually want something like this to work:
DECLARE
v_calculation_date date;
BEGIN
DROP TABLE t1;
CREATE TABLE t1 as (
WITH intermediary1 AS (
-- The actual query from above, as a subquery
SELECT *
FROM t
WHERE to_number(to_char(trunc(t.some_date), 'YYYYMM'))
BETWEEN to_number(to_char(add_months(v_calculation_date,-24), 'YYYYMM'))
AND to_number(to_char(add_months(v_calculation_date,-1), 'YYYYMM'));
)
)
/* other drops, creates, inserts ... */
END
What is wrong in my query?
Execution info: the SQL dialect is PL/SQL, the IDE is PL/SQL Developer version 13.0, the database server runs Oracle Database 12c Enterprise Edition Release 12.2.
In PL/SQL, every SELECT (that isn't part of a cursor) must have an INTO clause. Therefore, it's not enough that one of your SELECTs has it.
As of your final code (the one that contains CREATE TABLE): that won't work. DDL can't be executed from PL/SQL, unless you use dynamic SQL (execute immediate). Therefore, all your "other drops, creates, ..." will also have to be dynamic.
Note that we usually do not create objects dynamically; create table at SQL level. Then, if you want to populate it with different data, do so (either from SQL or PL/SQL). First delete "old" data - you can use delete or truncate (but it is DDL so - as you already know by now - it requires execute immediate), and then re-populate the table.
Dynamic SQL is difficult to debug. Don't use it if you don't have to.
If you're concerned about your own data so that other users wouldn't interfere, consider creating a global temporary table (or private; depending on database version you use).
[EDIT, based on your comments]
I'm afraid you're misinterpreting reality. DDL can't be executed in PL/SQL unless it is dynamic SQL. Your "final" code is an anonymous PL/SQL block (I'm talking about the last code you posted, the one that begins with the DECLARE, contains DROP and CREATE table t1) so it can not execute those statements as you posted.
Here's a demo. I don't have your tables so I'll mimic what you're doing, using Scott's sample DEPT table instead on my 11gXE database.
Does the WITH factoring clause work? Yes.
SQL> with intermediary1 as (
2 -- The actual query from above, as a subquery
3 select *
4 from dept
5 where 1 = 1
6 )
7 select * from intermediary1;
DEPTNO DNAME LOC
---------- -------------- -------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
Does it work with the CREATE TABLE? Not on my 11gXE:
SQL> create table t1 as (
2 with intermediary1 as (
3 -- The actual query from above, as a subquery
4 select *
5 from dept
6 where 1 = 1
7 )
8 select * from intermediary1
9 );
)
*
ERROR at line 9:
ORA-32034: unsupported use of WITH clause
I tried it on apex.oracle.com which runs 18cEE - doesn't work either.
Maybe your database version supports it, can't tell as I don't have any higher database version to try it on.
But OK, doesn't matter, that's easily fixed by moving a CTE into a subquery:
SQL> create table t1 as
2 select *
3 from (-- The actual query from above, as a subquery
4 select *
5 from dept
6 where 1 = 1
7 );
Table created.
So, everything is OK with the query itself, it won't cause my PL/SQL block to fail because of it.
OK, so - let's try it, the way you say you use frequently.
SQL> declare
2 v_calculation_date date;
3 begin
4 drop table t1;
5
6 create table t1 as
7 select *
8 from (-- The actual query from above, as a subquery
9 select *
10 from dept
11 where 1 = 1
12 );
13 /* other drops, creates, inserts ... */
14 end;
15 /
drop table t1;
*
ERROR at line 4:
ORA-06550: line 4, column 3:
PLS-00103: Encountered the symbol "DROP" when expecting one of the following:
( begin case declare exit for goto if loop mod null pragma
raise return select update while with <an identifier>
<a double-quoted delimited-identifier> <a bind variable> <<
continue close current delete fetch lock insert open rollback
savepoint set sql execute commit forall merge pipe purge
SQL>
Whooops! There's an error on DROP. Let's remove it and leave CREATE TABLE:
SQL> declare
2 v_calculation_date date;
3 begin
4 -- drop table t1;
5
6 create table t1 as
7 select *
8 from (-- The actual query from above, as a subquery
9 select *
10 from dept
11 where 1 = 1
12 );
13 /* other drops, creates, inserts ... */
14 end;
15 /
create table t1 as
*
ERROR at line 6:
ORA-06550: line 6, column 3:
PLS-00103: Encountered the symbol "CREATE" when expecting one of the following:
( begin case declare exit for goto if loop mod null pragma
raise return select update while with <an identifier>
<a double-quoted delimited-identifier> <a bind variable> <<
continue close current delete fetch lock insert open rollback
savepoint set sql execute commit forall merge pipe purge
SQL>
Whoops #2! Error on CREATE TABLE. Looks like your way doesn't really work. Let's try it my way, using dynamic SQL:
SQL> declare
2 v_calculation_date date;
3 begin
4 execute immediate 'drop table t1';
5
6 execute immediate (
7 'create table t1 as
8 select *
9 from (-- The actual query from above, as a subquery
10 select *
11 from dept
12 where 1 = 1
13 )'
14 );
15 /* other drops, creates, inserts ... */
16 end;
17 /
PL/SQL procedure successfully completed.
SQL> select * from t1;
DEPTNO DNAME LOC
---------- -------------- -------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
SQL>
Now it works.
I'd be more than happy if you demonstrate the way you're doing it without dynamic SQL. Please, post your own SQL*Plus session (just like I did). Or, if you use some GUI, post a screenshot, no problem.
You do not need a PL/SQL block for your query and can simplify it to:
SELECT *
FROM t
WHERE ADD_MONTHS(TRUNC(SYSDATE, 'MM'), -24) <= some_date
AND some_date < TRUNC(SYSDATE, 'MM');
If you want it in a PL/SQL block then you want to use SELECT ... INTO ... if the query is going to return a single row:
DECLARE
v_calculation_date DATE := SYSDATE;
v_col1 T.COL1%TYPE;
v_col2 T.COL2%TYPE;
v_some_date T.SOME_DATE%TYPE;
BEGIN
SELECT col1, col2, some_date
INTO v_col1, v_col2, v_some_date
FROM t
WHERE ADD_MONTHS(TRUNC(v_calculation_date, 'MM'), -24) <= some_date
AND some_date < TRUNC(v_calculation_date, 'MM');
-- Do something with the variables.
DBMS_OUTPUT.PUT_LINE( v_col1 || ', ' || v_col2 || ', ' || v_some_date );
END;
/
If you can return multiple rows then use SELECT ... BULK COLLECT INTO ... or use a cursor:
DECLARE
v_calculation_date DATE := SYSDATE;
BEGIN
FOR cur IN (
SELECT *
FROM t
WHERE ADD_MONTHS(TRUNC(v_calculation_date, 'MM'), -24) <= some_date
AND some_date < TRUNC(v_calculation_date, 'MM')
)
LOOP
-- Do something with the cursor.
DBMS_OUTPUT.PUT_LINE( cur.col1 || ', ' || cur.col2 || ', ' || cur.some_date );
END LOOP;
END;
/
db<>fiddle here
Again, for your more complicated query, you do not need PL/SQL (and cannot have DDL statements in a PL/SQL context):
DROP TABLE t1;
CREATE TABLE t1 as
SELECT *
FROM t
WHERE ADD_MONTHS(TRUNC(SYSDATE, 'MM'), -24) <= some_date
AND some_date < TRUNC(SYSDATE, 'MM');
Or, if you want to use PL/SQL then do the DDL statements in the SQL context and then switch to PL/SQL for the DML statements:
DROP TABLE t1;
CREATE TABLE t1 AS
SELECT * FROM t WHERE 1 = 0;
-- or
-- TRUNCATE TABLE t1;
DECLARE
v_calculation_date DATE := SYSDATE;
BEGIN
INSERT INTO t1
SELECT *
FROM t
WHERE ADD_MONTHS(TRUNC(v_calculation_date, 'MM'), -24) <= some_date
AND some_date < TRUNC(v_calculation_date, 'MM');
END;
/

How can I change before update trigger to before insert trigger?

I have a table and this table must have at most 1000 row. When user wants to add 1001st row, there must be an error. I know i should use insert trigger. I'm a new person in SQL can you help me?
My code is:
create or replace trigger update_ext_app_serv
before update on EXTERNAL_APP_SERVICE
for each row
declare
row_count number;
old_service_id number;
new_service_trigger varchar(30);
old_service_trigger varchar(30);
begin
row_count := 0;
new_service_trigger := :NEW.TRIGGER_NAME;
old_service_trigger := :OLD.TRIGGER_NAME;
old_service_id := :OLD.SERVICE_ID;
select count(*) into row_count
from EXTERNAL_APP_PROFILE
where ORIG_ID = old_service_id
or TERM_FAILURE_RESP_ID = old_service_id
or TERM_ID = old_service_id;
if (row_count > 0) and (new_service_trigger not like old_service_trigger)
then
raise_application_error(-20706, 'Unable to update Trigger. The service is referenced at least External Application Profile.');
end if;
end;
The way to do this is with an AFTER trigger, and at the statement rather than row level.
SQL> create or replace trigger t1000_trg
2 after insert on t1000
3 declare
4 n pls_integer;
5 begin
6 select count(*) into n
7 from t1000;
8 if n > 1000 then
9 raise_application_error(-20999, 'No more than 1000 records!');
10 end if;
11 end;
12 /
Trigger created.
SQL>
Here's our test data.
SQL> select count(*) from t1000;
COUNT(*)
----------
999
SQL>
1000th record okay...
SQL> insert into t1000 values (1000);
1 row created.
SQL>
... but 1001st record is rejected:
SQL> insert into t1000 values (1001);
insert into t1000 values (1001)
*
ERROR at line 1:
ORA-20999: No more than 1000 records!
ORA-06512: at "FOX.T1000_TRG", line 7
ORA-04088: error during execution of trigger 'FOX.T1000_TRG'
SQL>
Works for multi-row insert statements too.
SQL> roll
Rollback complete.
SQL> select count(*) from t1000;
COUNT(*)
----------
999
SQL> insert into t1000 select 1000 + level from dual connect by level <= 5;
insert into t1000 select 1000 + level from dual connect by level <= 5
*
ERROR at line 1:
ORA-20999: No more than 1000 records!
ORA-06512: at "FOX.T1000_TRG", line 7
ORA-04088: error during execution of trigger 'FOX.T1000_TRG'
SQL>

Using SELECT within SELECT statement in ORACLE

I have a table name SAMPLETABLE this has the tablenames of the tables I require in column TABLENAMES. Lets say the tablenames are TABLEA, TABLEB and TABLEC.
On query
SELECT TABLENAMES FROM SAMPLETABLE WHERE ROWNUM = 1
I get the output the output of TABLENAMES column with TABLEA value.
My problem is, now I want to use this selected value in a select statement. That is,
SELECT * FROM (SELECT TABLENAMES FROM SAMPLETABLE WHERE ROWNUM = 1)
My idea is that it'd return the contents of TABLEA because when the nested SELECT returns TABLEA, the outer SELECT should capture and display it.
On the contrary, I get the output only of the inner statement, that is,
SELECT TABLENAMES FROM SAMPLETABLE WHERE ROWNUM = 1
and
SELECT * FROM (SELECT TABLENAMES FROM SAMPLETABLE WHERE ROWNUM = 1)
return the same output.
I want the first SELECT statement to fetch the returned value of second SELECT and display the table. They above query doesn't do that, so how do I do it? And what is wrong with my idea?
I am on Oracle 10g, any help appreciated.
As table name is not known at compile time you need to use dynamic SQL(execute immediate, native dynamic SQL, for instance) to be able to select from a table, name of which is stored as a string literal - you cannot accomplish it with static SQL
Here is an example:
-- table which contains names of other tables
-- in the table_name column
SQL> create table Table_Names as
2 select 'employees' as table_name
3 from dual
4 ;
Table created
SQL> set serveroutput on;
-- example of an anonymous PL/SQL block
-- where native dynamic SQL (execute immediate statement)
-- is used to execute a dynamically formed select statement
SQL> declare
2 type T_record is record( -- example of record for fetched data
3 f_name varchar2(123),
4 l_name varchar2(123)
5 );
6
7 l_table_name varchar2(123); -- variable that will contain table name
8 l_select varchar2(201);
9 l_record T_Record; -- record we are going to fetch data into
10 begin
11 select table_name
12 into l_table_name -- querying a name of a table
13 from table_names -- and storing it in the l_table_name variable
14 where rownum = 1;
15
16 l_select := 'select first_name, last_name from ' ||
17 dbms_assert.simple_sql_name(l_table_name) ||
18 ' where rownum = 1'; -- forming a query
19
20 execute immediate l_select -- executing the query
21 into l_record;
22 -- simple output of data just for the sake of demonstration
23 dbms_output.put_line('First_name: ' || l_record.f_name || chr(10) ||
24 'Last name: ' || l_record.l_name);
25 exception
26 when no_data_found
27 then dbms_output.put_line('Nothing is found');
28 end;
29 /
First_name: Steven
Last name: King
PL/SQL procedure successfully completed
As a second option you could use weakly typed cursors - refcursors to execute a dynamically formed select statement:
SQL> variable refcur refcursor;
SQL> declare
2 l_table_name varchar2(123);
3 l_select varchar2(201);
4 begin
5 select table_name
6 into l_table_name
7 from table_names
8 where rownum = 1;
9
10 l_select := 'select first_name, last_name from ' ||
11 dbms_assert.simple_sql_name(l_table_name) ||
12 ' where rownum = 1';
13
14 open :refcur
15 for l_select;
16
17 exception
18 when no_data_found
19 then dbms_output.put_line('Nothing is found');
20 end;
21 /
PL/SQL procedure successfully completed.
SQL> print refcur;
FIRST_NAME LAST_NAME
-------------------- -------------------------
Steven King
SQL> spool off;
Find out more about cursors and cursor variables
You can do this with help of dynamic sql. Since the table name is obtained during run time you have to frame the query dynamically and run it.
Declare
Tab_Name Varchar2(30);
Begin
SELECT TABLENAMES into Tab_Name FROM SAMPLETABLE WHERE ROWNUM = 1;
Execute Immediate 'Select * into (Collection Variable) from ' || Tab_Name;
End
/
I just gave it as example. You declare a variable to get the data out or something else as you need. But when you try to use execute immediate with input parameter read about sql injection and then write your code.

Oracle PL/SQL: syntax error when using a variable within SAMPLE clause

The following PL/SQL block works:
DECLARE
r TABLE1%ROWTYPE;
BEGIN
SELECT * INTO r FROM TABLE1 SAMPLE(1) WHERE ROWNUM = 1;
END;
However, when I try to replace the literal with a variable within the SAMPLE clause, Oracle returns a syntax error:
DECLARE
s NUMBER;
r TABLE1%ROWTYPE;
BEGIN
s := 1;
SELECT * INTO r FROM TABLE1 SAMPLE(s) WHERE ROWNUM = 1;
END;
ORA-06550: line 6, column 39:
PL/SQL: ORA-00933: SQL command not properly ended
What am I doing wrong?
I'm using Oracle 10 and SQL Developer.
(These are simplified examples. What I'm actually trying to do in practice is to optimize the selection of random row, where SAMPLE percentage would be calculated dynamically, based on the current number of rows in the table. So I can't use literal, I need a variable to assign the result of the calculation.)
The SAMPLE synthax requires a numeral. You could use dynamic SQL to build a dynamic query, for example with a ref cursor:
SQL> CREATE TABLE table1 AS
2 SELECT ROWNUM ID, rpad(ROWNUM, 10, 'x') DATA
3 FROM dual CONNECT BY LEVEL <= 1000;
Table created
SQL> DECLARE
2 l_cur SYS_REFCURSOR;
3 l_row table1%ROWTYPE;
4 l_pct NUMBER := 50;
5 BEGIN
6 OPEN l_cur
7 FOR 'SELECT * FROM table1 SAMPLE('||l_pct||') WHERE rownum = 1';
8 LOOP
9 FETCH l_cur INTO l_row;
10 EXIT WHEN l_cur%NOTFOUND;
11 dbms_output.put_line(l_row.id);
12 END LOOP;
13 END;
14 /
3
PL/SQL procedure successfully completed

Resources