Returning a PLSQL Cursor - sql

I have a table which contains schedule information. Depending on the situation I may wish to select all schedule information for a driving serial or I may want to select based on start and end dates. This is quite a generic thing to want to do.
I then have a function which requires said schedule information but something inside the method will decide whether a start date is required or not.
So I want something like this:
getCursor(serial, startdate, enddate)
Inside my function:
if something or other:
cursor = getCursor(serial, null, null)
else
cursor = getCursor(serial, start, null)
Loop around cursor
Any idea about how to get this behaviour? I have looked around and seen various different methods but haven't been able to get any of them to work. Is what I want to do event the right approach in PL/SQL? It's not a language I often use...

It's not obvious to me that you need an IF statement or any recursion. It sounds like you just want
CREATE OR REPLACE FUNCTION getSchedule( p_serial IN table_name.serial%type,
p_start_date IN DATE DEFAULT NULL,
p_end_date IN DATE DEFAULT NULL )
RETURN sys_refcursor
IS
l_schedule_rc sys_refcursor;
BEGIN
OPEN l_schedule_rc
FOR SELECT *
FROM table_name
WHERE serial = p_serial
AND (p_start_date IS NULL or start_date >= p_start_date)
AND (p_end_date IS NULL or end_date <= p_end_date);
RETURN l_schedule_rc;
END;
This allows the start and end dates to be optional parameters and your query ignores the dates if the parameters input are NULL or are omitted. Of course, this assumes that you have a calling program that knows how to call a PL/SQL function that returns a sys_refcursor...

It looks like you do not need a special function for you issue.
Try this:
-- Retruns all rows that start_date is between p_from and p_to
SELECT *
FROM table_name t
WHERE t.serial = p_serial
AND t.start_date BETWEEN NVL(p_from, t.start_date)
AND NVL(p_to , t.start_date)

Related

PLSQL-0043 - cannot be v_day into select / fetch

