How can I declare and initialize a package variable by SELECT - sql

Can someone help me understand why I can declare a hardcoded date value in a package but not assign the value from a query? I've seen a number of examples (including the reference book) that show declarations of hardcoded values but I'm unable to locate examples of assigning values to variable through queries.
This is allowed:
create or replace package body PACKAGE_NAME AS
tDate DATE := '2012-05-30';
-- ...procedures follow
This is allowed:
create or replace package body PACKAGE_NAME AS
tDate DATE := sysdate;
This is not allowed:
create or replace package body PACKAGE_NAME AS
tDate DATE := select MAX(date_) from Table_Name;
I've tried a number of ways and I'm ok with it not working - I can use it as needed in the procedures themselves. But I'd like to know why I can't assign a value to tDate this way? The specific error is:
Encountered the symbol 'SELECT' when expecting ....
Adding that I can get variable values assigned through queries in a stored procedure but the same process does not seem to work for package body.
PROCEDURE Proc_Name IS
tDate Date;
BEGIN
SELECT MAX(date_) into tDate from Table_Name;

You need to create a package initialization block in your package body. This is a relatively arcane bit of PL/SQL package lore, but it's there and can be used. Here's an example:
CREATE OR REPLACE PACKAGE TEST_PKG IS
tDate DATE;
END TEST_PKG;
Here we've created a package spec which contains only a single DATE variable. Now we'll create a simple body which initializes that variable using a SELECT statement:
CREATE OR REPLACE PACKAGE BODY TEST_PKG IS
-- package-private variables go first (if any)
(...)
-- then the public functions/procedures
(...)
-- then a final BEGIN-END block which is the package initialization block
BEGIN
SELECT SYSDATE
INTO tDATE
FROM DUAL;
EXCEPTION -- you can have exception handlers in your initialization block
WHEN OTHERS THEN
NULL; -- pointless here, but this is just an example
RAISE;
END TEST_PKG;
Now if you execute the following:
begin
-- Test statements here
DBMS_OUTPUT.PUT_LINE('TEST_PKG.tDate = ' || TEST_PKG.tDate);
end;
it should print the current date.
Share and enjoy.

You need to use INTO when selecting directly into a variable.
select MAX(date_)
INTO tDate
from Table_Name;

Related

PLS00215: String length constraints must be in range (1..32767)

I am new to pl/sql. I want to create a procedure that has three parameters called 'startMonth', 'endMonth', 'thirdMonth'. In the procedure, I am executing a sql query which is in 'run_sql' column in table_query. Values for 'startMonth', 'endMonth', 'thirdMonth' are needed to this query. This is how I wrote the procedure. My plan is to put all the sql queries in a separate table and execute in the for loop in the procedure. There I am creating a table called table1 and in the next month I want to drop it and create the table again. This is how I have written the procedure.
CREATE OR REPLACE procedure schema.sixMonthAverage (startMonth varchar,endMonth varchar ,thirdMonth varchar )
IS
start_date varchar := startMonth;
end_date varchar := endMonth;
begin
for c_rec in(select run_sql from table_query)
loop
dbms_output.put_line(startmonth);
dbms_output.put_line(endmonth);
execute immediate c_rec.run_sql using start_date, end_date;
Execute IMMEDIATE 'commit';
END LOOP;
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line('Exception');
END;
This is the query in the run_sql column in table_query.
create table table1
as
select account_num,bill_seq,bill_version,
to_char(start_of_bill_dtm,''YYYYMM-DD'') st_bill_dtm,
to_char(bill_dtm - 1,''YYYYMM-DD'') en_bill_dtm,
to_char(actual_bill_dtm,''YYYYMM-DD'') act_bill_dtm,
round((invoice_net_mny + invoice_tax_mny)/1000,0) mon_bill,
bill_type_id,bill_status
from billsummary
where to_char(bill_dtm - 1,''YYYYMM'') between'||chr(32)||
startMonth ||chr(32)||'and'|| chr(32)||endMonth ||chr(32)||
'and cancellation_dtm is null;
But when I try to compile the procedure it gives me the error 'PLS00215: String length constraints must be in range (1..32767). Though I searched for the error I could not find the exact reason. It seems to be a problem in variable assigning. But I could not resolve it.
--Update
As it is given in the answer I converted the strings to dates.
CREATE OR REPLACE procedure REPO.sixMonthAverage (startMonth varchar2,endMonth varchar2 ,thirdMonth varchar2 )
IS
start_date date := TO_DATE(startMonth, 'yyyymm');
end_date date := TO_DATE(endMonth, 'yyyymm');
But when executing the query it gives the error message that ORA-00904: "END_DATE": invalid identifier. But it does not show any error message for the start_date and what would be the reason for this error message?
The error is pointing you to where the problem is. String declarations (char, varchar, varchar2 - but you should only be using varchar2, not varchar) need a length; so for example:
CREATE OR REPLACE procedure sixMonthAverage (startMonth varchar2,endMonth varchar2 ,thirdMonth varchar2 )
IS
start_date varchar2(10) := startMonth;
end_date varchar2(10) := endMonth;
...
Notice the procedure arguments do not specify a length; only the local variable declarations.
If those represent dates then they, and passed-in arguments, should probably be dates, not strings. It depends what your dynamic SQL is expecting though - if that is converting the strings to dates and specifying the format mask then I guess it's OK; otherwise you should be passed dates, or convert the strings to dates. The example you showed doesn't seem to have any bind variables to populate, though.
Dropping and recreating tables is generally not something you want to be doing though. You could delete/truncate and repopulate a table; or use partitioning if you want to keep more than one month; or use a view (or materialized view).

