PL/SQL on Oracle 11g code is when executing stored procedure - sql

What I am trying to do here is not run an insert code over and over, which is why I decided to create a stored procedure. Below is the script for the stored procedure and it created successfully, but when I execute the stored procedure "BEGIN SP_INSERT_PMC_UPDATE_DP; END;" I receive an error message "*wrong number or types of arguments in call to SP_INSERT_PMC_UPDATE_DP*"
My frame of thinking and sp code on this is:
1- I check to see if there are records in the PMC_UPDATE_DP and place that value into RECORD_COUNT.
2- Now if RECORD_COUNT is greater than zero I want to clear out all the records from the PMC_UPDATE_DP table.
3- If the RECORD_COUNT is equal to zero, then I want to insert data from the EXTERNAL_PMC_UPDATE_FD table to PMC_UPDATE_DP.
CREATE OR REPLACE PROCEDURE PMC.SP_INSERT_PMC_UPDATE_DP
(RECORD_COUNT OUT NUMBER) --Is my issue here???
IS
BEGIN
--Returns the total records and adds the value to RECORD_COUNT variable.
SELECT COUNT(*)
INTO RECORD_COUNT
FROM PMC.PMC_UPDATE_DP;
--Condition to see if RECORD_COUNT is greater than zero and dumps data to clear out PMC.PMC_UPDATE_DP table.
IF RECORD_COUNT > 0 THEN
EXECUTE IMMEDIATE 'TRUNCATE TABLE PMC.PMC_UPDATE_DP';
END IF;
--Condition to see if RECORD_COUNT equals zero. If true insert data into PMC_UPDATE_DP table from the PMC.EXTERNAL_PMC_UPDATE_FD table.
IF RECORD_COUNT = 0 THEN
INSERT INTO PMC.PMC_UPDATE_DP
( JOB_ID,
CONTROL_ID,
ACCT_NO,
CALC_DIVIDEND,
CERT_NEW_SHARES,
CALC_CASH_DISBURSMENT,
DECEASED,
STATUS,
ALPHA_SP1,
ALPHA_SP2,
ALPHA_SP3,
ALPHA_SP4,
NUM_SP1,
NUM_SP2,
NUM_SP3,
NUM_SP4,
DONT_CALL,
DONT_MAIL
)
SELECT JOB_ID,
CONTROL_ID,
ACCT_NO,
CALC_DIVIDEND,
CERT_NEW_SHARES,
CALC_CASH_DISBURSMENT,
DECEASED,
STATUS,
ALPHA_SP1,
ALPHA_SP2,
ALPHA_SP3,
ALPHA_SP4,
NUM_SP1,
NUM_SP2,
NUM_SP3,
NUM_SP4,
DONT_CALL,
DONT_MAIL
FROM PMC.EXTERNAL_PMC_UPDATE_FD
COMMIT;
END IF;
END;

If you want to call a procedure with an OUT parameter, you'd need to pass in a local variable so Oracle has something to hold the output.
DECLARE
l_record_count pls_integer;
BEGIN
PMC.SP_INSERT_PMC_UPDATE_DP ( l_record_count );
-- Do something with l_record_count, probably not just calling dbms_output
-- dbms_output.put_line( 'Record count = ' || l_record_count );
END;
If you are not planning on doing anything with the ouput, perhaps you really don't want the procedure to have an OUT parameter. Perhaps you just want to declare record_count as a local variable.

Related

Oracle save updated rows in log

I have an update in my stored procedure. I would like to, save in my log the number of rows updated. How can I achieve this?
You can do something like this:
declare
l_rows_updated number;
begin
update table1 set col1 = 'abc' where foo = 'bar';
l_rows_updated := sql%rowcount;
insert into audit_table ( num_rows) values ( l_rows_updated );
end;
You can use the sql%rowcount which will return the number of rows Inserted or Updated by the last DML.
Depending upon how you are calling the procedure, the procedure can return the value as and OUT parameter and your calling program (could be Java, Python ) can write into a log file in the server.

How do I search for records in a table using a procedure in PL/SQL?

