Oracle Stored Procedure 'Learnings' issue - sql

I have been tasked (as part of an assignment) to write a stored procedure in Oracle PL/SQL. There are 3 requirements that have to be met.
There must be 2 parameters, 1 IN and 1 OUT.
I must use an implicit cursor and SQL function to calculate a count of the numbers of fields of the same type (in this case the type is car models, so how many cars of each model are there).
I must use another implicit cursor to display the description of the models.
To be honest, I am at a loss. So far for the stored proc I have:
CREATE OR REPLACE Procedure model_details_sp
(p_model IN VARCHAR2,
p_noofcars OUT NUMBER)
IS
BEGIN
SELECT COUNT(Model_Name) INTO p_noofcars
FROM i_car
GROUP BY Model_Name;
END;
I really have no idea where to go from here. Any advice or direction would be most appreciated.
Many thanks.
Hi guys I appreciate all the comments. I wasn't very clear with the end requirements. I want to be able to call this procedure via an anonymous block so that the user will enter a model type (&vairalbe) and the procedure will display how many of that model types are in the database.

When dealing with this type of problems, first think about the data you're trying to capture.
Dealing with implicit cursors in PL/SQL require 1-row, so you need to make sure you understand the data.
In this case, you pass in a variable that you don't use in any of your queries, so I suggest you re-evaluate.
I don't have a database at hand to run this, but you should be able to work this out and hopefully get you a bit closer. I put it in an anonymous block so that I can write it really quick.
DECLARE
PROCEDURE model_details_sp (p_model IN VARCHAR2, p_noofcars OUT NUMBER)
IS
p_description VARCHAR2 (200);
BEGIN
--2
SELECT COUNT (model_name)
INTO p_noofcars
FROM i_car
WHERE model_name = p_model;
DBMS_OUTPUT.put_line ('No of Cars for model: ' || p_noofcars);
--3
SELECT model_description
INTO p_description
FROM i_car --the table should be the car_model table so that only one record is returned
WHERE model_name = p_model;
DBMS_OUTPUT.put_line ('Model Desc' || p_description);
END model_details_sp;
BEGIN
dbms_output.put_line('');
END;
To #David Aldridge comment:
Try running this--the result should be a failure--as you cannont select multiple rows using the into CLAUS, unless you aggregate the data:
DECLARE
p_num NUMBER;
BEGIN
SELECT LEVEL INTO p_num FROM DUAL CONNECT BY LEVEL <= 10;
dbms_output.put(p_num);
END;
The error you should see is this:
Error report:
ORA-01422: exact fetch returns more than requested number of rows
ORA-06512: at line 4
01422. 00000 - "exact fetch returns more than requested number of rows"
*Cause: The number specified in exact fetch is less than the rows returned.
*Action: Rewrite the query or change number of rows requested

From the description and subsequent comments, this is the solution I would provide:
DECLARE
PROCEDURE model_details_sp
(p_model IN VARCHAR2,
p_noofcars OUT NUMBER)
IS
BEGIN
SELECT COUNT(*)
INTO p_noofcars
FROM i_car
WHERE model_name = p_model;
END;
no_of_cars NUMBER := 0;
BEGIN
model_details_sp(:model_name, no_of_cars);
dbms_output.put_line('no of cars for ' || :model_name || ' = ' || no_of_cars);
END;
I've created the PROCEDURE in-line but you can just as easily extract this to the database by removing it from the declare section and executing it with CREATE OR REPLACE.
This example assumes use of an IDE that supports bind variable replacement (:model_name) on execute of the anonymous block. In TOAD, for example, the "user" will be prompted to provide a value for :model_name.

Related

Oracle stored procedure not working PLS-00306

