Oracle - Comparing dates - sql

I have this Oracle procedure:
CREATE OR REPLACE PROCEDURE xmas
IS
CURSOR curUser IS SELECT userID, coins FROM Users;
thisUser curUser%ROWTYPE;
d VARCHAR(7);
CURSOR curDate IS SELECT TO_CHAR(sysdate, 'DD-MON') FROM DUAL;
BEGIN
OPEN curDate;
FETCH curDate INTO d;
CLOSE curDate;
IF ((TO_DATE(d)) = (TO_DATE('25-DEC'))) THEN
OPEN curUser;
LOOP
FETCH curUser INTO thisUser;
EXIT WHEN (curUser%NOTFOUND);
thisUser.coins := thisUser.coins + 5.00;
UPDATE Users SET coins = thisUser.coins WHERE userID = thisUser.userID;
END LOOP;
CLOSE curUser;
END IF;
END xmas;
and when I call it, I get this error:
ORA-01840: input value not long enough for date format.
Tried different comparison methods for hours and nothing else has worked.
What's the problem??

You need to specify a date format for Oracle to know what actually '25-DEC' means.
select TO_DATE('25-DEC', 'DD-MON') from dual;
The problem should be on this line:
IF ((TO_DATE(d)) = (TO_DATE('25-DEC'))) THEN
As a note, it seems to me that you are also making a lot of unnecessary conversions. I don't know if this is an educational attempt, but why not just run the update below:
update USERS
set
coins = coins + 5
where
to_char(sysdate, 'DD-MON') = '25-DEC';

Related

adding to variables inside of a oracle procedure

I created a procedure that takes the account number of a user and it returns every transaction they've made plus their running balance. everything works except the balance. The code compiles and runs but nothing shows up in the running balance column of the dbms.output
CREATE OR REPLACE PROCEDURE print_dett(
in_account_nbr NUMBER
)
IS
dCount NUMBER;
BEGIN
dbms_output.put_line(' Date : transaction amount : balance - For: ' || in_account_nbr);
FOR r IN(
SELECT t.tx_nbr, t.tx_date, t.tx_amount, t.tx_type_code, a.balance
FROM transaction t, account a
WHERE t.account_nbr = a.account_nbr
AND t.account_nbr = in_account_nbr
ORDER BY tx_date)
LOOP
IF r.tx_type_code = 'D' OR r.tx_type_code = 'R' THEN
dCount := dCount + r.tx_amount;
ELSE
dCount := dCount - r.tx_amount;
END IF;
dbms_output.put_line(r.tx_date || ' : ' || r.tx_amount || ' : ' || dCount );
END LOOP;
END;
What can I change in the code so the running balance will actually show up when printed?
Number variables get initialized with NULL. Your variable dCount is hence null first and adding values doesn't change this.
Initialize it with zero instead:
CREATE OR REPLACE PROCEDURE print_dett(
in_account_nbr NUMBER
)
IS
dCount NUMBER := 0;
...

Oracle SQL - table type in cursor causing ORA-21700: object does not exist or is marked for delete

I have problem with my function, I'm getting ORA-21700: object does not exist or is marked for delete error. It's caused by table type parameter in cursor, but I've no idea how to fix it.
I've read that table type part should be assigned to a variable, but it can't be done in cursor, right? I've marked the part which causing the issue
Can anyone help? Is there any other way I can do this?
My package looks something like this:
FUNCTION createCSV(DateFrom date
,DateTo date)
RETURN clob IS
CURSOR c_id (c_DateFrom date
,c_DateTo date) IS
SELECT id
FROM limits
WHERE utcDateFrom <= NVL(c_DateTo, utcDateFrom)
AND NVL(utcDateTo, c_DateFrom + 1) >= c_DateFrom + 1;
CURSOR c (c_DateFrom date
,c_DateTo date
,pc_tDatePeriods test_pkg.t_date_periods) IS -- this is table type (TYPE xx AS TABLE OF records)
SELECT l.id limit_id
,TO_CHAR(time_cond.utcDateFrom, og_domain.cm_yyyymmddhh24mi) time_stamp_from
,TO_CHAR(time_cond.utcDateTo, og_domain.cm_yyyymmddhh24mi) time_stamp_to
FROM limits l
JOIN (SELECT limit_id, utcDateFrom, utcDateTo FROM TABLE(pc_tDatePeriods) --This part is causing the issue
) time_cond
ON l.id = time_cond.limit_id
WHERE l.utcDateFrom <= NVL(c_DateTo, l.utcDateFrom)
AND NVL(l.utcDateTo, c_DateFrom + 1) >= c_DateFrom + 1;
CSV clob;
tDatePeriods test_pkg.t_date_periods := test_pkg.t_date_periods();
BEGIN
FOR r_id IN c_id(DateFrom, DateTo)
LOOP
tDatePeriods := test_pkg.includeTimeGaps(p_Id => r_id.id); --this loop is ok
FOR r IN c(DateFrom, DateTo, tDatePeriods) --here I'm getting error
LOOP
CSV := CSV || chr(13) || r.limit_id || ',' || r.time_stamp_from || ',' || r.time_stamp_to;
END LOOP;
END LOOP;
RETURN CSV;
END createCSV;
Your problem should be solved by declaring type test_pkg.t_date_periods on schema level instead of in package.
Similar answer can be found here but with more details.

