Oracle PL/SQL error - store procedure - ORA-00979 and ORA-06512 - sql

I am using following store procedure to insert data to a table.
create or replace PROCEDURE PM
(
date1 in varchar2
,date2 in varchar2
,date3 in varchar2
) AS
cursor cur_cd is
(
select to_date(date1,'DD-MON-YY') as date1
,trim(t.DEPT_CODE) DEPT_CODE
,count(t.DEPT_CODE) count_dept
,sum(t.amount) amount
from department t
where t.date >= to_date(date2,'DD-MON-YY')
and t.date <= to_date(date2,'DD-MON-YY')
and t.dept_name like 'finance%'
and (trim(t.DT_code)='TR_01' or t.DT_file like 'DTF_20%')
and t.DEPT_CODE not in ('HR','ADMIN','ACADEMIC')
group by t.DEPT_CODE
);
Type rec_set is table of dept_file%rowtype;
v_rec_set record_set;
begin
open cur_cd;
loop
fetch cur_cd
bulk collect into v_rec_set limit 100;
exit when v_rec_set.count()=0;
begin
forall i in v_rec_set.first..v_rec_set.last
insert into dept_file
values v_rec_set(i);
end;
end loop;
close cur_cd;
exception when others then raise;
end PM;
It's giving me a runtime error when execute procedure. But the query execute without error manually.
ORA-000979 : not a GROUP BY expression
ORA-006512 : at "ABS.PM", line 9
Also, when hard code the parameters (date1, date2 and date3) the procedure working without error.
Can you please help me to resolve this error?

All non-aggregated columns must be specified in the GROUP BY clause. Therefore:
GROUP BY TO_DATE (date1, 'DD-MON-YY'), TRIM (t.dept_code)
By the way, are you really storing date values as strings? Why do I ask? Because you're using TO_DATE functions all over the code. If you are, then try not to do it in the future. Oracle offers DATE datatype, you should use it.

Related

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

How to print record that caused ORA-01843 error?

I have a table with millions of records. I am trying to format one column data to DATE format
which is currently in VARCHAR2. But, I am getting ORA-01843 not a valid month error.
I am trying to get those records which are causing ORA-01843
CREATE OR REPLACE PROCEDURE pr_TEST_CUSTOM_ORA1843 AS
v_company MyTable.MyColumn%TYPE;
BEGIN
BEGIN
SELECT to_char(to_date(TRIM(MyColumn), 'YYMMDD'), 'MM/DD/YYYY')
INTO v_company FROM MyTable;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('ORA-01843 caused by'||v_company);
END;
END pr_TEST_CUSTOM_ORA1843;
But, the value of v_company is not printed.
How to get the records which are causing ORA-01843 error?
I just commented on your previous question : How to format only those records for which ORA-01843 is not thrown? but you did not pay attention to it.
Create a function which checks if it is a valid date like this.
CREATE OR REPLACE FUNCTION validmmddyyyy (p_str IN VARCHAR2)
RETURN NUMBER
AS
V_date DATE;
BEGIN
V_Date := TO_DATE (p_str, 'MM/DD/YYYY');
RETURN 1;
EXCEPTION
WHEN OTHERS
THEN
RETURN 0;
END;
Then, to select the records which fail, you can run a query
select MyColumn FROM MyTable where validmmddyyyy(MyColumn) = 0
When you are lucky enough to use Oracle 12c R2, you could make use of DEFAULT..ON..CONVERSION ERROR clause of TO_DATE
SELECT *
FROM MyTable
WHERE TO_DATE (MyColumn default null on conversion error,'MM/DD/YYYY') IS NULL
An important advice as always, don't use a VARCHAR2 / CHAR type for DATEs in database.

Wrong date format for input parameter?

CREATE OR REPLACE PROCEDURE PROC1(
V_STARTTIME IN DATE,
V_ENDTIME IN DATE)
BEGIN
INSERT INTO TAB1
SELECT COINS FROM TAB2
WHERE DATE BETWEEN TO_DATE(V_STARTTIME,'MM/DD/YYYY') AND TO_DATE(V_ENDTIME,'MM/DD/YYYY');
COMMIT;
END;
SAMPLE DATE in Tab2 IS TIMESTAMP DATATYPE 5/5/2014 9:46:38.000000 AM
When I try to execute
Execute PROC1(TO_DATE('5/5/2014','MM/DD/YYYY'),TO_DATE('5/6/2014','MM/DD/YYYY'));
the procedure is successfully completed but my Insert into was ignored.
I tried printing the input date through dbms_output.put_line and the date did not return.
This is very, very similar to the question you asked yesterday.
If v_starttime and v_endtime are of type date, it makes no sense to call to_date on them. to_date does not take an argument of type date. It takes a parameter of type varchar2. If you try to pass a date to to_date, Oracle has to implicitly cast the date to a varchar2 using the session's NLS_DATE_FORMAT. If that doesn't match the format mask you're passing to to_date, you may get an error or you may get an incorrect result. As in yesterday's question, you want to avoid implicit conversions.
A date in Oracle has both a day and a time component (to the second). If you are doing the to_date in order to ensure that the time component is midnight, use the trunc function instead.
INSERT INTO TAB1( column_name )
SELECT COINS
FROM TAB2
WHERE <<timestamp column>> BETWEEN trunc( v_starttime ) AND trunc( v_endtime );
You say that your "insert was ignored". That seems highly unlikely. It's much more likely that your SELECT statement returned 0 rows so your INSERT inserted 0 rows. That's not an error. If you want to treat it as an error, you'd need to check SQL%ROWCOUNT after the INSERT and throw an error if the INSERT statement inserts 0 rows.
If the SELECT was not selecting any rows because of an implicit conversion error, then getting rid of the to_date and potentially adding the trunc would fix the problem.
The function TO_DATE requires string as first parameter.
CREATE OR REPLACE PROCEDURE PROC1(
V_STARTTIME IN DATE,
V_ENDTIME IN DATE)
BEGIN
INSERT INTO TAB1
SELECT COINS FROM TAB2 WHERE DATE BETWEEN V_STARTTIME AND V_ENDTIME;
COMMIT; --You should not use commit in procedure.
END;