i have this questions i am trying to solve and find below what i have solved so far. although the stored procedure haveno error but calling it i get this error :
ERROR at line 2: ORA-06550: line 2, column 3: PLS-00306: wrong
number or types of arguments in call to 'PUB_JOB_COUNT' ORA-06550:
line 2, column 3: PL/SQL: Statement ignored
Requirement:
Create a stored PL/SQL procedure object in the database. The procedure
should insert the publisher’s name, city, telephone number and the
number (count) of jobs he/she requested in the table PublisherDetails
for each Publisher who requested less than three print jobs otherwise
the procedure should display on the screen the publisher name followed
by job number, job start date and job completion date for each job
he/she requested. Screen output (hint: use the concatenation operator
‘||’) should be in the following format:
Please someone help me out please?
Publisher Name: Addison-Wesley
JobNo Start Date Completion Date
12 17-JAN-14 25-JAN-14
14 28-FEB-14 01-APR-14
Finally, a NO-DATA-FOUND exception should be catered for in the
EXCEPTION section and a message displayed on the screen (hint: use
DBMS_OUTPUT.put_line procedure provided by Oracle) informing the user
if such an error arises. Note that in order for DBMS_OUTPUT.put_line
to work in SQL*Plus, you should set SERVEROUTPUT on first. You should
check if the procedure executes properly by invoking the procedure and
checking the content of the PublisherDetails table. Do the following:
a) Create a script file with the necessary code to create the table
PublisherDetails and the PL/SQL procedure in the database; b) Create a
second script file with the following: • An SQL statement that clears
the content of the table PublisherDetails; • A PL/SQL anonymous block
statement to invoke (execute) the PL/SQL procedure; • A SELECT
statement to select all the records in PublisherDetails table.
my tables
publisher(publisherName, publisherCity, phoneNo)
pk
printJob(JobNo, startDate, complitionDate, publisherName)
pk fk(publisher)
publisherdetails(publisherName, publisherCity, phoneNo, JobNo)
pk
Code:
CREATE OR REPLACE PROCEDURE PUB_JOB_COUNT (
JOBNO IN NUMBER
) AS
PUBLISHERNAME PRINTJOB.PUBLISHERNAME%TYPE;
NOTFOUND EXCEPTION;
CURSOR PUBCURSOR IS
SELECT PUBLISHER.PUBLISHERNAME,
PUBLISHER.PUBLISHERCITY,
PUBLISHER.PHONENO,
PRINTJOB.STARTDATE,
PRINTJOB.COMPLETIONDATE,
SUM(JOBNO) AS NUMOFJOBS
FROM PUBLISHER
INNER JOIN PRINTJOB ON PUBLISHER.PUBLISHERNAME = PRINTJOB.PUBLISHERNAME
GROUP BY PUBLISHER.PUBLISHERNAME,
PUBLISHER.PUBLISHERCITY,
PUBLISHER.PHONENO,
PRINTJOB.STARTDATE,
PRINTJOB.COMPLETIONDATE;
PUBREC PUBCURSOR%ROWTYPE;
BEGIN
OPEN PUBCURSOR;
FOR PRINTJOB IN PUBCURSOR LOOP
PUBLISHERNAME := PRINTJOB.PUBLISHERNAME;
DBMS_OUTPUT.PUT_LINE('Publisher Name : ' || PRINTJOB.PUBLISHERNAME);
LOOP
FETCH PUBCURSOR INTO PUBREC;
EXIT WHEN PUBCURSOR%NOTFOUND;
IF PUBREC.NUMOFJOBS <= 3 THEN INSERT INTO PUBLISHERDETAILS VALUES (
PUBREC.PUBLISHERNAME,
PUBREC.PUBLISHERCITY,
PUBREC.PHONENO,
PUBREC.NUMOFJOBS
);
ELSE DBMS_OUTPUT.PUT_LINE(PUBREC.NUMOFJOBS
|| ' '
|| PUBREC.STARTDATE
|| ' '
|| PUBREC.COMPLETIONDATE);
END IF;
END LOOP;
END LOOP;
CLOSE PUBCURSOR;
COMMIT;
EXCEPTION
WHEN NOTFOUND THEN DBMS_OUTPUT.PUT_LINE('Record Not Found');
END;
Gleaned from comments below, the code being used to execute the procedure:
BEGIN
pub_Job_Count;
End;
Your program is expecting an input, a number.
But when you call it, you're not providing said number.
So, the database gets upset and issues this:
PLS-00306: wrong number or types of arguments in call to 'PUB_JOB_COUNT'
To fix it,
BEGIN
pub_Job_Count(1); -- your number is added here, either explicitley or via a variable you add in a DECLARE
END;
/
A basic example
clear screen
create or replace procedure so_missing_inputs (x in integer, y in date) is
begin
dbms_output.put_line('X is: ' || x);
dbms_output.put_line('Y is: ' || to_char(y, 'MM-DD-YYYY HH24:MI:SS'));
end;
/
set serveroutput on
declare
a integer := 2;
b date := sysdate;
begin
-- so_missing_inputs(); -- missing 2 inputes
-- so_missing_inputs(1); -- missing 1 inputs
-- so_missing_inputs(sysdate, 2); -- right number of inputs, but not right data types
so_missing_inputs(x => a, y => b); -- let's be explicit about inputs vs coutning on right order
end;
/
If I run this -
If you were to uncomment one of the previous lines, you'd see the PLS-00306 creep back in.
One final note, on DBMS_OUTPUT. It's a good way to see what things are happening while 'debugging' your code, but it's not a good way to communicate things outside the PL/SQL program.

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.