Using multiple procedures and multiple cursors in a package

I have created a package with two procedures and two cursors in it, but while executing the procedure, it is executed successful, but same record executed multiple times and a buffer overflow occurred.
I also tried removing the loop from the cursor but for 1 record that will be fine and for multiple record it won't work as expected.
EXPECTED
I just need to remove multiple execution of same record from the procedure where i am getting multiple execution of same record
for single procedure and single cursor it is working properly but for multiple cursor and multiple procedure i am getting problem here which caused buffer overflow too where i need different record
Is there any alternative way that I can fix the problem ?
CREATE OR REPLACE PACKAGE test.report AS
PROCEDURE distribution (
code_in IN user.test.code%TYPE,
fromdate date,
todate date
);
PROCEDURE tdvalue (
id IN user.test.custid%TYPE
);
END report;
/
Package Body
CREATE OR REPLACE PACKAGE BODY test.report as
----------VARIABLE DECLARATION----------------
code_in user.test.code%TYPE;
custidin user.test.custid%TYPE;
fromdate DATE;
todate DATE;
diff number(17,2);
---------------CURSOR DECLARATION--------------
CURSOR td_data(code_in user.test.code%TYPE,
fromdate date,
todate date
) IS
( SELECT
test.code,
COUNT(test.code) AS count,
SUM(test2.Deposit_amount) AS total,
test.currency
FROM
user.test2
JOIN user.test ON test2.acid = test.acid
WHERE
user.test2.open_effective_date BETWEEN TO_DATE(fromdate, 'dd-mm-yyyy') AND TO_DATE(todate, 'dd-mm-yyyy')
and
user.test.code = code_in
GROUP BY
test.code,test.currency
);
td__data td_data%rowtype;
CURSOR C_DATA(custidin user.test.custid%TYPE) IS SELECT
test.custid,
test2.id,
TO_DATE(test2.initial_date, 'dd-mm-yyyy') - TO_DATE(test2.end_date, 'dd-mm-yyyy') AS noofdays,
round(((test2.deposit_amount *((TO_DATE(test2.initial_date, 'dd-mm-yyyy') - TO_DATE(test2.end_date, 'dd-mm-yyyy'
)) / 365) * test4.interest_rate) / 100), 2) + test2.deposit_amount AS calculated_amount,
SUM(test.flow_amt) + test2.deposit_amount AS system_amount
FROM
user.test
JOIN user.test2 ON test3.entity_id = test2.id
WHERE
test.custid = custidin
GROUP BY
test.custid,
test2.id;
c__data c_data%ROWTYPE;
PROCEDURE distribution
(
code_in IN user.test.code%TYPE,
fromdate in date,
todate in date
)
AS
BEGIN
OPEN td_data(code_in,fromdate,todate);
loop
FETCH td_data INTO td__data;
dbms_output.put_line(td__data.code
|| ' '
|| td__data.count
|| ' '
||td__data.currency
||' '
||td__data.total
);
end loop;
CLOSE td_data;
END distribution;
PROCEDURE tdvalue (
custidin IN user.test.custid%TYPE
)
AS
BEGIN
open c_data(custidin);
fetch c_data into c__data;
loop
diff:= c__data.calculated_amount- c__data.system_amount;
dbms_output.put_line(c__data.custid
|| ' '
|| c__data.noofdays
|| ' '
|| c__data.end_date
|| ' '
|| c__data.initial_date
|| ' '
|| c__data.calculated_amount
||' '
||diff
);
end loop;
close c_data;
END tdvalue;
END report;
/
To run
ALTER SESSION set nls_date_format='dd-mm-yyyy';
SET SERVEROUTPUT ON;
EXEC REPORT.DISTRIBUTION('872328','01-02-2016','08-02-2019');
/
EXEC REPORT.tdvalue('S9292879383SS53');
Buffer overflow - ORU-10027 - happens when the total number of bytes displayed through DBMS_OUTPUT exceeds the size of the serveroutput buffer. The default is only 20000 bytes (who knows why?). Your session is using that default because of how you enable serveroutput. Clearly one record is less than 2000 and you only hit that limit when you run for multiple records.
To fix this try this
SET SERVEROUTPUT ON size unlimited
It's not actually unlimited, but the upper bound is the PGA limit (session memory) and you really shouldn't hit that limit with DBMS_OUTPUT. Apart from anything else who would read all that?
So the other problem with your code - as #piezol points out - is that your loops have no exit points. You should test whether the FETCH actually fetched anything and exit if it didn't:
loop
FETCH td_data INTO td__data;
exit when td_data%notfound;
dbms_output.put_line(td__data.code
|| ' '
|| td__data.count
|| ' '
||td__data.currency
||' '
||td__data.total
);
end loop;
Remembering to do this is just one reason why implicit cursors and cursor for loops are preferred over explicit cursors.
The second cursor loop is even worse because not only does it not have an exist point, the fetch is outside the loop. That's why you have repeated output for the same record.
So let's rewrite this ...
open c_data(custidin);
fetch c_data into c__data; -- should be inside
loop
diff:= c__data.calculated_amount- c__data.system_amount;
… as a cursor for loop:
PROCEDURE tdvalue (
custidin IN user.test.custid%TYPE
)
AS
BEGIN
for c__data in c_data(custidin)
loop
diff:= c__data.calculated_amount- c__data.system_amount;
dbms_output.put_line(c__data.custid
|| ' '
|| c__data.noofdays
|| ' '
|| c__data.end_date
|| ' '
|| c__data.initial_date
|| ' '
|| c__data.calculated_amount
||' '
||diff
);
end loop;
END tdvalue;
No need for OPEN, CLOSE or FETCH, and no need to check when the cursor is exhausted.
In PL/SQL, the preferred mechanism for setting the DBMS_OUTPUT buffer size would be within your procedure. This has the benefit of working in any client tool, such as Java or Toad (though it is still up to the client tool to retrieve the output from DBMS_OUTPUT).
DBMS_Output.ENABLE
Pass in a parameter of NULL for unlimited buffer size.
It would go like this:
BEGIN
DBMS_OUTPUT.ENABLE(NULL);
FOR I IN 1..1000 LOOP
DBMS_OUTPUT.PUT_LINE('The quick red fox jumps over the lazy brown dog.');
END LOOP;
END;
/
Bonus fact:
You can use the other functions and procedures in DBMS_OUTPUT to roll your own if you aren't using SQL*Plus or a DBMS_OUTPUT-savvy tool like Toad.
You can use the GET_LINE or GET_LINES procedures from your client code to get whatever may have been written to DBMS_OUTPUT.
GET_LINE