No data found error in PL/SQL with the PROCEDURE

I am new with PLSQL and I have issue with this procedure.
I don't know what the error mean at the same time I am sure the table and data are created successfully.
the procedure should receive the start and end date with the invoice number to show the details
create or replace PROCEDURE Invoicedetails (Fromdate IN DATE , Todate IN DATE , InvoiceNum NUMBER)
IS
INV_info invoicetable%ROWTYPE;
BEGIN
SELECT *
INTO INV_info
FROM invoicetable
WHERE InvoiceNum = INV_info.InvoiceNum AND INV_info,InvoiceDate betwen Fromdate And Todate;
dbms_output.put_line('ID:'|| INV_info.InvoiceNum);
dbms_output.put_line('Amount:'|| INV_info.Invoiceamount);
dbms_output.put_line('Date:'|| INV_info.InvoiceDate);
END Invoicedetails;
When I call the procedure like this
BEGIN
Invoicedetails('01-JAN-2020','05-JAN-2020',200651)
END;
Error report
ORA-01403 :no data found
ORA-06512 : at "APPS.Invoicedetails",line 5
ORA-06512 : at line 2
01403. 00000 - "no data found"
If you say you are new then you have done a good job.
Let's dig into the problem,
If you are learning then put into TODO list, the next topic about exception in PLSQL and how to handle.
The error you get say ORA-01403 :no data found which is self explanatory and mean we are searching for something and whatever code you have written didn't find it as expected which leads us to the select statement,
SELECT *
INTO INV_info
FROM invoicetable
WHERE InvoiceNum = INV_info.InvoiceNum
AND INV_info,InvoiceDate betwen Fromdate And Todate;
In the above if you see,
First small problem is syntactical which is INV_info,InvoiceDate which should be INV_info.InvoiceDate (this is anyway not correct as per the expectations of result which I will clarify below)
Second and most important problem is you are trying to compare the value of InvoiceNum with the rowtype variable which is InvoiceNum = INV_info.InvoiceNum and you have to understand here INV_info.InvoiceNum is a variable and doesn't hold any value at this very time.
So you should compare the table value with the input you provided via parameter as WHERE invoicetable.InvoiceNum = invoiceNum. Left side is the table column and right side is the parameter you passed.
Similarly the condition AND INV_info,InvoiceDate betwen Fromdate And Todate should change to AND invoicetable.InvoiceDate betwen Fromdate And Todate.
Having said all these there are some things you also need to consider interms of naming convention of variables and also usage of alias for table. (Which can be seen what changes I made to the procedure below)
Accumulating all points the procedure can be further modified as,
create or replace procedure invoicedetails
(
p_fromdate in date
, p_todate in date
, p_invoicenum number)
is
inv_info invoicetable%rowtype;
begin
select *
into inv_info
from invoicetable i
where i.invoicenum = p_invoicenum
and i.invoiceDate between p_fromdate and p_todate;
dbms_output.put_line('ID:'|| inv_info.invoicenum);
dbms_output.put_line('Amount:'|| inv_info.invoiceamount);
dbms_output.put_line('Date:'|| inv_info.invoicedate);
end invoicedetails;
/
Here is db<>fiddle for your reference. I have to do a little trick by calling dbms.output to print the result while calling the procedure which you don't need when you try in your machine
First thing out of the chute, you declare to input parameters as DATE, but then when you call the procedure you supply a CHARACTER STRING. Just because the input looks like a date to you does not mean that oracle interprets as a DATE. DATE is an internal, binary data type. Where will the input values actually originate? As per your example, you need to convert the input string to a DATE:
create or replace PROCEDURE Invoicedetails (Fromdate IN VARCHAR2 , Todate IN VARCHAR2 , InvoiceNum NUMBER)
IS
v_fromdate date;
v_todate date;
INV_info invoicetable%ROWTYPE;
BEGIN
v_fromdate := to_date(fromdate,'dd-Mon-yyyy');
v_todoate := to_date(todate,'dd-Mon-yyyy');
Then, in the rest of your code, reference v_fromdate and v_todate instead of your input parms.
As an aside, you should also develop the habit of being consistent in your coding style. Unlike some other rdbms products, oracle really doesn't support MixedCaseNames. (Well, you can jump through some hoops to force it to, but that is going against the grain and you really don't want to go there.) Instead of MixedCaseNames, the oracle standard is underscore_separated_names.
You need to:
Not name the procedure's arguments the same as columns in your table; it's confusing to debug and can confuse the SQL parser into comparing the column to itself rather than comparing the column to the parameter's argument.
Handle the NO_DATA_FOUND exception.
Handle the TOO_MANY_ROWS exception.
Using something like this:
CREATE PROCEDURE InvoiceDetails (
p_FromDate IN InvoiceTable.InvoiceDate%TYPE, -- Use the types from the table
p_ToDate IN InvoiceTable.InvoiceDate%TYPE,
p_InvoiceNum IN InvoiceTable.InvoiceNum%TYPE
)
IS
inv_info invoicetable%ROWTYPE;
BEGIN
SELECT *
INTO INV_info
FROM invoicetable
WHERE InvoiceNum = p_InvoiceNum -- Don't have the same variable name as the
-- column name. One practice is to prefix the
-- parameter names with p_ to distinguish
-- that they were passed from outside the
-- procedure.
AND InvoiceDate BETWEEN p_FromDate AND p_ToDate;
dbms_output.put_line('ID: ' || INV_info.InvoiceNum);
dbms_output.put_line('Amount: ' || INV_info.InvoiceAmount);
dbms_output.put_line('Date: ' || TO_CHAR( INV_info.InvoiceDate, 'YYYY-MM-DD' ) );
EXCEPTION
WHEN NO_DATA_FOUND THEN -- Handle the exception when no rows are found.
dbms_output.put_line('No Invoices exist.');
WHEN TOO_MANY_ROWS THEN -- Handle the exception when multiple rows are found.
dbms_output.put_line('Multiple invices exist.');
END InvoiceDetails;
/
So, if you have the table:
CREATE TABLE invoicetable (
invoicenum NUMBER(10,0),
invoiceamount NUMBER(17,2),
invoicedate DATE
);
and then execute your anonymous PL/SQL block:
BEGIN
Invoicedetails( DATE '2020-01-01',DATE '2020-01-05',200651);
END;
/
There will be no rows to match and the NO_DATA_FOUND exception will be raised and you get the output:
No Invoices exist.
If you then insert a row:
INSERT INTO invoicetable (invoicenum, invoiceamount, invoicedate )
VALUES ( 200651, 200, DATE '2020-01-04' );
and run the same anonymous PL/SQL block, you now get the output:
ID: 200651
Amount: 200
Date: 2020-01-04
and, if you insert a second row:
INSERT INTO invoicetable (invoicenum, invoiceamount, invoicedate )
VALUES ( 200651, 300, DATE '2020-01-05' );
and run the anonymous PL/SQL block again, you get the output:
Multiple invices exist.
db<>fiddle here

