PL/SQL: how do I prompt user input in a procedure? - sql

This is a question about a small part of a large project I'm doing. I tried the following but I just get the two errors below it:
SET SERVEROUTPUT ON
CREATE OR REPLACE PROCEDURE HELLO AS
DECLARE
variable1 NUMBER(1);
variable2 CHAR(1);
BEGIN
DBMS_OUTPUT.PUT_LINE('Hello World');
variable1 := &please_enter_1_or_0;
variable2 := &please_enter_y_or_n;
END;
/
Error(2,5): PLS-00103: Encountered the symbol "DECLARE" when expecting
one of the following: begin function pragma procedure subtype type
current
cursor delete exists prior external language The symbol "begin" was
substituted for "DECLARE" to continue.
Error(10,8): PLS-00103: Encountered the symbol "end-of-file" when
expecting one of the following: ( begin case declare end exception
exit for goto if loop mod null pragma raise return select update
while with
<< continue close current delete fetch lock
insert open rollback savepoint set sql execute commit forall merge
pipe purge
We were given a markscheme of how our code would be marked, and for this section, the relevant criteria would be:
"Does the script use a procedure?" and
"Does the script prompt for right/wrong and team/individual and handle the data provided correctly?".
The project brief quotes "Develop a procedure that prompts for RIGHT/WRONG (using &), then updates table" (where table is the name of a table).
The purpose of the variables was to update an existing record attribute. i.e. if user chose 1 and n then update the null in the record to 2. if it was 1 and y then update to 1, and if 0 and y/n then update to 0.

PL/SQL is a language for writing autonomous programs. It is not designed for user interactivity. Input values are passed as parameters.
So your program should look like this
CREATE OR REPLACE PROCEDURE hello
( p1 in number
, p2 in varchar2 )
AS
l_salutation varchar2(20) := 'Hello World';
BEGIN
DBMS_OUTPUT.PUT_LINE(l_salutation);
DBMS_OUTPUT.PUT_LINE('p1 = ' || p1);
DBMS_OUTPUT.PUT_LINE('p2 = ' || p2);
END;
/
Note there is no need for DECLARE with a named Procedure. The section between AS and BEGIN is for declaring variables, as I've done with l_salutation.
You can provide values for those parameters when invoking the program. In SQL*Plus it would work like this:
SET SERVEROUTPUT ON
accept p1 prompt "please enter 1 or 0: "
accept p2 prompt "please enter Y or N: "
exec HELLO (&p1, '&p2')

This piece of code works only in SQL*Plus and can't be used to produce a stored procedure!!!
DECLARE
variable1 NUMBER(1);
variable2 CHAR(1);
BEGIN
DBMS_OUTPUT.PUT_LINE('Hello World');
variable1 := &please_enter_1_or_0;
variable2 := '&please_enter_y_or_n';
END;
Mind the difference in the last statement where the last substitution variable is quoted in a string to be properly accepted by the PL/SQL syntax. Anyway, as I told you in the last comment to your question this is not a user interaction but just the result of a statement preprocessing. Every time you input different values the RDBMS executes a different source code.
Probably your requirement to use a "procedure" doesn't meant to use a STORED procedure(that is impossible to do so), but they just intended a SQL*Plus script, ask for clarifications.

You cannot directly receive messages from the client in a PL/SQL procedure or package.
The best you can do to emulate this is to interface with table data, and have users insert data into the table and react to that, or use Advanced Queueing (which amounts to pretty much the same thing).
Alternatively, accept the user input as parameters when the procedure is called.

You can just Remove the declare to remedy that ora error

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.

Can I call a global variable in source variable in ODI?

I am trying to build a ODI procedure, which will take schema name, db procedure name and parameters from a oracle database metadata table. The parameter field contains a name of a ODI global variable.The source command is like this
SELECT SCHEMA_NAME VAR_SCHEMA, PROCEDURE_NAME VAR_PROCEDURE, PARAMETER_NAME
VAR_PARAMETER FROM SCHEMA-NAME.TABLE_NAME
the output of the source command is like this:
VAR_SCHEMA_NAME VAR_TABLE_NAME VAR_PARAMETER
ABC PROC_LIST TO_DATE('#VAR_ETL_LOAD_DATE','DD/MM/RRRR')
Here, #VAR_ETL_LOAD_DATE is a global variable in ODI.
In the target command of the procedure, I want to use these information from source command to execute procedures listed in metadata table. I wrote a command like this:
DECLARE
VVC_SQL_STMT LONG;
BEGIN
VVC_SQL_STMT := 'BEGIN
#VAR_SCHEMA_NAME.#VAR_PROCEDURE_NAME(#VAR_PARAMETER);
END;';
INSERT INTO AK_TST2 VALUES(VVC_SQL_STMT,SYSDATE);
COMMIT;
EXECUTE IMMEDIATE (VVC_SQL_STMT);
END;
This code gives the following error in ODI:
ODI-1228: Task PROC_SP_HANDLER (Procedure) fails on the target ORACLE
connection OCDM_SYS.
Caused By: java.sql.SQLException: ORA-06550: line 8, column 61:
PLS-00103: Encountered the symbol "#" when expecting one of the following:
* & = - + ; < / > at in is mod remainder not rem
<an exponent (**)> <> or != or ~= >= <= <> and or like like2
like4 likec between || multiset member submultiset
What is the reasons for this and how can I execute stored procedures in ODI by reading procedure names and parameters from a metadata table?
If you select data from a table and use the result as a code for further execution, normally you cannot use ODI variables there. Because it too late for ODI to recognse that it is a variable and substitute it by a variable. This is the same for both global and project variables.
If you could print "#"+variable_name from ?- or %-substitution than it will work. But if #-substitution prints variable name or if variable appears as a final code after fetching values from Source it is too late. In this cases it remains as a plain text #VAR.
In your particular case you can do the following:
Declare all variables like #VAR_ETL_LOAD_DATE in a package. I mean all variables that could potentially appear in the metadata table. Bacause scenario should know all variables in advance.
Select and fetch records within ?-substitution using odiRef.getJDBCConnection('SRC'). Collect all results into a java-variable in the form of executable code.
E.g., source code could look like this:
select 1 from dual;
<?
import java.sql.*;
String crlf = System.getProperty("line.separator");
String result = "begin"+crlf+"null;"+crlf;
PreparedStatement stmt = odiRef.getJDBCConnection("SRC").prepareStatement("select schema||'.'||proc||'('||param||')' from metatable");
ResultSet rs = stmt.executeQuery();
while(rs.next()){
result += "insert into ak_tst2 values('"+rs.getString(1).replaceAll("'",'"'.toString())+"');"+crlf;
result += "commit;"+crlf;
result += rs.getString(1)+";"+crlf;
}
result += "end;";
rs.close();
stmt.close();
?>
Target code should be very simple
<?=result?>
At runtime target code will appear like this
begin
null;
insert into ak_tst2 values('qwe.asd("param_using_#var")');
commit;
qwe.asd('param_using_#var');
insert into ak_tst2 values('qwe2.asd2("param2_using_#var")');
commit;
qwe2.asd2('param2_using_#var');
insert into ak_tst2 values('qwe3.asd3("param3_using_#var")');
commit;
qwe3.asd3('param3_using_#var');
end;
And ODI variables will be successfully substituted by values.

I am executing following procedure in oracle but get compilation error

Actually I want to get usernames from table using Stored Procedure
CREATE OR REPLACE PROCEDURE GetRecord
(
p_ID IN integer ,
p_user OUT VARCHAR2
)
AS
BEGIN
SELECT
USERNAMES
INTO
p_user
FROM tblUsers
WHERE ID = p_ID ;
END GetRecord;
BEGIN
DECLARE A VARCHAR2
EXECUTE GetRecord(21,A);
END
When I run the above procedure I got following errors
Error starting at line : 17 in command -
BEGIN
DECLARE A VARCHAR2
EXECUTE GetRecord(21,A);
END
Error report -
ORA-06550: line 3, column 1:
PLS-00103: Encountered the symbol "EXECUTE" when expecting one of the following:
:= . ( # % ; not null range default character
The symbol ";" was substituted for "EXECUTE" to continue.
ORA-06550: line 4, column 1:
PLS-00103: Encountered the symbol "END" when expecting one of the following:
begin function pragma procedure subtype type <an identifier>
<a double-quoted delimited-identifier> current cursor delete
exists prior
06550. 00000 - "line %s, column %s:\n%s"
*Cause: Usually a PL/SQL compilation error.
*Action:
Actually I want to get usernames from table using Stored Procedure
The code that you are using to execute your procedure appears to be incorrect. It sounds like you want
DECLARE
l_usernames tblUsers.usernames%type;
BEGIN
GetRecord( 21, l_usernames );
END;
In general, I'd strongly object to your naming convention. It makes no sense to use a plural usernames as a column name. A procedure named GetRecord tells you nothing about what it does-- GetUsername would be much more meaningful. Your anonymous block also appears to be declaring a local variable a that stores the returned username which also makes no sense from the standpoint of using meaningful identifiers.
I'd also suggest that a procedure is the wrong approach here. If your goal is to return a single value, use a function. Functions return things, procedures do not. A function can potentially be used in a SQL statement, a procedure cannot be.

Oracle Stored Procedure 'Learnings' issue

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.

Solution to "cannot perform a DML operation inside a query"?

I am using a Data Analysis tool and the requirement I have was to accept a value from the user, pass that as a parameter and store it in a table. Pretty straighforward so I sat to write this
create or replace
procedure complex(datainput in VARCHAR2)
is
begin
insert into dumtab values (datainput);
end complex;
I executed this in SQL Developer using the following statement
begin
complex('SomeValue');
end;
It worked fine, and the value was inserted into the table. However, the above statements are not supported in the Data Analysis tool, so I resorted to use a function instead. The following is the code of the function, it compiles.
create or replace
function supercomplex(datainput in VARCHAR2)
return varchar2
is
begin
insert into dumtab values (datainput);
return 'done';
end supercomplex;
Once again I tried executing it in SQL Developer, but I got cannot perform a DML operation inside a query upon executing the following code
select supercomplex('somevalue') from dual;
My question is
- I need a statement that can run the mentioned function in SQL Developer or
- A function that can perform what I am looking for which can be executed by the select statement.
- If it is not possible to do what I'm asking, I would like a reason so I can inform my manager as I am very new (like a week old?) to PL/SQL so I am not aware of the rules and syntaxes.
P.S. How I wish this was C++ or even Java :(
EDIT
I need to run the function on SQL Developer because before running it in DMine (which is the tool) in order to test if it is valid or not. Anything invalid in SQL is also invalid in DMine, but not the other way around.
Thanks for the help, I understood the situation and as to why it is illegal/not recommended
You could use the directive pragma autonomous_transaction. This will run the function into an independant transaction that will be able to perform DML without raising the ORA-14551.
Be aware that since the autonomous transaction is independent, the results of the DML will be commited outside of the scope of the parent transaction. In most cases that would not be an acceptable workaround.
SQL> CREATE OR REPLACE FUNCTION supercomplex(datainput IN VARCHAR2)
2 RETURN VARCHAR2 IS
3 PRAGMA AUTONOMOUS_TRANSACTION;
4 BEGIN
5 INSERT INTO dumtab VALUES (datainput);
6 COMMIT;
7 RETURN 'done';
8 END supercomplex;
9 /
Function created
SQL> SELECT supercomplex('somevalue') FROM dual;
SUPERCOMPLEX('SOMEVALUE')
--------------------------------------------------------------------------------
done
SQL> select * from dumtab;
A
--------------------------------------------------------------------------------
somevalue
Tom Kyte has a nice explanation about why the error is raised in the first place. It is not safe because it may depend upon the order in which the rows are processed. Furthermore, Oracle doesn't guarantee that the function will be executed at least once and at most once per row.
Just declare a variable to accept the return value, for example:
declare
retvar varchar2(4);
begin
retvar := supercomplex('somevalue');
end;
The select doesn't work because the function is performing an insert, if all it did was return a value then it would work.
Just execute the function in a dummy if ... end if; statement to ignore the return value:
exec if supercomplex('somevalue') then null; end if;
Or execute it as a parameter for put_line procedure to output the return value:
exec dbms_ouput ('result of supercomplex='||supercomplex('somevalue'));
result of supercomplex=done