Using ACCEPT, CASE to create CURSOR in pl/sql

I am trying to create a script that will allow the user to select which CASE population to use from an ACCEPT when gathering student contact info.
PROMPT 'Select a popluation for emails'
PROMPT '1. Currently registered'
PROMPT '2. New Applicants'
PROMPT
ACCEPT cnt number PROMPT 'Selection: ';
...
CURSOR stu_lst IS
CASE &cnt
WHEN 1 THEN -- Current registered students.
select distinct SFRSTCA_PIDM pidm
from SFRSTCA
where SFRSTCA_TERM_CODE = '201403' and
SFRSTCA_LEVL_CODE = '01' and
SFRSTCA_RSTS_CODE = 'RE';
WHEN 2 THEN -- New applicants
select app_pidm pidm
from app
where app_term = 'Fall 2014';
ELSE
-- Incorrect selection.
DBMS_OUTPUT.PUT_LINE('Incorrect selection made.');
exit;
END;
END;
Assuming the two queries return the same data type, you could use a union with a filter that checks the variable in each part; something like:
DECLARE
CURSOR stu_lst IS
-- Current registered students.
select distinct SFRSTCA_PIDM pidm
from SFRSTCA
where &cnt = 1 and
SFRSTCA_TERM_CODE = '201403' and
SFRSTCA_LEVL_CODE = '01' and
SFRSTCA_RSTS_CODE = 'RE';
UNION ALL
-- New applicants
select app_pidm
from app
where &cnt = 2 and
app_term = 'Fall 2014';
invalid_argument EXCEPTION;
...
BEGIN
IF &cnt NOT IN (1, 2) THEN
RAISE invalid_argument;
END IF
FOR rec IN stu_lst LOOP
h_pidm := rec.pidm;
...
END LOOP;
EXCEPTION
WHEN invalid_argument THEN
dbms_output.put_line('Incorrect selection made.');
END;
/
You could also declare a cursor variable and open that with the appropriate query, inside a case statement within the main body of the block. This sticks with your explicit cursor syntax though.