Oracle Procedure error (PLS-00428)

This is the error message: PLS-00428: an INTO clause is expected in this SELECT statement. Meanwhile, this is the procedure for testing displaying the system date:
CREATE OR REPLACE
PROCEDURE "TEST_PROCEDURE"
AS
BEGIN
SELECT SYSDATE FROM DUAL;
END;
In the first place I don't need to use that INTO Oracle is insisting for me to do. Is there other way around beside using a cursor (I've seen it here https://stackoverflow.com/a/6029963/1983024)? I think it should not be like that, this does run normally just like in MS SQL without using INTO or cursor.
In the first place I don't need to use that INTO Oracle is insisting
for me to do.
The fact is, Oracle is correct: you do need to use an INTO to take the return value.
After all, if you want to display the result of the query you're going to need a variable to put it in first.
you can write
CREATE OR REPLACE
PROCEDURE "TEST_PROCEDURE"
AS
BEGIN
for r_row in ( SELECT SYSDATE s FROM DUAL)
dbms_output.put_line(r_row.s);
end loop;
END;
or you have to have a variable.
CREATE OR REPLACE
PROCEDURE "TEST_PROCEDURE"
AS
v_Date date;
BEGIN
SELECT SYSDATE into v_date FROM DUAL;
dbms_output.put_line(v_date );
END;
output format is dictated by your NLS_DATE_FORMAT setting, or you can be explicit like to_char(v_date, 'dd-mm-yyyy') etc.
Finally found a solution to the output I want (based on your responses) :D
CREATE OR REPLACE
PROCEDURE "TEST_PROCEDURE"
RET_DATE CHAR(10);
BEGIN
SELECT TO_CHAR(SYSDATE, 'MM/DD/YYYY') INTO RET_DATE FROM DUAL;
DBMS_OUTPUT.PUT_LINE(RET_DATE);
END;
This displays the SYSDATE with format of MM/DD/YYYY. Still, thanks for the replies and ideas (mostly #Jeffrey Kemp). Oracle just lengthens what MS SQL can normally do in one line :D
You can use like
CREATE OR replace PROCEDURE Test_procedure
IS
date CHAR(10);
BEGIN
SELECT To_char(SYSDATE, 'MM/DD/YYYY')
INTO date
FROM dual;
dbms_output.Put_line(date);
END;
it will return date into char format.
If you want to get date into date format just declare the date type variable then assign the sysdate value INTO that variable.Then use DBMS_OUTPUT.PUT_LINE(variable) to print the DATE.
If you wanted to do it in one line, you could also use:
CREATE OR replace PROCEDURE Test_procedure
IS
BEGIN
dbms_output.put_line(to_char(sysdate, 'MM/DD/YY'));
END;

Want to insert timestamp through procedure in oracle

I have this procedure
PROCEDURE insertSample
(
return_code_out OUT VARCHAR2,
return_msg_out OUT VARCHAR2,
sample_id_in IN table1.sample_id%TYPE,
name_in IN table1.name%TYPE,
address_in IN table1.address%TYPE
)
IS
BEGIN
return_code_out := '0000';
return_msg_out := 'OK';
INSERT INTO table1
sample_id, name, address)
VALUES
(sample_id_in, name_in, address_in);
EXCEPTION
WHEN OTHERS
THEN
return_code_out := SQLCODE;
return_msg_out := SQLERRM;
END insertSample;
I want to add 4th column in table1 like day_time and add current day timestamp in it.. ho can i do that in this procedure.. thank you
Assuming you you have (or add) a column to the table outside of the procedure, i.e.
ALTER TABLE table1
ADD( insert_timestamp TIMESTAMP );
you could modify your INSERT statement to be
INSERT INTO table1
sample_id, name, address, insert_timestamp)
VALUES
(sample_id_in, name_in, address_in, systimestamp);
In general, however, I would strongly suggest that you not return error codes and error messages from procedures. If you cannot handle the error in your procedure, you should let the exception propagate up to the caller. That is a much more sustainable method of writing code than trying to ensure that every caller to every procedure always correctly checks the return code.
Using Sysdate can provide all sorts of manipulation including the current date, or future and past dates.
http://edwardawebb.com/database-tips/sysdate-determine-start-previous-month-year-oracle-sql
SYSDATE will give you the current data and time.
and if you add the column with a default value you can leave your procedure as it is
ALTER TABLE table1 ADD when_created DATE DEFAULT SYSDATE;