I'm trying to...
Ask user for input and store the input to a variable
Pass this variable into a procedure
Procedure then should iterate through every row in one column of a certain table
If the variable matches then all columns for that row will be printed out via. DBMS_OUTPUT.PUT_LINE('');
Procedure continues to iterate through all rows until finished
I've tried reading my course-material, but the procedures being created are for different uses (e.g. updating a column in a row when a 'where' condition is met) and I'm struggling to understand how to use a procedure to do what I'm trying to do.
The table 'Vehicles' has rows 'v_regno'(PK), 'v_make', 'v_model', 'v_year' etc.
Here's the code.
/*Procedure to search for car by make*/
CREATE OR REPLACE PROCEDURE SaleByMake(
search_make IN VARCHAR2(15)
)
IS
BEGIN
FOR /*each row in 'Items' table*/
IF i_make = search_make THEN
/*Print out columns of this row*/
END IF;
END LOOP;
EXCEPTION
WHEN OTHERS THEN /*What goes in here?*/
END;
-- Get Input from User
ACCEPT search_make CHAR(15) PROMPT 'Enter car make: ';
-- Call the SaleByMake() Procedure and check stock amount status of item
EXECUTE SaleByMake(&search_make);
is that what you Need?
/*Procedure to search for car by make*/
CREATE OR REPLACE PROCEDURE SaleByMake(
search_make IN VARCHAR2
)
IS
BEGIN
FOR rec in (select col1,col2,col3 from table where col = search_make )
Loop
DBMS_OUTPUT.PUT_LINE(rec.col1||' ' || rec.col2||' ' ||rec.col3);
END LOOP;
EXCEPTION
WHEN OTHERS THEN /*What goes in here?*/
END;
you should replace the select the table Name a column names with names you need

How to write a procedure to display the contents of a table in sql