How to receive only the name of a month as input parameter and not entire date in pl/sql

create or replace
PROCEDURE DISPLAY_PRESC(
P_PATIENT_ID IN NUMBER,
P_MONTH IN DATE)
IS
V_patient_name PATIENT.PATIENT_NAME%TYPE;
CURSOR PRES_CURSOR IS
SELECT PRESCRIPTION_ID, PRESCRIPTION_DATE
FROM PATIENT PAT, PRESCRIPTION PRES
WHERE PAT.PATIENT_ID = P_PATIENT_ID
AND PAT.PATIENT_ID = PRES.PATIENT_ID
AND PRES.PRESCRIPTION_DATE = P_MONTH;
BEGIN
SELECT PATIENT_NAME INTO V_PATIENT_NAME
FROM PATIENT
WHERE PATIENT_ID = P_PATIENT_ID;
DBMS_OUTPUT.PUT_LINE ('List of prescriptions for: ' || V_PATIENT_NAME|| ' during: ' ||P_MONTH);
FOR PRES_REC IN PRES_CURSOR LOOP
DBMS_OUTPUT.PUT_LINE (PRES_REC.PRESCRIPTION_ID|| ' ' || PRES_REC.PRESCRIPTION_DATE);
END LOOP;
DBMS_OUTPUT.PUT_LINE ('*** END OF REPORT ***');
END DISPLAY_PRESC;
This procedure is supposed to display all the prescriptions of a particular patient for a certain month.
input parameters
eg: Patient_ID = 10;
P_Month = 'June' & not '01-Jun-07' like I currently have to enter it.
thanks in advance for the help.
try this code to display prescriptions in certain month in all years:
create or replace PROCEDURE DISPLAY_PRESC(P_PATIENT_ID IN NUMBER,
P_MONTH IN varchar2)
IS
V_patient_name PATIENT.PATIENT_NAME%TYPE;
CURSOR PRES_CURSOR IS
SELECT PRESCRIPTION_ID, PRESCRIPTION_DATE
FROM PATIENT PAT, PRESCRIPTION PRES
WHERE PAT.PATIENT_ID = P_PATIENT_ID
AND PAT.PATIENT_ID = PRES.PATIENT_ID
------ modified
AND trim(Initcap(to_char(PRES.PRESCRIPTION_DATE, 'month')) ) = trim(Initcap(P_MONTH))
------ added
order by PRES.PRESCRIPTION_DATE ;
BEGIN
SELECT PATIENT_NAME
INTO V_PATIENT_NAME
FROM PATIENT
WHERE PATIENT_ID = P_PATIENT_ID;
DBMS_OUTPUT.PUT_LINE('List of prescriptions for: ' || V_PATIENT_NAME ||
' during: ' || Initcap(P_MONTH) );
FOR PRES_REC IN PRES_CURSOR LOOP
DBMS_OUTPUT.PUT_LINE(PRES_REC.PRESCRIPTION_ID || ' ' ||
PRES_REC.PRESCRIPTION_DATE);
END LOOP;
DBMS_OUTPUT.PUT_LINE('*** END OF REPORT ***');
END DISPLAY_PRESC;
I suggest you to keep p_month as date parameter and handle it in your program interface code before sending it as parameter for DISPLAY_PRESC procedure.
Date object is a full date which includes year,month, day, hour, minutes, seconds.
If you want to get only a month you should get either a VARCHAR2, or a number, and migrate it to the required date.
There's no "month" data type in Oracle, so if you want to handle only monthes you'll have to do it manually in your code.
The way most of the systems do the same thing is getting to full dates, which they extract data between these dates (with days, and year), and that way bypass the problem you face right now.
Also remember that each year there is June, so you would probably need the year as input - so send in 06/01/2014 00:00:00 is actually helpful a bit - since you get one object, which you can extract two pieces of data from.