Execute a Select inside a loop PL/SQL and return cursor?

This is probably a simple question for who knows PL/SQL.
I have a stored procedure who takes an array of varchar in input:
TYPE MULTI is table of VARCHAR(15) index by BINARY_INTEGER;
PROCEDURE MYPROC(
SINGLE IN MULTI,
P_RESULT OUT MY_PCK.MYCURSOR,
P_SOMETHING OUT VARCHAR2,
);
The cursor works because i have tested it in other cases but this is the first with an array parameter.
I have a problem with the body, how can i assign each value i get from the select to the cursor?
BEGIN
FOR i IN SINGLE.first .. SINGLE.last
LOOP
--BEGIN OPEN P_RESULT FOR this --this on left gives me error
SELECT MT.DESCR INTO P_SOMETHING
FROM MYTABLE1 MT
WHERE MT.IDS = SINGLE(i)
AND and rownum < 2;
--dbms_output.put_line(SINGLE(i)); --if i use this instead of select i get the values i send to this procedure.
END LOOP;
i tried also:
SELECT MT.DESCR INTO P_RESULT but gives error
What i'm doing wrong?
Thanks in advice.
You can't use your PL/SQL collection type in a SQL statement in 11g. You could create a SQL collection type, or find one you already have access to (which has a suitable string length) and use that to at least verify the mechanism.
For instance, this uses a local variable of type SYS.HSBLKNAMLST, which is defined as a table of varchar2(30), more than enough to match your own PL/SQL type's string length. That variable is populated from your passed-in PL/SQL type, and that is then used in a query to open the cursor:
PROCEDURE MYPROC(
SINGLE IN MULTI,
P_RESULT OUT MY_PCK.MYCURSOR
) IS
LOCAL_COLL SYS.HSBLKNAMLST := SYS.HSBLKNAMLST();
BEGIN
FOR i IN SINGLE.first .. SINGLE.last
LOOP
LOCAL_COLL.extend();
LOCAL_COLL(LOCAL_COLL.last) := SINGLE(i);
END LOOP;
OPEN P_RESULT FOR
SELECT MT.DESCR
FROM MYTABLE1 MT
LEFT JOIN TABLE(LOCAL_COLL) LC
ON LC.COLUMN_VALUE = MT.IDS
WHERE LC.COLUMN_VALUE IS NULL;
END;
I'm a bit confused about the query you showed in your loop though; you seem to be attempting to re-open the cursor for each element of the array, though maybe you were trying to append the result of the query for each element to the same cursor - thought the != would mean that all rows would be included at some point. I've guess that you're trying to get all records with IDS values that are not in the array. If you actually want all that are then that would be:
OPEN P_RESULT FOR
SELECT MT.DESCR
FROM TABLE(LOCAL_COLL) LC
JOIN MYTABLE1 MT
ON MT.IDS = LC.COLUMN_VALUE;
You can see which built-in types are available to you by querying the data dictionary, e.g:
select owner, type_name, coll_type, elem_type_name, length
from all_coll_types
where elem_type_name = 'VARCHAR2'
and coll_type = 'TABLE'
and owner = 'SYS'
order by length;
It would be preferable to create your own SQL type if you're able to.