I am trying to write a PLSQL code to get current day name using PLSQL and assigning it to variable
Code
CREATE OR REPLACE PROCEDURE ABC_STORE
(
v_day varchar2
)
AS
BEGIN
select to_char(CURRENT_TIMESTAMP,'DAY') into v_day from dual;
DBMS_OUTPUT.PUT_LINE(v_day);
END;
Error :
You should add al least clause OUT for value v_day:
create or replace PROCEDURE ABC_STORE
(
v_day IN OUT varchar2
)
AS
BEGIN
select to_char(CURRENT_TIMESTAMP,'DAY') into v_day from dual;
DBMS_OUTPUT.PUT_LINE(v_day);
END;
Thank you
You don't need the query, because PL/SQL has an assignment operator :=.
You also don't need dbms_output if the requirement is just to bring back the day name.
Also, the 'DAY' format element includes blank padding by default (e.g. SUNDAY ), so 'fmDAY' provides the expected result SUNDAY. If it doesn't need to be all capitals, you can use 'fmDay'.
create or replace procedure day_name
( v_day out varchar2 )
as
begin
v_day := to_char(sysdate,'fmDay');
end day_name;
Just to add, if you are checking the day name as part of some business logic (for example, a batch step should not run at the weekend), you will also need to specify a date language to avoid the situation where you are checking for 'Sunday' and the procedure is called from a support desk in Paris and returns 'Dimanche'. You would do this using (for example - substitute any language you want)
to_char(sysdate,'fmDay','nls_date_language=English')
(Of course, if you were just checking for the weekend you would only need to get the English three letter abbreviation and check whether it's like 'S%', but this is not what you asked for. I mention it because I have seen production errors caused by poorly handled day name checks, and as a result I use it as an interview question, which it turns out very few candidates can answer.)

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

Identifying the Weekday for an Order Date in PL/SQL as stored function

Code:
CREATE OR REPLACE FUNCTION DAY_ORD_SF
(
P_DATE_CREATED IN bb_basket.dtcreated%type
)
RETURN DATE AS
lv_date_created bb_basket.dtcreated%type;
BEGIN
SELECT to_char(to_date(P_DATE_CREATED,'yyyy-mm-dd'),'DAY') DAY_CREATED
INTO lv_date_created
FROM BB_BASKET
WHERE lv_date_created <= sysdate
ORDER BY lv_date_created ASC;
RETURN lv_date_created;
END DAY_ORD_SF;
/
SELECT IDBASKET, dtcreated date_created, to_char(DTCREATED,'DAY') DAY_CREATED, day_ord_sf(dtcreated) weekday_created
FROM BB_BASKET
order by DTCREATED asc;
This is my stored function I am working on as a task to practice stored function. I am really close to finishing this problem, but I am getting a no data found error. I'm not really understanding this error because when I run the code by itself it works. Basically this function is suppose to taking a date and return a varchar2 data type. I did have the "to_date(…,'yyyy-mm-dd'),.." gone before adding at that piece of code into the function.
First part of this task is to create a SELECT statement that lists the basket ID and weekday for every basket, and the second part of the task is to create a SELECT statement, using a GROUP BY clause to list the total number of baskets per weekday. Based on the results, what’s the most popular shopping day? Also I forgot to ask, if you can tell me why I am getting the "No data found" error that would be much appreciated!
Thanks for helping me out!
Well, there seems to be mutiple issues here.
Lets start with no data found problem. In case you use INTO within select statment and select returns no rows, you get an no data found exception. This can be handled via anonymouse begin end block with exception handler inside, but I believe this is not the case. As example:
declare
v_value number;
begin
select null
into v_value
from dual
where 1=2;
exception
when NO_DATA_FOUND then
null; -- Ignore exception and continue
end;
Condition 1=2 is never met and therefore the select always returns no rows, this will always produce no data found error. With exception handler we decide what to do next. Null will do nothing in this example.
Back to your function, your condition is if variable lv_date_created is less or equal to current date then do something. This will never work as lv_date_created will be at the moment of execution equal to null. It was just declared in your function and then used in your select. This will always lead to false and therefore select will always return no rows and no data found exception.
You also mentioned that you want the function to return varchar2 but your definition sais it returns date. Also variable lv_date_created is of type date and this is variable that is returned and you fill it with varchar2 value so apparently Oracle is doing some uncontroled conversion of the value of this is not throwing an exception about expected datatypes.
Also condition lv_date_created <= sysdate may indicate that select will find mutiple values and with INTO, this will cause too many rows exception if mutiple rows are found.
Now lets get to fixing this function. The main question is if you need to slect something in it or not. Selecting something like that usualy is used to check if entry in table exists and no data found exceptions would tell us that entry does not exist. If it is a general function to be used on mutiple places in your DB then I think select is not needed. I will include both solutions as example.
Note for this first one that you should no longer provide dtcreated colume on calling it but idbasket.
CREATE OR REPLACE FUNCTION DAY_ORD_SF
(
P_IDBASKET IN bb_basket.idbasket%type
)
RETURN VARCHAR2 AS
lv_date_created VARCHAR2(240);
BEGIN
SELECT to_char(dtcreated,'DAY') DAY_CREATED
INTO lv_date_created
FROM BB_BASKET
WHERE IDBASKET = P_IDBASKET;
RETURN lv_date_created;
END DAY_ORD_SF;
/
Or
CREATE OR REPLACE FUNCTION DAY_ORD_SF
(
P_DATE_CREATED IN bb_basket.dtcreated%type
)
RETURN VARCHAR2 AS
BEGIN
RETURN to_char(P_DATE_CREATED,'DAY');
END DAY_ORD_SF;
/

How do I use a date as a variable in a postgreSQL function?

I'm trying to write a function in postgreSQL 9.5 that takes a date as a parameter. My table has a column called inception_date, and I want the function to return all rows from the table where inception_date is greater than the date provided as the variable. Below is my function:
CREATE OR REPLACE FUNCTION somefunc(date) RETURNS setof table AS
$BODY$
DECLARE variable ALIAS FOR $1;
BEGIN
RETURN QUERY SELECT * FROM table WHERE inception_date > variable;
END;
$BODY$
LANGUAGE 'plpgsql';
SELECT somefunc('2014-07-02');
I haven't been able to find any info saying dates are handled differently in posgreSQL functions than other datatypes, but this function doesn't display any output, while the query
SELECT * FROM table WHERE inception_date > '2014-07-02';
returns 15 rows. Does anyone know what might be going wrong here?
What is going here, You interpret Your code differently from PostgreSQL parser. For You '2014-07-02' is a date. For parser this is a srting. But he tries to be nice and when it see You use it for filtering a date column, it tries to interpret it as a date.
But given '2014-07-02', did You mean seventh day of february, or second day of july? He does not know it (and I do not know either). So, basically this is error on Your side and You should not do it this way.
What I would propose, is to create function with named parameter of date type, and use it as date. Then, when invoking this function, and having string as parameter, I would turn this string to date, telling PostgreSQL what I mean.
Examples and references to manual below.
CREATE OR REPLACE FUNCTION somefunc(p_date date)
RETURNS setof table
AS
$BODY$
BEGIN
RETURN QUERY SELECT * FROM table WHERE inception_date > p_date;
END;
$BODY$
LANGUAGE 'plpgsql';
SELECT somefunc(to_date('2014-07-02','YYYY-MM-DD'));
Here You can find more information on date formatting and creating of functions.
I have this 'pattern' for using pseudo variables containing dates. It does NOT rely on a function.
I like using the WITH() CTE for this purpose:
WITH myvar (enddate) AS (VALUES(to_date('2020-06-26', 'YYYY-MM-DD')))
SELECT 'Total downloaded records', sum(d.total_records) FROM download_table d
JOIN myvar m ON date(d.created) BETWEEN '2020-01-01' AND m.enddate
Please notice that the WITH Common Table Expression contains the date cast to a date format. You need this because you need to compare between date types and not date <= text.

Setting up a stored procedure in Oracle

I'm working to create a stored procedure that takes input of an id and a start and end date, then returns trips that fall within that range. I've been looking over the oracle documentation and I think I'm close, but getting a few errors yet:
CREATE or replace PROCEDURE chg_per_aircraft
(p_aircraft_id IN RCC_AIRCRAFT.aircraft_id,
p_start_date IN date,
p_end_date IN date,
p_ttl_chg_per_acft OUT INTEGER)
AS
BEGIN
SELECT RCC_AIRCRAFT.aircraft_id,
SUM(RCC_CHARTER.distance * RCC_MODEL.charge_per_mile) ttl_chg
INTO
p_aircraft_id,
p_ttl_chg_per_acft
FROM RCC_AIRCRAFT
full join RCC_CHARTER
on RCC_CHARTER.aircraft_id = RCC_AIRCRAFT.aircraft_id
left join RCC_MODEL
on RCC_MODEL.model_code = RCC_AIRCRAFT.model_code
Where RCC_CHARTER.trip_date > p_start_date and RCC_CHARTER.trip_date < p_end_date
group by RCC_AIRCRAFT.aircraft_id;
SYS.DBMS_OUTPUT.PUT_LINE(ttl_chg);
end;
Your first error is the parameter definition:
p_aircraft_id IN RCC_AIRCRAFT.aircraft_id
should be
p_aircraft_id IN RCC_AIRCRAFT.aircraft_id%TYPE
But then you're selecting INTO p_aircraft_id, which is declared as an IN parameter, so you can't set it to a new value. Is that a variable you want to pass in, or a value you want to get out? It makes more sense as something the caller supplies along with the dates, but then you'd need to use it as a filter in the select statement. If there was more than one aircraft ID - likely if it's only restricted by date - then you'd get multiple results back, which would be a too_many_rows error anyway.
Your output will only be visible to a session that is set up to handle it, so that would perhaps make more sense for the caller to do; but in any case should be:
DBMS_OUTPUT.PUT_LINE(p_ttl_chg_per_acft);
... as ttl_chg only exists as a column alias, not a PL/SQL variable.
If you are passing in the aircraft ID, you might want something like this:
CREATE or replace PROCEDURE chg_per_aircraft
(p_aircraft_id IN RCC_AIRCRAFT.aircraft_id%TYPE,
p_start_date IN date,
p_end_date IN date,
p_ttl_chg_per_acft OUT INTEGER)
AS
BEGIN
SELECT SUM(RCC_CHARTER.distance * RCC_MODEL.charge_per_mile) ttl_chg
INTO p_ttl_chg_per_acft
FROM RCC_AIRCRAFT
JOIN RCC_CHARTER
ON RCC_CHARTER.aircraft_id = RCC_AIRCRAFT.aircraft_id
JOIN RCC_MODEL
ON RCC_MODEL.model_code = RCC_AIRCRAFT.model_code
WHERE RCC_CHARTER.trip_date > p_start_date
AND RCC_CHARTER.trip_date < p_end_date
AND RCC_AIRCRAFT.aircraft_id = p_aircraft_id
GROUP BY RCC_AIRCRAFT.aircraft_id;
-- just to debug!
DBMS_OUTPUT.PUT_LINE(p_ttl_chg_per_acft);
END;
/
I've also changed to inner joins as it doesn't seem useful to make them outer joins. This would also make more sense as a function than a procedure; though wrapping a single query in a stored program may be unnecessary anyway - though this looks like an assignment.