I have a created a procedure as
create or replace procedure availability(num in number) as
begin
delete from vehicle_count;
insert into vehicle_count from select engine_no,count(engine_no)
from vehicle
where engine_no = num
group by engine_no;
end;
/
The procedure was created successfully but now i have to write a separate query to view the contents of vehicle_count as
select * from vehicle_count;
I tried inserting the select statement into the procedure after insertion but it showed a error stating "an INTO clause is expected in the select statement".
How can I create procedure to select the required contents and display it in a single execute statement?
Table schema
vehicle(vehicle_no,engine_no,offence_count,license_status,owner_id);
vehicle_count(engine_no,engine_count);
Check this (MS SQL SERVER)-
create or alter procedure availability(#num as int) as
begin
delete from vehicle_count;
insert into vehicle_count
output inserted.engine_no,inserted.count_engine_no
select engine_no,count(engine_no) as count_engine_no
from vehicle
where engine_no=#num
group by engine_no;
end;
If you want to use a SELECT into a PL/SQL block you should use either a SELECT INTO or a loop (if you want to print more rows).
You could use something like this:
BEGIN
SELECT engine_no, engine_count
INTO v_engine, v_count
FROM vehicle_count
WHERE engine_no = num;
EXCEPTION
WHEN NO_DATA_FOUND THEN
v_engine := NULL;
v_count := NULL;
END;
v_engine and v_count are two variables. You can declare them in your procedure, and they will contain the values you want to print.
You said that the procedure you wrote (actually, you posted here) compiled successfully. Well, sorry to inform you - that's not true. This is not a valid syntax:
insert into vehicle_count from select engine_no,count(engine_no)
----
from? Here?
Consider posting true information.
As of your question (if we suppose that that INSERT actually inserted something into a table):
at the beginning, you delete everything from the table
as SELECT counts number of rows that share the same ENGINE_NO (which is equal to the parameter NUM value), INSERT inserts none (if there's no such NUM value in the table) or maximum 1 row (because of aggregation)
therefore, if you want to display what's in the table, all you need is a single SELECT ... INTO statement whose result is displayed with a simple DBMS_OUTPUT.PUT_LINE which will be OK if you're doing it interactively (in SQL*Plus, SQL Developer, TOAD and smilar tools). Regarding table description, I'd say that ENGINE_NO should be a primary key (i.e. that not more than a single row with that ENGINE_NO value can exist in a table).
create or replace procedure availability (num in number) as
l_engine_no vehicle_count.engine_no%type;
l_engine_count vehicle_count.engine_count%type;
begin
delete from vehicle_count;
insert into vehicle_count (engine_no, engine_count)
select engine_no, count(engine_no)
from vehicle
where engine_no = num
group by engine_no;
-- This query shouldn't return TOO-MANY-ROWS if ENGINE_NO is a primary key.
-- However, it might return NO-DATA-FOUND if there's no such NUM there, so you should handle it
select engine_no, engine_count
into l_engine_no, l_engine_count
from vehicle_count
where engine_no = num;
dbms_output.put_line(l_engine_no ||': '|| l_engine_count);
exception
when no_data_found then
dbms_output.put_line('Nothing found for ENGINE_NO = ' || num);
end;
/
There are numerous alternatives to that (people who posted their answers/comments before this one mentioned some of those), and the final result you'd be satisfied with depends on where you want to display that information.

Limit data input based on count of instances in table

Oracle 12c.
I currently have a table to hold patient visits containing a physician id, patient id, and data/time of visit.
I would like to create a constraint that, upon data entry, checks whether a specific physician has 5 appointments in that given day. If the physician does have 5 appointments, no additional appointment can be added.
Is there any way to do this other than using a stored procedure for entry?
If I were to use a stored procedure (as opposed to a trigger due issues declaring a variable) I receive the following error: Error(4,8): PLS-00103: Encountered the symbol "UPDATE" when expecting one of the following: := . ( # % ; not null range default character
I am unsure if this is because I can't use a BEFORE UPDATE on a procedure. Any thoughts?
CREATE OR REPLACE PROCEDURE doc_apt_limit_5
IS
v_visit_count
BEFORE UPDATE OR INSERT ON aa_patient_visit
FOR EACH ROW
BEGIN
SELECT (COUNT(*)) INTO v_visit_count
FROM aa_patient_visit
WHERE physid = :NEW.physid
GROUP BY physid, visittime;
IF v_visit_count > 4 THEN
RAISE_APPLICATION_ERROR(-20001, 'physician is fully booked on this date');
END IF;
END;
Go with trigger. Probably the best solution in this scenario.
CREATE OR REPLACE TRIGGER doc_apt_limit_5 BEFORE
UPDATE OR
INSERT ON aa_patient_visit FOR EACH ROW
DECLARE v_visit_count PLS_INTEGER;
BEGIN
SELECT COUNT(*)
INTO v_visit_count
FROM aa_patient_visit
WHERE physid = :NEW.physid
GROUP BY physid,
visittime;
IF v_visit_count > 4 THEN
RAISE_APPLICATION_ERROR(-20001, 'physician is fully booked on this date');
END IF;
END;

Using function inside a cursor (by using a variable)

I would like to confirm the correct use of the following:
1) Use a global variable to get return values from a function only once
(since my function will be returning some Sequence values)
2) Use that variable inside a cursor multiple times
3) All of these will be inside a procedure
Below shows a sample.
CREATE OR REPLACE Procedure insert_myTable is
--declare variables for insert
v_firstNO VARCHAR2(10);
v_secondNO VARCHAR2(6);
--declare variable to store the sequence number
var_ASeqno varchar2(6);
-- Validation
v_check VARCHAR2 (10 Byte);
v_table_name varchar2(50):='myTable';
cursor c1 is
select distinct firstNO,
secondNO
from (SELECT hdr.someNum firstNO,
-- using variable to assign the sequence no
var_ASeqno secondNO
FROM someOtherTable hdr
WHERE -- some condition
union
SELECT hdr.someNum firstNO,
-- using variable to assign the sequence no
var_ASeqno secondNO
FROM someOtherTable hdr
WHERE -- some other conditions
union
SELECT hdr.someNum firstNO,
-- using variable to assign the sequence no
var_ASeqno secondNO
FROM someOtherTable hdr
WHERE -- some other other conditions
begin
if c1%isopen then
close c1;
end if;
v_check:=null;
FOR i IN c1 LOOP
--assign variables for insert
v_firstNO := i.firstNO ;
v_secondNO := i.secondNO ;
begin
-- calling the Function aSeqNoFunc and assign the
--Sequence Number into the variable var_ASeqno
var_ASeqno := aSeqNoFunc();
select firstNO
into v_check
from myTable a
where firstNO = i.firstNO
and secondNO =i.secondNO;
exception
when no_data_found then
--insert into target table
INSERT INTO myTable (firstNO, secondNO)
values (v_firstNO, v_secondNO);
end ;
end loop;
end;
As can be seen, the function 'aSeqNoFunc' is called before the Insert near the end. The values are assigned to the variable 'var_ApmSeqno' which in turn is used three times inside the cursor.
Thank you.
A few suggestions:
You have an END; statement after the declaration of cursor c1 which doesn't match up with anything. You should remove this from your code.
There's no need to check to see if the cursor is open when you enter the procedure. It won't be. Even better, don't use an explicit cursor declaration - use a cursor FOR-loop.
Use UNION ALL instead of UNION unless you know what the difference between the two is. (And go read up on that. 99.9% of the time you want UNION ALL...).
However, as it appears that all the rows are being selected from the same table you may be able to eliminate the UNION's altogether, as shown below.
There's no benefit to assigning NULL to a variable at the beginning of a function. Variables are initialized to NULL if there's no other explicit initialization value given.
IMO there's no benefit to having a function which returns the next value from a sequence. It just makes understanding the code more difficult. Get rid of FUNCTION aSeqNoFunc and just invoke SOME_SEQUENCE.NEXTVAL where appropriate - so in the above I suggest you use var_ASeqno := SOME_SEQUENCE.NEXTVAL.
You need to assign the value to var_ASeqno before cursor c1 is opened. As written abovevar_ASeqno will be null at the time the cursor is opened, so the cursor will probably not return what you expect. But more to the point I don't see that there's any reason to have the cursor return the value of var_ASeqno. Just use the value of var_ASeqno in your INSERT statements or wherever else they're needed.
Use a MERGE statement to insert data if it doesn't already exist. This avoids the awkward "SELECT...catch the NO_DATA_FOUND exception...INSERT in the exception handler" logic.
And as #boneist points out in her comment, by the time we've gone this far there's really no point to the cursor. You might as well just use the MERGE statement to perform the INSERTs without using a cursor.
So I'd try rewriting this procedure as:
CREATE OR REPLACE Procedure insert_myTable is
begin
MERGE INTO MYTABLE m
USING (SELECT FIRSTNO,
SOME_SEQUENCE.NEXTVAL AS SECONDNO
FROM (SELECT DISTINCT hdr.someNum AS FIRSTNO
FROM someOtherTable hdr
WHERE (/* some condition */)
OR (/* some other conditions */)
OR (/* some other other conditions */))) d
ON d.FIRSTNO = m.FIRSTNO AND
d.SECONDNO = m.SECONDNO
WHEN NOT MATCHED THEN INSERT (FIRSTNO, SECONDNO)
VALUES (d.FIRSTNO, d.SECONDNO);
end INSERT_MYTABLE;
Taking into account the fact that you want all the rows being inserted to have the same sequence number assigned, I think you could probably rewrite your procedure to something like:
create or replace procedure insert_mytable
is
v_seq_no number;
begin
v_seq_no := somesequence.nextval;
merge into mytable tgt
using (select firstno,
v_seq_no secondno
from (select hdr.somenum firstno
from someothertable1 hdr
where -- some condition
union
select hdr.somenum firstno
from someothertable2 hdr
where -- some other conditions
union
select hdr.somenum firstno
from someothertable3 hdr
where -- some other other conditions
)
) src
on (tgt.firstno = src.firstno and tgt.secondno = src.secondno)
when not matched then
insert (tgt.firstno, tgt.secondno)
values (src.firstno, src.secondno);
end insert_mytable;
/
If this doesn't match with what you're trying to do, please edit your question to provide more information on what the aim of the procedure is. Example input and output data would be appreciated, so that we have a better idea of what you're wanting (since we can't see your table structures, data, etc).
ETA: Info on performance considerations between set-based and row-by-row approaches.
Here's a simple script to insert of a million rows, both row-by-row and as a single insert statement:
create table test (col1 number,
col2 number);
set timing on;
-- row-by-row (aka slow-by-slow) approach
begin
for rec in (select level col1, level * 10 col2
from dual
connect by level <= 1000000)
loop
insert into test (col1, col2)
values (rec.col1, rec.col2);
end loop;
end;
/
commit;
truncate table test;
-- set based approach (keeping in an anonymous block for comparison purposes)
begin
insert into test (col1, col2)
select level, level*10
from dual
connect by level <= 1000000;
end;
/
commit;
drop table test;
Here's the output I get, when I run the above in Toad:
Table created.
PL/SQL procedure successfully completed.
Elapsed: 00:00:21.87
Commit complete.
Elapsed: 00:00:01.03
Table truncated.
Elapsed: 00:00:00.22
PL/SQL procedure successfully completed.
Elapsed: 00:00:01.96
Commit complete.
Elapsed: 00:00:00.03
Table dropped.
Elapsed: 00:00:00.18
Do you see the elapsed time of 21 seconds for the row-by-row approach, and 2 seconds for the set-based approach? Massive difference in performance, don't you agree? If that's not reason to consider writing your code as set based in the first instance, then I don't know what else will convince you/your boss!