Component must be declared error (ORA-06550)

Im getting this error:
ORA-06550:line 1, column 25:PLS-00302: component PA_EXCEPTION_LIST_UPDATE must be declared: line1, column 7:PL/SQL: Statement ignored.
I can't figure out what i did wrong.
PROCEDURE Pa_exception_list_update (p_ceid collection_entities.ceid%TYPE,
p_idusr users.idusr%TYPE
)
IS
v_idusr users.idusr%TYPE;
v_ceid collection_entities.ceid%TYPE;
BEGIN
INSERT INTO pa_exception_list(pa_exception_list_id,
ceid,
creation_date,
created_by)
VALUES(pa_exception_list_seq.nextval, p_ceid, SYSDATE, p_idusr);
END Pa_exception_list_update;
It looks like you are calling the procedure before it has been declared.
Look at this example.
Procedure A calls procedure B. But B is unknown at that moment.
create or replace package test is
begin
end test;
create or replace package body test is
procedure a
is
begin
b;
end;
procedure b is
begin
-- do someting
end;
end test;
Solution. Change the order of the procedures within the package or place the procedure in the package specification.
create or replace package test is
begin
procedure b;
end test;
create or replace package body test is
procedure a
is
begin
b;
end;
procedure b is
begin
-- do someting
end;
end test;
According error message the error appears at line 1.
In case this is a stand-alone procedure you must write like create or replace procedure Pa_exception_list_update ...
In case this is part of a PL/SQL Package then you must write like
CREATE OR REPLACE PACKAGE BODY <package name> AS
procedure Pa_exception_list_update ...
I think you are missing something when you declare.
p_ceid IN collection_entities.ceid%TYPE,
p_idusr IN users.idusr%TYPE
I also faced the same problem.
After checking I found that, the procedure i was calling did not exist in the package!.
Later changed the procedure name and it worked.

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

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

