I am new to PL/SQL and don't understand how to declare date variables. How would I write this T-SQL script in PL/SQL?
declare #date1 as date
declare #date2 as date
set #date1 = '2022-06-01'
set #date2 = '2022-06-30'
select *
from example_table
where example_date between #date1 and #date2
Declaring variables is easy.
Problem comes when you want to do something with the select statement as PL/SQL requires you to select INTO something (a local variable, set of local variables, ref cursor, collection, ...). You didn't say what you'd want to do afterwards, so the following example
selects number of rows that satisfy condition
loops through the table and does nothing (null;) for each loop iteration.
You'd do something else, I presume.
declare
date1 date := date '2022-06-01';
date2 date := date '2022-06-30';
l_cnt number;
begin
select count(*)
into l_cnt
from example_table
where example_date between date1 and date2;
for cur_r in (select *
from example_table
where example_date between date1 and date2
)
loop
null;
end loop;
end;
/
I would like to declare some variables that contain the first and last date of the current month in Oracle. I know how to get these values, but evidently not store or use them; I am more of a T-SQL guy.
In T-SQL, I could write:
DECLARE #startDate DATE = GETDATE();
DECLARE #endDate DATE = EOMONTH(GETDATE());
SELECT *
FROM SomeTable
WHERE SomeDate BETWEEN #startDate AND #endDate;
I cannot, for the life of me, work out how to do this in Oracle. I have tried several variations, like:
DECLARE END_DT DATE := TRUNC(LAST_DAY(SYSDATE))
END_DT DATE := TRUNC(LAST_DAY(SYSDATE))
DECLARE END_DT DATE;
SELECT TRUNC(LAST_DAY(SYSDATE)) INTO END_DT FROM DUAL;
I am mostly using 11g, but I would like to be able to use the same script on a 9i/10g server also.
You can use such a query to detect first and last days of the current month :
SELECT TRUNC(LAST_DAY(ADD_MONTHS(SYSDATE,-1)))+1,
TRUNC(LAST_DAY(SYSDATE))
INTO :startDate,:endDate
FROM DUAL;
with the contribution of ADD_MONTHS() function
Update : Alternatively use this PL/SQL code block :
DECLARE
startDate date;
endDate date;
BEGIN
startDate := TRUNC(LAST_DAY(ADD_MONTHS(SYSDATE,-1)))+1;
endDate := TRUNC(LAST_DAY(SYSDATE));
DBMS_OUTPUT.PUT_LINE ('startDate : '||startDate);
DBMS_OUTPUT.PUT_LINE ('endDate : '||endDate);
END;
/
Demo
You can use the LAST_DAY and TRUNC combination as following:
DECLARE
START_DT DATE := TRUNC(SYSDATE, 'MM');
END_DT DATE := TRUNC(LAST_DAY(SYSDATE));
BEGIN
--DBMS_OUTPUT.PUT_LINE(START_DT || ' ' || END_DT);
SELECT
*
INTO <...>
FROM
SOMETABLE
WHERE
SOMEDATE BETWEEN START_DT AND END_DT;
END;
/
Cheers!!
I'm very new to plsql and am having a bit of trouble with one of my assignments. I've been asked to create a date dimension table and populate it with various date information.
I created my table as follows
CREATE TABLE date_dimension_table
(
v_date DATE NOT NULL,
full_date VARCHAR2(50) NOT NULL,
day_of_week NUMBER(1,0) NOT NULL,
day_in_month NUMBER(2,0) NOT NULL,
day_in_year NUMBER(3,0) NOT NULL,
last_day_of_week_flag VARCHAR2(1) NOT NULL,
last_day_of_month_flag VARCHAR2(1) NOT NULL,
end_of_week_date DATE NOT NULL,
month_number NUMBER(2,0) NOT NULL,
month_name VARCHAR2(32) NOT NULL,
year_month CHAR(32) NOT NULL,
quarter_number NUMBER(1,0) NOT NULL,
year_and_quarter VARCHAR2(32) NOT NULL,
v_year NUMBER(4,0) NOT NULL,
CONSTRAINT date_dimension_table_PK PRIMARY KEY (v_date)
);
I then created the following procedure to populate the table.
create or replace PROCEDURE sp_populate_dates(
v_first_date_in DATE,
v_second_date_in DATE)
AS
--Declare two variables as DATE datatypes
v_current_date DATE;
v_end_date DATE;
BEGIN
--Assign the start date and end date to the variables
v_current_date := TO_DATE(v_first_date_in, 'DDMMYYYY');
v_end_date := TO_DATE(v_second_date_in, 'DDMMYYYY');
--Delete whats currently in the table
DELETE FROM date_dimension_table;
--condition checks if the first date is before the second date
WHILE v_current_date <= v_end_date
LOOP
--insert into table
INSERT INTO date_dimension_table
(
v_date,
full_date,
day_of_week,
day_in_month,
day_in_year,
last_day_of_week_flag,
last_day_of_month_flag,
end_of_week_date,
month_number,
month_name,
year_month,
quarter_number,
year_and_quarter,
v_year
)
VALUES
(
v_current_date, --date
TO_CHAR(v_current_date, 'Day, Month DD, YYYY'), --full date
TO_NUMBER(TO_CHAR(v_current_date, 'D')) -1, --day in week
TO_CHAR(v_current_date,'DD'), --day in month
TO_CHAR(v_current_date,'DDD'), --day in year
CASE --last day of week flag
WHEN TO_CHAR(v_current_date,'FMDay') = 'Sunday' THEN 'Y'
ELSE 'N'
END,
CASE --last day of month flag
WHEN last_day(TO_DATE(v_current_date, 'MM/DD/YYYY')) = TO_DATE(v_current_date, 'MM/DD/YYYY') THEN 'Y'
ELSE 'N'
END,
CASE --date of next sunday
WHEN TO_CHAR(v_current_date,'FMDay') = 'Sunday' THEN v_current_date
ELSE NEXT_DAY(v_current_date,'SUNDAY')
END,
TO_CHAR(v_current_date,'MM'), --month number
TO_CHAR(v_current_date,'MONTH'), --name of month
TO_CHAR(v_current_date,'MONTH YYYY'), --month and year
TO_CHAR(v_current_date,'Q'), --number of quarter
TO_CHAR(v_current_date,'YYYY Q'), --year and quarter
TO_CHAR(v_current_date,'YYYY') --year
);
--Increment and assign the current date value to be re-evaluated
v_current_date := v_current_date + 1;
END LOOP;
END;
As the program has to insert information from a range of dates, I was using two dates as the input parameters and it was throwing up an error.
Example:
BEGIN
sp_populate_dates(01/01/2005, 01/01/2006);
END;
I was then getting errors suggesting it was the wrong type of argument to call in the function. I was wondering if anyone could help with this please, and show me where I'm going wrong. Cheers
First of all, you have procedure that get
v_first_date_in DATE,
v_second_date_in DATE
DATEs, but you're trying to pass even not a string. Try something like this
BEGIN
sp_populate_dates(TO_DATE('01/01/2005', 'dd/mm/yyyy'), TO_DATE('01/01/2005', 'dd/mm/yyyy'));
END;
another thing is - you process these parameters in the procedure, so you probably wanted to pass varchar2 type variables. That means,
create or replace PROCEDURE sp_populate_dates(
v_first_date_in IN VARCHAR2,
v_second_date_in IN VARCHAR2)
...
and call it like
BEGIN
sp_populate_dates('01/01/2005','01/01/2005');
END;
However be careful here: if type of date here is ('dd/mm/yyyy'), your processing date in the procedure ('ddmmyyyy') is not correct.
Hope it will help!
If you don't really need any date formatting and parsing, always use the SQL standard DATE literal:
BEGIN
sp_populate_dates(DATE '2005-01-01', DATE '2006-01-01');
END;
You can populate such a table (for the dates between 2000-2030) by using the following query:
CREATE TABLE DIM_DATE
as
(
select
TO_NUMBER(TO_CHAR(DATE_D,'j')) as DATE_J,
DATE_D,
TO_CHAR(DATE_D,'YYYY-MM-DD') as DATE_V,
TO_NUMBER(TO_CHAR(DATE_D,'YYYY')) as YEAR_NUM,
TO_NUMBER(TO_CHAR(DATE_D,'Q')) as QUARTER_NUM,
TO_NUMBER(TO_CHAR(DATE_D,'MM')) as MONTH_NUM,
TRIM(TO_CHAR(DATE_D,'Month','nls_date_language=english')) as MONTH_DESC,
TO_NUMBER(TO_CHAR(DATE_D,'IW')) as ISO_WEEK_NUM,
TO_NUMBER(TO_CHAR(DATE_D,'IYYY')) as ISO_YEAR_NUM,
TO_NUMBER(TO_CHAR(DATE_D,'DD')) as DAY_OF_MONTH_NUM,
TO_NUMBER(TO_CHAR(DATE_D,'D')) as DAY_OF_WEEK_NUM,
TRIM(TO_CHAR(DATE_D,'Day','nls_date_language=english')) as DAY_OF_WEEK_DESC,
(CASE WHEN TRIM(TO_CHAR(DATE_D,'Day','nls_date_language=english')) IN ('Saturday','Sunday') THEN 'Weekend' ELSE 'Weekday' END) as DAY_TYPE_DESC
from
(
select
to_date('1999-12-31','YYYY-MM-DD')+ROWNUM as DATE_D
from
dual
connect by level <= to_date('2029-12-31','YYYY-MM-DD')-to_date('1999-12-31','YYYY-MM-DD')
)
);
I have a PostgreSQL function which calculates date difference:
CREATE OR REPLACE FUNCTION testDateDiff () RETURNS int AS $BODY$
DECLARE startDate TIMESTAMP;
DECLARE endDate TIMESTAMP;
DECLARE diffDatePart int ;
BEGIN
Select evt_start_date From events Where evt_id = 5 INTO startDate ;
Select evt_start_date From events Where evt_id = 6 INTO endDate ;
SELECT EXTRACT(day FROM TIMESTAMP startDate - endDate) INTO diffDatePart;
RETURN diffDatePart;
END;
$BODY$
LANGUAGE plpgsql
COST 100
If dates are subtracted directly then difference is calculated. But in my case dates are present in variables as startDate and endDate, which causes the problem.
How can I subtract dates contained in variables?
Debug
What your function is doing could be done much simpler. The actual cause for the syntax error is here:
SELECT EXTRACT(day FROM TIMESTAMP startDate - endDate) INTO diffDatePart;
It looks like you are trying to cast startDate to timestamp, which is nonsense to begin with, because your parameter startDate is declared as timestamp already.
It also does not work. I quote the manual here:
To avoid syntactic ambiguity, the type 'string' syntax can only be
used to specify the type of a simple literal constant.
It would work like this:
SELECT EXTRACT(day FROM startDate - endDate)::int INTO diffDatePart;
But that still wouldn't make a lot of sense. You are talking about "dates", but still define your parameters as timestamp. You could sanitize what you have like this:
CREATE OR REPLACE FUNCTION f_date_diff()
RETURNS int AS
$BODY$
DECLARE
start_date date;
end_date date;
date_diff int;
BEGIN
SELECT evt_start_date FROM events WHERE evt_id = 5 INTO start_date;
SELECT evt_start_date FROM events WHERE evt_id = 6 INTO end_date;
date_diff := (endDate - startDate);
RETURN date_diff;
END
$BODY$ LANGUAGE plpgsql;
DECLARE only needed once.
date columns declared as proper type date.
Don't use mixed case identifiers, unless you know exactly what you are doing.
Subtract the start from the end to get a positive number or apply the absolute value operator #.
Since subtracting dates (as opposed to subtracting timestamps, which yields an interval) already yields integer, simplify to:
SELECT (startDate - endDate) INTO diffDatePart;
Or even simpler as plpgsql assignment:
diffDatePart := (startDate - endDate);
Simple query
You can solve the simple task with a simple query - using a subquery:
SELECT (SELECT evt_start_date
FROM events
WHERE evt_id = 6)
- evt_start_date AS date_diff
FROM events
WHERE evt_id = 5;
Or you could CROSS JOIN the base table to itself (1 row from each instance, so that's ok):
SELECT e.evt_start_date - s.evt_start_date AS date_diff
FROM events e
,events s
WHERE e.evt_id = 6
AND s.evt_id = 5;
SQL function
If you insist on a function for the purpose, use a simple sql function:
CREATE OR REPLACE FUNCTION f_date_diff(_start_id int, _end_id int)
RETURNS int LANGUAGE sql AS
$func$
SELECT e.evt_start_date - s.evt_start_date
FROM events s, events e
WHERE s.evt_id = $1
AND e.evt_id = $2
$func$;
Call:
SELECT f_date_diff(5, 6);
PL/pgSQL function
If you insist on plpgsql ...
CREATE OR REPLACE FUNCTION f_date_diff(_start_id int, _end_id int)
RETURNS int LANGUAGE plpgsql AS
$func$
BEGIN
RETURN (SELECT evt_start_date
- (SELECT evt_start_date FROM events WHERE evt_id = _start_id)
FROM events WHERE evt_id = _end_id);
END
$func$;
Same call.
I would write the query like this:
create function testDateDiff()
returns integer as $$
declare
startDate timestamp;
endDate timestamp;
begin
startDate := (select evt_start_date From events Where evt_id = 5);
endDate := (select evt_start_date From events Where evt_id = 6);
return (select extract(day from startDate - endDate));
end;
$$ language 'plpgsql';
The difference between using := and into in the context above is that using := your query must return a single value. If you use into your query can return a single row (i.e. more than one column).
For a full explanation of using select with into and plpgsql you should read http://www.postgresql.org/docs/9.1/static/plpgsql-statements.html. Specifically, section 39.5.3 of the PostgreSQL documentation.
Do you really need a function for this?
This query would work as well:
SELECT (SELECT evt_start_date::date FROM events WHERE evt_id = 5)
- evt_start_date::date
FROM events WHERE evt_id = 6;
Given 2 dates (StartDate and EndDate), how to do i generate quarterly periods in Pl/SQL.
Example:
Start Date: 01-JAN-2009
End Date: 31-DEC-2009
Expected Output:
StartDate EndDate
01-JAN-2009 31-MAR-2009
01-APR-2009 30-JUN-2009
01-JUL-2009 30-SEP-2009
01-OCT-2009 31-DEC-2009
SELECT ADD_MONTHS( TRUNC(PARAM.start_date, 'Q'), 3*(LEVEL-1) ) AS qstart
, ADD_MONTHS( TRUNC(PARAM.start_date, 'Q'), 3*(LEVEL) ) -1 AS qend
FROM ( SELECT TO_DATE('&start_date') AS start_date
, TO_DATE('&end_date') AS end_date
FROM DUAL
) PARAM
CONNECT BY ADD_MONTHS( TRUNC(PARAM.start_date, 'Q'), 3*(LEVEL) ) -1
<= PARAM.end_date
Rules for params, you may need to adjust the query to suit your purposes:
If start_date is not exact quarter start it effectively uses the quarter contain start date.
If end_date is not exact quarter end then we end on the quarter that ended BEFORE end_date (not the one containing end date).
Here's one way that you can do it with PL/SQL
declare
startDate Date := '01-JAN-2009';
endDate Date := '31-DEC-2009';
totalQuarters number := 0;
begin
totalQuarters := round(months_between(endDate, startDate),0)/3;
dbms_output.put_line ('total quarters: ' || totalQuarters);
for i in 1..totalQuarters loop
dbms_output.put_line('start date: '|| startDate || ' end date:' || add_months(startDate -1,3));
startDate := add_months(startDate,3) ;
end loop;
end;