Create a procedure to print the result of and SQL query - oracle

I'm using Oracle SQL developer and I want to create a procedure to print the result of this SQL code:
SELECT Title , Name
FROM BOOK, BORROWER, BOOK_LOANS
WHERE Due_date = SYSDATE AND Return_date = NULL;
Here's my code. I keep getting "SQL statement ignored, statement ignored, LOOP index variable 'books' is invalid". Please tell me what I am missing here. I tried moving the SQL statement to a cursor, but it also doesn't work.
CREATE OR REPLACE PROCEDURE overdueToday IS
BEGIN
FOR books IN (SELECT Title , Name
FROM BOOK, BORROWER, BOOK_LOANS
WHERE Due_date = SYSDATE
AND Return_date = NULL)
LOOP
DBMS_OUTPUT.put_line(books.Title || ' -- ' || books.Name);
END LOOP;
END overdueToday;
/

Apart from what you've already been told (cross-join), WHERE condition won't work. SYSDATE is a function that returns both date and time, so there's no chance that it'll return anything. You should use TRUNC function.
Moreover, when dealing with NULL values, they aren't "equal" (=) to anything - you should use IS NULL (or IS NOT NULL, depending on what you do).
The following example is kind of stupid; you didn't provide test case so I'm creating my own tables with absolutely minimal column set, just to be sure that the procedure won't fail.
SQL> create table book (title varchar2(20));
Table created.
SQL> create table borrower (name varchar2(20));
Table created.
SQL> create table book_loans (due_date date, return_date date);
Table created.
SQL>
SQL> insert into book values ('Pinky');
1 row created.
SQL> insert into borrower values ('Littlefoot');
1 row created.
SQL> insert into book_loans values (trunc(sysdate), null);
1 row created.
SQL>
The procedure; I marked places you should pay attention to:
SQL> set serveroutput on;
SQL> create or replace procedure overduetoday
2 is
3 begin
4 for books in ( select title,
5 name
6 from book,
7 borrower,
8 book_loans
9 where due_date = trunc(sysdate) --> trunc!
10 and return_date is null --> is!
11 ) loop
12 dbms_output.put_line(books.title ||' -- '|| books.name);
13 end loop;
14 end overduetoday;
15 /
Procedure created.
SQL>
SQL> exec overduetoday;
Pinky -- Littlefoot
PL/SQL procedure successfully completed.
SQL>
As you can see, it works (or, should I rather say, *doesn't fail". If there were more rows in those tables, the result would be really wrong).
Errors you mentioned can't be raised with code you posted. That's why it is important to post exactly what you do, just as I did. Doing so, there's no doubt in what you have, what you did and how Oracle responded. Anything else is just a matter of speculation.

Oracle 12c and above, you may use DBMS_SQL.RETURN_RESULT. You require proper join conditions and aliases in your query, which we are not aware of.
CREATE OR replace PROCEDURE OverdueToday
IS
rc SYS_REFCURSOR;
BEGIN
OPEN rc FOR
SELECT title, -- b.title ?
name -- br.name ?
FROM book b
join borrower br
ON ( 1 = 1 ) --Add proper join condition here
join book_loans bl
ON ( 1 = 1 ) --Add proper join condition here
WHERE due_date = TRUNC(SYSDATE)
AND return_date IS NULL;
DBMS_SQL.RETURN_RESULT(rc);
END overduetoday;
/

If you're using SQL Developer, you can just run your query (hit F5 or Ctrl+Enter). You don't need to write a PL/SQL program.
That being said, your query is almost certainly wrong since you have three tables and no join conditions for them. But you should still get some output.

This is the simple way to loop in select statement:
CREATE OR REPLACE PROCEDURE overdueToday IS
CURSOR book_cur is
SELECT Title , Name
FROM BOOK, BORROWER, BOOK_LOANS
WHERE Due_date = SYSDATE
AND Return_date = NULL;
BEGIN
FOR book_rec IN book_cur loop
DBMS_OUTPUT.put_line(book_rec .Title || ' -- ' || book_rec .Name);
END LOOP;
END overdueToday;

Related

Procedure to Create Backup Table For multiple table each having different Where condition

Create or replace procedure PROC AS
V_TABLE_NAME VARCHAR2(255);
V_LIST SYS_REFCURSOR;
DATE_VALUE_INS VARCHAR2(10);
BEGIN
DATE_VALUE_INS:=TO_CHAR(SYSDATE,'YYMMDD');
OPEN V_LIST FOR
SELECT NAME FROM DW.table_name_list ;
LOOP
FETCH V_LIST
INTO V_TABLE_NAME;
EXIT WHEN V_LIST%NOTFOUND;
EXECUTE IMMEDIATE 'CREATE TABLE Schema.'||V_TABLE_NAME||'_'||DATE_VALUE_INS||' AS SELECT * FROM DW.'||V_TABLE_NAME;
END LOOP;
CLOSE V_LIST;
end;
I have created this Proc which takes value from a table which has Table_name and create Backup using Execute Immediate.
Now the requirement has changed that i only need to create backup for partial records (i.e. where clause on each table )
I have 6 tables as such .
New Approach i am thinking is :
EXECUTE IMMEDIATE 'CREATE TABLE Schema.'||V_TABLE_NAME||'_'||DATE_VALUE_INS||' AS SELECT * FROM DW.'||V_TABLE_NAME where some condition;
But the problem becomes all 6 have different column to filter on.
My Ask is How should I change my design of proc to Adjust this new Requirement.
6 tables? Why bother? Create a procedure which - depending on table name passed as a parameter - in IF-THEN-ELSE runs 6 different CREATE TABLE statements.
On the other hand, another approach would be to create backup tables in advance (at SQL level), add BACKUP_DATE column to each of them, and - in procedure - just perform INSERT operation which doesn't require dynamic SQL at all.
For example:
create table emp_backup as select * from emp where 1 = 2;
alter table emp_backup add backup_date date;
create or replace procedure p_backup (par_table_name in varchar2) is
begin
if par_table_name = 'EMP' then
insert into emp_backup (empno, ename, job, sal, backup_date)
select empno, ename, job, sal, trunc(sysdate)
from emp
where deptno = 20; --> here's your WHERE condition
elsif par_table_name = 'DEPT' then
insert into dept_backup (...)
select ..., trunc(sysdate)
from dept
where loc = 'DALLAS';
elsif ...
...
end if;
end;
/
Doing so, you'd easier access backup data as you'd query only one table, filtered by BACKUP_DATE. That's also good if you have to search for some data that changed several days ago, but you don't know exact day. What would you rather do: query 10 tables (and still not find what you're looking for), or query just one table and find that info immediately?

Insert into not working on plsql in oracle

declare
vquery long;
cursor c1 is
select * from temp_name;
begin
for i in c1
loop
vquery :='INSERT INTO ot.temp_new(id)
select '''||i.id||''' from ot.customers';
dbms_output.put_line(i.id);
end loop;
end;
/
Output of select * from temp_name is :
ID
--------------------------------------------------------------------------------
customer_id
1 row selected.
I have customers table which has customer_id column.I want to insert all the customer_id into temp_new table but it is not being inserted. The PLSQL block executes successfully but the temp_new table is empty.
The output of dbms_output.put_line(i.id); is
customer_id
What is wrong there?
The main problem is that you generate a dynamic statement that you never execute; at some point you need to do:
execute immediate vquery;
But there are other problems. If you output the generated vquery string you'll see it contains:
INSERT INTO ot.temp_new(id)
select 'customer_id' from ot.customers
which means that for every row in customers you'll get one row in temp_new with ID set to the same fixed literal 'customer_id'. It's unlikely that's what you want; if customer_id is a column name from customers then it shouldn't be in single quotes.
As #mathguy suggested, long is not a sensible data type to use; you could use a CLOB but only really need a varchar2 here. So something more like this, where I've also switched to use an implicit cursor:
declare
l_stmt varchar2(4000);
begin
for i in (select id from temp_name)
loop
l_stmt := 'INSERT INTO temp_new(id) select '||i.id||' from customers';
dbms_output.put_line(i.id);
dbms_output.put_line(l_stmt);
execute immediate l_stmt;
end loop;
end;
/
db<>fiddle
The loop doesn't really make sense though; if your temp_name table had multiple rows with different column names, you'd try to insert the corresponding values from those columns in the customers table into multiple rows in temp_new, all in the same id column, as shown in this db<>fiddle.
I guess this is the starting point for something more complicated, but still seems a little odd.

how to execute delete statement inside plsql block and call it in a procedure

I have written below pl sql block and trying to create a procedue. But i am getting warnings and not able to execute the procedue.
Please suggest if something i am missing \
Please let me know if this question is duplicate as i am not able to get the exact link to refer
create or replace PROCEDURE EmployeeProc
IS
BEGIN
delete from Employeetable where EmplId in (
select EmployeeId FROM EmployeeMstrTbl where JoiningDate between to_date('2019-01-01','YYYY-MM-DD') and to_date('2019-02-28','YYYY-MM-DD'));
commit;
DBMS_OUTPUT.PUT_LINE('Deleted '||SQL%ROWCOUNT ||' records from Employeetable');
END;
Error: Object Invalid
Try using cursor
CREATE OR REPLACE PROCEDURE EMPLOYEEPROC IS
CURSOR C1 IS
SELECT EMPLOYEEID
FROM EMPLOYEEMSTRTBL
WHERE JOININGDATE BETWEEN TO_DATE('2019-01-01','YYYY-MM-DD') AND TO_DATE('2019-02-28','YYYY-MM-DD'));
BEGIN
FOR I IN C1 LOOP
DELETE FROM EMPLOYEETABLE
WHERE EMPLID=I.EMPLOYEEID;
END LOOP;
COMMIT;
DBMS_OUTPUT.PUT_LINE('DELETED '||SQL%ROWCOUNT ||' RECORDS FROM EMPLOYEETABLE');
END;
Your code works just fine.
CREATE TABLE Employeetable
(
EmplId NUMBER
);
CREATE TABLE EmployeeMstrTbl
(
EmployeeId NUMBER,
JoiningDate DATE
);
CREATE OR REPLACE PROCEDURE EmployeeProc
IS
BEGIN
DELETE FROM Employeetable
WHERE EmplId IN
(SELECT EmployeeId
FROM EmployeeMstrTbl
WHERE JoiningDate BETWEEN TO_DATE ('2019-01-01',
'YYYY-MM-DD')
AND TO_DATE ('2019-02-28',
'YYYY-MM-DD'));
COMMIT;
DBMS_OUTPUT.PUT_LINE (
'Deleted ' || SQL%ROWCOUNT || ' records from Employeetable');
END;
EXEC EmployeeProc;
DROP TABLE Employeetable;
DROP TABLE EmployeeMstrTbl;
DROP PROCEDURE EmployeeProc;
Script output:
Table created.
Table created.
Procedure created.
PL/SQL procedure successfully completed.
Table dropped.
Table dropped.
Procedure dropped.
DBMS Output:
Deleted 0 records from Employeetable
Maybe you have a typo in a table name, column name or something similar.
I suggest that you try to execute your delete statement first to check if it works.
Not sure if perhaps you mistyped something, or if it is to do with how you have it set up. But even if it is a mistype and it could work, it's not nice, so do a loop.
i = 0;
FOR r in (select * FROM EmployeeMstrTbl where JoiningDate between to_date('2019-01-01','YYYY-MM-DD') and to_date('2019-02-28','YYYY-MM-DD'))
LOOP
DELETE FROM Employeetable where EmplId = r.EmployeeId;
END LOOP;
DBMS_OUTPUT.PUT_LINE('Deleted '|| i ||' records from Employeetable');
Because this will work, and more importantly, its easier to understand. Keeping code short and abbreviated has become much less important nowadays since the size of the code is almost never the problem, but keeping it easy to understand is extremely important so that it can be maintained in the future.

use the same auto increment trigger in oracle for many tables

I created a database in MySQL with ~10 tables, each starting with the column
SN INT NOT NULL AUTO_INCREMENT
SN doesn't mean anything, just the primary to differentiate between possibly repeating/similar names/titles, etc
I'm moving it to Oracle now, and found this post here on stackoverflow to make the trigger to auto increment the SN field. Basically,
CREATE SEQUENCE user_seq;
CREATE OR REPLACE TRIGGER user_inc
BEFORE INSERT ON users
FOR EACH ROW
BEGIN
SELECT user_seq.NEXTVAL
INTO :new.SN
FROM dual;
END;
/
Now, how can I rewrite that trigger once to apply to all the other tables? Because otherwise I have to rewrite it for many tables, just changing the trigger name and sequence name... I was picturing something like:
BEFORE INSERT ON users OR other_table OR another_one
I also found this post here, but the one answer there isn't helpful because I think it's reasonable for many tables to have the same SN field, or I'm misunderstanding the point.
Also, not Oracle 12c so no identity columns
Thanks in advance
I was going to just comment on the first post I mentioned but I can't comment without more reputation points :/
Creating a trigger referencing many tables in Oracle is not possible,
What you can do is to generate the triggers with a PL/SQL statement.
Below is an example on how could you achieve this
drop table tab_a;
drop table tab_b;
drop table tab_c;
drop sequence seq_tab_a_id;
drop sequence seq_tab_b_id;
drop sequence seq_tab_c_id;
--create test tables
create table tab_a (SN number, otherfield varchar2(30), date_field date);
create table tab_b (SN number, otherfield varchar2(30), date_field date);
create table tab_c (SN number, otherfield varchar2(30), date_field date);
-- this pl/sql block creates the sequences and the triggers
declare
my_seq_create_stmt varchar2(2000);
my_trigger_create_stmt varchar2(2000);
begin
for i in (select table_name
from user_tables
-- remember to change this where condition to filter
-- the tables that are relevant for you
where table_name in ('TAB_A', 'TAB_B', 'TAB_C') )loop <<TableLoop>>
my_seq_create_stmt := 'CREATE SEQUENCE '||'SEQ_'||i.table_name||'_ID '
||CHR(13)||' START WITH 1 INCREMENT BY 1 NOCYCLE ';
execute immediate my_seq_create_stmt;
my_trigger_create_stmt := 'CREATE OR REPLACE TRIGGER '||'TRG_'||i.Table_name||'_ID_BI '||' BEFORE INSERT ON '||i.table_name||' FOR EACH ROW '
||CHR(13)||'BEGIN '
||CHR(13)||' SELECT '||'SEQ_'||i.table_name||'_ID'||'.NEXTVAL '
||CHR(13)||' INTO :new.SN '
||CHR(13)||' FROM dual; '
||CHR(13)||'END; ';
execute immediate my_trigger_create_stmt;
end loop TableLoop;
end;
/
-- test the triggers and the sequences
insert into tab_a (otherfield, date_field) values ('test 1',sysdate);
insert into tab_a (otherfield, date_field) values ('test 2',sysdate);
commit;
Select * from tab_a;

FORALL+ EXECUTE IMMEDIATE + INSERT Into tbl SELECT

I have got stuck in below and getting syntax error - Please help.
Basically I am using a collection to store few department ids and then would like to use these department ids as a filter condition while inserting data into emp table in FORALL statement.
Below is sample code:
while compiling this code i am getting error, my requirement is to use INSERT INTO table select * from table and cannot avoid it so please suggest.
create or replace Procedure abc(dblink VARCHAR2)
CURSOR dept_id is select dept_ids from dept;
TYPE nt_dept_detail IS TABLE OF VARCHAR2(25);
l_dept_array nt_dept_detail;
Begin
OPEN dept_id;
FETCH dept_id BULK COLLECT INTO l_dept_array;
IF l_dept_array.COUNT() > 0 THEN
FORALL i IN 1..l_dept_array.COUNT SAVE EXCEPTIONS
EXECUTE IMMEDIATE 'INSERT INTO stg_emp SELECT
Dept,''DEPT_10'' FROM dept_emp'||dblink||' WHERE
dept_id = '||l_dept_array(i)||'';
COMMIT;
END IF;
CLOSE dept_id;
end abc;
Why are you bothering to use cursors, arrays etc in the first place? Why can't you just do a simple insert as select?
Problems with your procedure as listed above:
You don't declare procedures like Procedure abc () - for a standalone procedure, you would do create or replace procedure abc as, or in a package: procedure abc is
You reference a variable called "dblink" that isn't declared anywhere.
You didn't put end abc; at the end of your procedure (I hope that was just a mis-c&p?)
You're effectively doing a simple insert as select, but you're way over-complicating it, plus you're making your code less performant.
You've not listed the column names that you're trying to insert into; if stg_emp has more than two columns or ends up having columns added, your code is going to fail.
Assuming your dblink name isn't known until runtime, then here's something that would do what you're after:
create Procedure abc (dblink in varchar2)
is
begin
execute immediate 'insert into stg_emp select dept, ''DEPT_10'' from dept_emp#'||dblink||
' where dept_id in (select dept_ids from dept)';
commit;
end abc;
/
If, however, you do know the dblink name, then you'd just get rid of the execute immediate and do:
create Procedure abc (dblink in varchar2)
is
begin
insert into stg_emp -- best to list the column names you're inserting into here
select dept, 'DEPT_10'
from dept_emp#dblink
where dept_id in (select dept_ids from dept);
commit;
end abc;
/
There appears te be a lot wrong with this code.
1) why the execute immediate? Is there any explicit requirement for that? No, than don't use it
2) where is the dblink variable declared?
3) as Boneist already stated, why not a simple subselect in the insert statement?
INSERT INTO stg_emp SELECT
Dept,'DEPT_10' FROM dept_emp#dblink WHERE
dept_id in (select dept_ids from dept );
For one, it would make the code actually readable ;)

Resources