using comma separated values inside IN clause for NUMBER column

I have 2 procedures inside a package. I am calling one procedure to get a comma separated list of user ids.
I am storing the result in a VARCHAR variable. Now when I am using this comma separated list to put inside an IN clause in it is throwing "ORA-01722:INVALID NUMBER" exception.
This is how my variable looks like
l_userIds VARCHAR2(4000) := null;
This is where i am assigning the value
l_userIds := getUserIds(deptId); -- this returns a comma separated list
And my second query is like -
select * from users_Table where user_id in (l_userIds);
If I run this query I get INVALID NUMBER error.
Can someone help here.
Do you really need to return a comma-separated list? It would generally be much better to declare a collection type
CREATE TYPE num_table
AS TABLE OF NUMBER;
Declare a function that returns an instance of this collection
CREATE OR REPLACE FUNCTION get_nums
RETURN num_table
IS
l_nums num_table := num_table();
BEGIN
for i in 1 .. 10
loop
l_nums.extend;
l_nums(i) := i*2;
end loop;
END;
and then use that collection in your query
SELECT *
FROM users_table
WHERE user_id IN (SELECT * FROM TABLE( l_nums ));
It is possible to use dynamic SQL as well (which #Sebas demonstrates). The downside to that, however, is that every call to the procedure will generate a new SQL statement that needs to be parsed again before it is executed. It also puts pressure on the library cache which can cause Oracle to purge lots of other reusable SQL statements which can create lots of other performance problems.
You can search the list using like instead of in:
select *
from users_Table
where ','||l_userIds||',' like '%,'||cast(user_id as varchar2(255))||',%';
This has the virtue of simplicity (no additional functions or dynamic SQL). However, it does preclude the use of indexes on user_id. For a smallish table this shouldn't be a problem.
The problem is that oracle does not interprete the VARCHAR2 string you're passing as a sequence of numbers, it is just a string.
A solution is to make the whole query a string (VARCHAR2) and then execute it so the engine knows he has to translate the content:
DECLARE
TYPE T_UT IS TABLE OF users_Table%ROWTYPE;
aVar T_UT;
BEGIN
EXECUTE IMMEDIATE 'select * from users_Table where user_id in (' || l_userIds || ')' INTO aVar;
...
END;
A more complex but also elegant solution would be to split the string into a table TYPE and use it casted directly into the query. See what Tom thinks about it.
DO NOT USE THIS SOLUTION!
Firstly, I wanted to delete it, but I think, it might be informative for someone to see such a bad solution. Using dynamic SQL like this causes multiple execution plans creation - 1 execution plan per 1 set of data in IN clause, because there is no binding used and for the DB, every query is a different one (SGA gets filled with lots of very similar execution plans, every time the query is run with a different parameter, more memory is needlessly used in SGA).
Wanted to write another answer using Dynamic SQL more properly (with binding variables), but Justin Cave's answer is the best, anyway.
You might also wanna try REF CURSOR (haven't tried that exact code myself, might need some little tweaks):
DECLARE
deptId NUMBER := 2;
l_userIds VARCHAR2(2000) := getUserIds(deptId);
TYPE t_my_ref_cursor IS REF CURSOR;
c_cursor t_my_ref_cursor;
l_row users_Table%ROWTYPE;
l_query VARCHAR2(5000);
BEGIN
l_query := 'SELECT * FROM users_Table WHERE user_id IN ('|| l_userIds ||')';
OPEN c_cursor FOR l_query;
FETCH c_cursor INTO l_row;
WHILE c_cursor%FOUND
LOOP
-- do something with your row
FETCH c_cursor INTO l_row;
END LOOP;
END;
/

How to improve query performance for dynamic sql in Oracle

I have to fetch data from a running-time-defined table and get data based on a running-time-defined column, I'm now using dynamic sql with ref cursor as below. Is there any more efficient ways to improve the performance ?
PROCEDURE check_error(p_table_name IN VARCHAR2
,p_keyword IN VARCHAR2
,p_column_name IN VARCHAR2
,p_min_num IN NUMBER
,p_time_range IN NUMBER
,p_file_desc IN VARCHAR2
)
IS
type t_crs is ref cursor;
v_cur t_crs;
v_file_name VARCHAR2(100);
v_date_started DATE;
v_date_completed DATE;
v_counter NUMBER := 0;
v_sql VARCHAR2(500);
v_num NUMBER :=0;
BEGIN
v_sql := 'SELECT '||p_column_name||', DATE_STARTED,DATE_COMPLETED FROM '||p_table_name
|| ' WHERE '||p_column_name||' LIKE '''||p_keyword||'%'' AND DATE_STARTED > :TIME_LIMIT ORDER BY '||p_column_name;
OPEN v_cur FOR v_sql USING (sysdate - (p_time_range/1440));
LOOP
FETCH v_cur INTO v_file_name,v_date_started,v_date_completed;
EXIT WHEN v_cur%NOTFOUND;
IF v_date_started IS NOT NULL AND v_date_completed IS NULL
AND (sysdate - v_date_started)*1440 > p_time_range THEN
insert_record(co_alert_stuck,v_file_name,p_table_name,0,p_file_desc,p_time_range);
END IF;
END LOOP;
END;
BTW, will this make it better ?
v_sql := 'SELECT :COLUMN_NAME1, DATE_STARTED,DATE_COMPLETED FROM :TABLE WHERE :COLUMN_NAME2 LIKE :KEYWORD AND DATE_STARTED > :TIME_LIMIT ORDER BY :COLUMN_NAME3';
OPEN v_cur FOR v_sql USING p_column_name,p_table_name,p_column_name,p_keyword||'%',(sysdate - (p_time_range/1440)),p_column_name;
First, I'm not sure that I understand what the code is doing. In the code you posted (which you may have cut down to simplify things), the IF statement checks whether v_date_started IS NOT NULL which is redundant since there is a WHERE clause on DATE_STARTED. It checks whether (sysdate - v_date_started)*1440 > p_time_range which is just a redundant repetition of the WHERE clause on the DATE_STARTED column. And it checks whether v_date_completed IS NULL which would be more efficient as an additional WHERE clause in the dynamic SQL statement that you built. It would make sense to do all of your checks in exactly one place and the most efficient place to do them would be in the SQL statement.
Second, how many rows should this query return and where is the time being spent? If the cursor potentially returns many rows (for some definition of many), you'll get a bit of efficiency from doing a BULK COLLECT from the cursor into a collection and modifying the insert_record procedure to accept and process a collection. If the time is all spent executing the SQL statement and the query itself returns just a handful of rows, PL/SQL bulk operations would probably not make things appreciably more efficient. If the bottleneck is executing the SQL statement, you'd need to hope that an appropriate index existed on whatever table was passed in. If the bottleneck is the insert_record procedure, we'd need to know what that procedure is doing to comment.
Third, if the insert_record procedure is (at least primarily) just inserting the data that you fetched into a different table, it would be more efficient to get rid of all the looping and just generate a dynamic INSERT statement.
Fourth, with respect to your edit, you cannot use bind variables for table names or column names so the syntax you're proposing is invalid. It won't be more efficient because it will generate a bunch of syntax errors.