ORACLE PL/SQL Variable Function Scope - Need Explanation

I just tripped over an answer to a problem I was having with a PL/SQL variable not being recognized by a function and I was hoping someone could explain to me why my solution worked and what is happening "underneath the hood".
Background
As part of an optimization project, I am trying to collect metrics on individual SQL scripts within a Stored Procedure. The Stored Proc that I am dissecting has an In-type date parameter that I need to define in order to run each individual SQL Script:
CREATE OR REPLACE myStoredProc (DATE_IN DATE, ERROR_OUT OUT VARCHAR2)
IS
BEGIN
--Truncate Temp Tables
--6 Individual SQL Scripts
EXCEPTION
--Error Handling
END;
To run each script individually, I decided to just drop each SQL statement into a PL/SQL block and feed the DATE_IN parameter in as a variable:
DECLARE
DATE_IN DATE := TO_DATE('16-JUL-2014','DD-MON-RR');
BEGIN
--Place individual script here
END;
The Problem
This approach worked fine for a couple of the queries that referred to this DATE_IN variable but one query with a reference to an outside function which takes DATE_IN as a parameter started throwing an ORA-00904 error:
DECLARE
DATE_IN DATE := TO_DATE('16-JUL-2014','DD-MON-RR');
BEGIN
insert into temp_table
SELECT table1.field1,
table1.field2,
table2.fieldA,
MyFunction(table1.field1, DATE_IN) --This was the problem line
FROM
table1,
table2
WHERE EXISTS (inner query)
AND table1.keys = table2.keys
AND table2.date <= DATE_IN
END;
Solution
At the advice of another Developer, I was able to get around this error by adding a colon (:) in front of the DATE_IN variable that I was passing into the function so that the problem line read MyFunction(table1.field1, :DATE_IN). As soon as I did that, my error disappeared and I was able to run the query without issue.
I was happy for the result but the other Developer wasn't able to explain why it was needed, only that it was necessary to call any functions or other stored procs from a PL/SQL statement. I assume this has something to do with scope but I would like to get a better explanation as to why this colon was necessary for the function to see the variable.
Questions
I've tried to do a little research looking over Oracle documentation on parameters, variables, binding/declaring and constants but my research has only given me more questions:
After reading up on variables, I now question if that is the correct term for what I have been using (since I didn't actually use the VARIABLE command and I'm passing in a date - which is not an allowable data type). If my DATE_IN DATE := statement is not a variable, then what is it?
Why were the rest of my references to DATE_IN recognized by the compiler but passing the value to the function was out of scope?
What exactly is the colon (:) doing here? Is this turning that into a bind variable?
Thanks in advance. I appreciate any guidance you can provide!
----------------------------------EDIT--------------------------------------
I was asked to provide additional information. My Db version is 11G, 11.2.0.2.0. The query that I was able to reproduce this error is below.
DECLARE
EXTRACT_DT_IN DATE := TO_DATE('16-JUL-2014','DD-MON-RR');
BEGIN
--This begins the pre-optimized query that I'm testing
insert into AELI_COV_TMP_2_OPT
SELECT /*+ ordered use_nl(CM MAMT) INDEX (CM CSMB_CSMB2_UK) INDEX (MAMT (MBAM_CSMB_FK_I) */
CM.CASE_MBR_KEY
,CM.pyrl_no
,MAMT.AMT
,MAMT.FREQ_CD
,MAMT.HOURS
,aeli$cov_pdtodt(CM.CASE_MBR_KEY, EXTRACT_DT_IN)
FROM
CASE_MEMBERS CM
,MEMBER_AMOUNTS MAMT
WHERE EXISTS (select /*+ INDEX(SDEF SLRY_BCAT_FK_I) */
'x'
from SALARY_DEF SDEF
where SDEF.CASE_KEY = CM.CASE_KEY
AND SDEF.TYP_CD = '04'
AND SDEF.SLRY_KEY = MAMT.SLRY_KEY)
AND CM.CASE_MBR_KEY = MAMT.CASE_MBR_KEY
AND MAMT.STAT_CD = '00'
AND (MAMT.xpir_dt is null or MAMT.xpir_dt > EXTRACT_DT_IN)
AND MAMT.eff_dt <= EXTRACT_DT_IN;
--This ends the pre-optimized query that I'm testing
END;
Here is the error I'm encountering when trying to run an Explain Plan on this statement. I am able to get past this error if I remove reference to line 13 or I add a colon (:) to the EXTRACT_DT_IN on that line.
----------------------EDIT 2-------------------
Here is the function signature of aeli$.cov_pdtodt. (I've replaced the owner for security reasons).
CREATE OR REPLACE function __owner__.aeli$cov_pdtodt
(CASE_MBR_KEY_IN IN NUMBER, EXTRACT_EFF_DT_IN DATE)
RETURN DATE IS
PDTODT DATE;
Your anonymous block is fine as it is, as long as you execute the whole block. If you try to execute just the insert, or its select, as a standalone command then it will indeed fail with ORA-00904.
That isn't quite a scope problem, it's a context problem. You're trying to refer to a PL/SQL variable in an SQL context, and that is never going to work.
In a PL/SQL context this would work:
declare
some_var dual.dummy%type := 'X';
begin
insert into some_table
select dummy from dual where dummy = some_var;
end;
/
... because the insert has access to the PL/SQL some_var.
In an SQL context this will error:
select * from dual where dummy = some_var;
... because it's looking for a column called SOME_VAR, and there isn't one.
If you do this instead:
select * from dual where dummy = :some_var;
... the some_var is now a client-managed bind variable. If you execute that you'll either be prompted for the bind value, or given a not-all-variables-bound error, or bind-variable-not-declared, or similar, depending on your client.
If you only do an explain plan of that though, e.g. with
set auto trace traceonly explain
select * from dual where dummy = :some_var;
... then the bind variable doesn't necessarily have to be populated for the plan to be calculated. Some clients may still complain and want a bind value, but the parser would be OK with it - enough to produce a plan anyway. Though not able to take advantage of bind variable peeking or histograms etc.
For example, SQL Developer happily produces a plan for your original sample query if both references are turned into bind variables, just the insert ... part of the block is selected, and you press Explain Plan (F10).
I'm not sure what you read, but you're mixed up on a few things here.
Your DATE_IN is a variable. You don't need to type 'VARIABLE' anywhere to declare a variable, all you need is the name of the variable and the datatype.
All of the below are legitimate variables in PL/SQL (although poorly named).
variable_1 NUMBER;
variable_2 VARCHAR2(100);
variable_3 DATE;
It's hard to tell what you're doing in your code without seeing it all. Do you have two DATE_IN variables declared within the same block? Is DATE_IN the name of a column in your table?
If you have a column named DATE_IN in table1 or table2, that's likely your problem. Oracle doesn't know if you want to use your variable or your column, and it will always default to the column name. Your function would be expecting a DATE and receiving a column, hence the error.