Get first (and last) day of month - expression simplification - sql

I need to get date, which was 30 (later I will use also 90 or 18O) days before first day of current (and previous) month. I wrote this, but I think it's unnecessarily complicated, so I come here for help. It's any way how can I simplify this:
ib_encodedate(EXTRACT(YEAR from (dateadd(-30-datediff(day from cast(EXTRACT(MONTH from CURRENT_DATE)
|| '-1-'
|| EXTRACT(YEAR from CURRENT_DATE) as date) to date 'now') DAY to CURRENT_DATE))), EXTRACT(MONTH from (dateadd(-30-datediff(day from cast(EXTRACT(MONTH from CURRENT_DATE)
|| '-1-'
|| EXTRACT(YEAR from CURRENT_DATE) as date) to date 'now') DAY to CURRENT_DATE))),EXTRACT(DAY from (dateadd(-30-datediff(day from cast(EXTRACT(MONTH from CURRENT_DATE)
|| '-1-'
|| EXTRACT(YEAR from CURRENT_DATE) as date) to date 'now') DAY to CURRENT_DATE))))
In database I have dates in double, so I have to use function ib_encodedate to convert date to double and compare with date in database. Function have prototype:
ib_encodedate(INT year, INT month, INT day)
The same I need to write for last day of month.
Thanks for any help.

Seems like you need something like this:
SELECT
DATEADD (-EXTRACT(DAY FROM CURRENT_DATE)+1 DAY TO CURRENT_DATE) AS FIRST_DAY_OF_MONTH,
DATEADD (-30 DAY TO DATEADD (-EXTRACT(DAY FROM CURRENT_DATE)+1 DAY TO CURRENT_DATE)) AS A_MONTH_AGO,
DATEADD (-90 DAY TO DATEADD (-EXTRACT(DAY FROM CURRENT_DATE)+1 DAY TO CURRENT_DATE)) AS THREE_MONTHS_AGO,
DATEADD (-180 DAY TO DATEADD (-EXTRACT(DAY FROM CURRENT_DATE)+1 DAY TO CURRENT_DATE)) AS SIX_MONTHS_AGO
FROM
RDB$DATABASE
Using the function DATEADD from firebird you can easily accomplish this.

First Day of The Date:
select cast(:DATA as date) - extract(day from cast(:DATA as date)) + 1 from RDB$DATABASE
Last Day of The Date:
select cast(:DATA as date) - extract(day from cast(:DATA as date)) + 32 - extract(day from (cast(:DATA as date) - extract(day from cast(:DATA as date)) + 32)) from RDB$DATABASE

Using Firebird 2.5
First Day:
SELECT CAST(EXTRACT(YEAR FROM CURRENT_DATE)||'-'||EXTRACT(MONTH FROM
CURRENT_DATE)||'-01' AS DATE) FROM RDB$DATABASE
Last Day:
SELECT DATEADD (-1 DAY TO DATEADD (1 MONTH TO CAST(EXTRACT(YEAR FROM
CURRENT_DATE)||'-'||EXTRACT(MONTH FROM CURRENT_DATE)||'-01' AS DATE)))
FROM RDB$DATABASE

If you use firebird 1.5 you can create this function which returns the last day of the month of a specified date:
create or alter procedure LAST_DAY_OF_MONTH (
REFERENCE_DATE date)
returns (
LAST_DAY date)
as
declare variable NEXT_DAY date;
declare variable COUNTER integer;
begin
NEXT_DAY = :REFERENCE_DATE;
COUNTER = 0;
while (:COUNTER < 33) do begin
NEXT_DAY = :NEXT_DAY + 1;
if (extract(month from :NEXT_DAY) = extract(month from :REFERENCE_DATE)) then begin
LAST_DAY = :NEXT_DAY;
end
COUNTER = :COUNTER + 1;
end
suspend;
end
Then to call it just execute:
select last_day from last_day_of_month('02-23-2016')
This will return 02-29-2016

select dateadd (month, -1,dateadd (-extract(day from current_date)+1 day to current_date)) First_day_month_ago
from RDB$DATABASE
union all
select dateadd (-extract(day from current_date)+1 day to current_date) First_day_current_month
from RDB$DATABASE

Migrating from MySQL I needed the FIRST_DAY and LAST_DAY functions. Here they are:
CREATE OR ALTER FUNCTION FIRST_DAY(d TIMESTAMP)
RETURNS DATE
AS
BEGIN
RETURN CAST(d - EXTRACT(DAY FROM d) + 1 AS DATE);
END
and
CREATE OR ALTER FUNCTION LAST_DAY(d TIMESTAMP)
RETURNS DATE
AS
BEGIN
RETURN DATEADD(1 MONTH TO FIRST_DAY(d)) - 1;
END
And executing from isql:
SQL> SELECT first_day(CURRENT_TIMESTAMP), last_day(CURRENT_TIMESTAMP) FROM RDB$DATABASE rd;
FIRST_DAY LAST_DAY
=========== ===========
2020-08-01 2020-08-31

but creating a function like this:
create or alter function F_DAYSOFMONTH (
LMONTH integer,
LYEAR integer)
returns integer
as
declare variable LTMPDT DM_DATA;
declare variable LTMPDT2 timestamp;
begin
SELECT CAST(CAST(:lmonth AS VARCHAR(10)) || '/01/' || CAST(:lyear AS VARCHAR(10)) AS TIMESTAMP) FROM RDB$DATABASE INTO :LTMPDT;
LTMPDT2 = dateadd(1 month TO :LTMPDT);
return datediff(DAY FROM :ltmpdt TO :LTMPDT2);;
end^
SET TERM ; ^

CREATE PROCEDURE FIRST_LAST_DAYOFMONTH(COMPARE_DATE TIMESTAMP)
RETURNS (FIRST_DAYOFOMONTH TIMESTAMP,LAST_DAYOFMONTH TIMESTAM)
AS
BEGIN
FIRST_DAYOFMONTH=COMPARE_DATE-(EXTRACT(DAY FROM :COMPARE_DATE)-1);
LAST_DAYOFMONTH=DATEADD(1 MONT TO :COMPARE_DATE)-1;
SUSPEND;
END^

Related

How to get the next business date in postgres?

Business days are Monday through Friday.
Given I have a datetime field scheduled_for, how can I find the next business date and return that in a column alias?
I've tried something from another SO answer but it doesn't work as intended.
EXTRACT(ISODOW FROM v.scheduled_for)::integer) % 7 as next_business_day,
Error:
Query 1 ERROR: ERROR: syntax error at or near ")"
LINE 3: EXTRACT(ISODOW FROM v.scheduled_for)::integer % 7) as next...
^
Edit:
Thanks for the suggestions, I've attempted this:
SELECT
v.id AS visit_id,
(IF extract(''dow'' from v.scheduled_for) = 0 THEN
return v.scheduled_for + 1::integer;
ELSIF extract(''dow'' from v.scheduled_for) = 6 THEN
return v.scheduled_for - 1::integer;
ELSE
return v.scheduled_for;
) as next_business_day,
'' as invoice_ref_code,
The error I get is:
Query 1 ERROR: ERROR: syntax error at or near ")"
LINE 1: ) as next_business_day,
^
To generalize you need to create a function to calculate the next business day from a given date.
create or replace function utl_next_business_day(date_in date default current_date)
returns date
language sql immutable leakproof strict
as $$
with cd as (select extract(isodow from date_in)::integer d)
select case when d between 1 and 4
then date_in + 1
else date_in + 1 + (7-d)
end
from cd;
$$;
--- any single date
select current_date, utl_next_business_day();
-- over time span (short)
select gdate::date for_date, utl_next_business_day(gdate::date) next_business_day
from generate_series( current_date, current_date + 14, interval '1 day') gdate;
-- around year end over a time span
with test_date (dt) as
( values (date '2019-12-31')
, (date '2020-12-31'), (date '2021-12-31'),(date '2022-12-31')
, (date '2021-01-01'), (date '2022-01-01'),(date '2023-01-01')
)
select dt, utl_next_business_day(dt) from test_date
order by dt;
Alternatively with the calendar table suggestion from #Eric we get.
-- create and populate work table
create table bus_day_calendar ( bus_day date);
insert into bus_day_calendar (bus_day)
select utl_next_business_day(gdate::date)
from generate_series( date '2018-12-31', date '2023-01-01', interval '1 day') gdate
where extract(isodow from gdate)::integer not in (6,7) ;
--- Function to return next business day
create or replace function utl_next_cal_business_day(date_in date default current_date)
returns date
language sql stable leakproof strict
as $$
select min(bus_day)
from bus_day_calendar
where bus_day > date_in;
$$;
--- any single date
select current_date, utl_next_cal_business_day();
-- over time span (short)
select gdate::date for_date, utl_next_cal_business_day(gdate::date) next_business_day
from generate_series( current_date, current_date + 14, interval '1 day') gdate;
-- around year end over a time span
with test_date (dt) as
( values (date '2019-12-31')
, (date '2020-12-31'), (date '2021-12-31'),(date '2022-12-31')
, (date '2021-01-01'), (date '2022-01-01'),(date '2023-01-01')
)
select dt, utl_next_cal_business_day(dt) from test_date
order by dt;
Neither of these as they currently stand handle a non-business day that falls on Mon-Fri, but both can be modified to do so. Since the calendar table requires only deleting roes I think that becomes the superior method if this is necessary.

Calculate age (year, month, day) using DateDiff in stored procedure

I need to show the time that has passed since a specific date in years, months and days, taking leap years/months into account. This has to be within a stored producedure in Firebird 2.1.
I'm struggling with the following code:
SELECT CASE
WHEN :CustomDateTime > CURRENT_DATE THEN 0
WHEN EXTRACT(YEARDAY FROM :CustomDateTime) > EXTRACT(YEARDAY FROM CURRENT_DATE) THEN DATEDIFF(YEAR, :CustomDateTime, CURRENT_DATE) - 1
ELSE DATEDIFF(YEAR, :CustomDateTime, CURRENT_DATE)
END
FROM RDB$DATABASE
INTO :YearDiff;
SELECT CASE
WHEN :CustomDateTime > CURRENT_DATE THEN 0
WHEN (EXTRACT(MONTH FROM :CustomDateTime) = EXTRACT(MONTH FROM CURRENT_DATE))
AND (EXTRACT(YEARDAY FROM :CustomDateTime) > EXTRACT(YEARDAY FROM CURRENT_DATE))
THEN DATEDIFF(MONTH, DATEADD(:YearDiff YEAR TO :CustomDateTime), CURRENT_DATE) - 1
ELSE DATEDIFF(MONTH, DATEADD(:YearDiff YEAR TO :CustomDateTime), CURRENT_DATE)
END
FROM RDB$DATABASE
INTO :MonthDiff;
SELECT CASE
WHEN :CustomDateTime > CURRENT_DATE THEN 0
WHEN (:YearDiff > 0) AND (:MonthDiff > 0) THEN DATEDIFF(DAY, DATEADD(:MonthDiff MONTH TO DATEADD(:YearDiff YEAR TO :CustomDateTime)), CURRENT_DATE)
WHEN (:YearDiff > 0) AND (:MonthDiff = 0) THEN DATEDIFF(DAY, DATEADD(:YearDiff YEAR TO :CustomDateTime), CURRENT_DATE)
WHEN (:YearDiff = 0) AND (:MonthDiff > 0) THEN DATEDIFF(DAY, DATEADD(:MonthDiff MONTH TO :CustomDateTime), CURRENT_DATE)
ELSE DATEDIFF(DAY, :CustomDateTime, CURRENT_DATE)
END
FROM RDB$DATABASE
INTO :DayDiff;
Any ideas on how to fix it?
This is in pl/sql, but you can use this idea for finding the time passed since v_date;
set serveroutput on;
declare
v_date date := '27-APR-16';
PROCEDURE passed(toCheck date) IS
years INTEGER := 0;
months INTEGER := 0;
days INTEGER := 0;
BEGIN
SELECT floor(MONTHS_BETWEEN(sysdate, toCheck)) into months FROM dual;
SELECT SYSDATE - ADD_MONTHS(toCheck, months) INTO days FROM dual;
years := floor(months / 12);
months := months - (years * 12);
dbms_output.put_line('Yers : ' || years || ', ' || ' Months : ' || months || ', Days: ' || days);
END passed;
begin
passed(v_date);
end;

How to get week start date from week number in postgresql?

I have all week numbers from 20161 to 201640. I want to know what is the start date and end date of week 31.
How can I write query in postgresql to get that?
To get the start date, simply convert the week number to a date using to_date()
If you are using ISO week numbers use:
select to_date('201643', 'iyyyiw');
Otherwise use:
select to_date('201643', 'yyyyww');
To get the end date, just add 7 to resulting date: to_date('201643', 'iyyyiw') + 7
SELECT date '2016-01-01' + interval '1 day' * ((7 - EXTRACT(DOW FROM DATE '2016-01-01')) + 29*7) AS start_date,
date '2016-01-01' + interval '1 day' * ((7 - EXTRACT(DOW FROM DATE '2016-01-01')) + 29*7 + 6) AS end_date,
Dealing with weeks may be tricky if you allow the first and last weeks of the year to be less than 7 days long.
This is my two cents using ISO weeks (first day is monday, with dow = 1). They return the first and last date of a week out of the year and the week index.
Notice a year has 365/7 = 52,142857142857143 weeks, or 366/7 = 52,285714285714286 weeks, depending on its length. So, weeks always range in [0, 52]. I had to use an if for week 52 as it cannot be calculated as 7-d.
create or replace function first_date_of_isoweek(y int, w int)
returns date
language sql as $$
select to_date(concat(y, to_char(w, 'fm00')), 'iyyyiw');
$$;
create or replace function last_date_of_isoweek(y int, w int)
returns date
language plpgsql as $$
declare
d1 date;
d2 date;
d smallint;
begin
-- Calculate first date of an isoweek
d1 = to_date(concat(y, to_char(w, 'fm00')), 'iyyyiw');
-- Year's last week needs an speacial treatment
if w = 52 then
return to_date(concat(y, '1231'), 'yyyyMMdd');
else
-- Calculate the dow of the first date
d = extract(isodow from d1);
-- Calculate the last date out of the first date
return d1 + interval '1 day' * (7-d);
end if;
end $$;
Test 2021 as follows:
select first_date_of_isoweek(2021, 0);
first_date_of_isoweek|
---------------------+
2021-01-01|
select last_date_of_isoweek(2021, 0);
last_date_of_isoweek|
--------------------+
2021-01-03|
select first_date_of_isoweek(2021, 1);
first_date_of_isoweek|
---------------------+
2021-01-04|
select last_date_of_isoweek(2021, 1);
last_date_of_isoweek|
--------------------+
2021-01-10|
select first_date_of_isoweek(2021, 52);
first_date_of_isoweek|
---------------------+
2021-12-27|
select last_date_of_isoweek(2021, 52);
last_date_of_isoweek|
--------------------+
2021-12-31|

How to calculate the difference in days between dates

I need to calculate the number of days between dates as detailed below using MSSQL
Each month should be considered as if it has 30 days (even if it doesn't)
The difference between 2 January, 2013 to 2 March, 2013 will be
(30-2) + 30 + 2 days
where (30-2) will be for January
30 will be for February
2 will be for March
create or replace function datediff( p_what in varchar2,
p_d1 in date,
p_d2 in date ) return number
as
l_result number;
begin
select (p_d2-p_d1) *
decode( upper(p_what),
'DAY', 1, 'SS', 24*60*60, 'MI', 24*60, 'HH', 24, NULL )
into l_result from dual;
return l_result;
end;
/
This is what I do in Oracle (Courtesy: ASKTOM).
I get either days, hours, minutes or seconds in difference.
In MS SQL, either
PRINT DATEDIFF(DAY, '1/1/2011', '3/1/2011')
This gives the number of times the midnight boundary is crossed between the two dates. You may decide to need to add one to this if you're including both dates in the count - or subtract one if you don't want to include either date.
OR
DECLARE #startdate datetime2 = '2007-05-05 12:10:09.3312722';
DECLARE #enddate datetime2 = '2009-05-04 12:10:09.3312722';
SELECT DATEDIFF(day, #startdate, #enddate);
Using this you can manipulate.
Looks like you want to get a result similar to Oracle's MONTHS_BETWEEN in SQL Server.
This is a SQL function i wrote in Teradata, you probably just have to change EXTRACT to YEAR/MONTH/DAY(date)
REPLACE FUNCTION MONTHS_BETWEEN(date1 DATE, date2 DATE)
RETURNS FLOAT
SPECIFIC months_between_DT
RETURNS NULL ON NULL INPUT
CONTAINS SQL
DETERMINISTIC
COLLATION INVOKER
INLINE TYPE 1
RETURN
(EXTRACT(YEAR FROM date1) * 12 + EXTRACT(MONTH FROM date1))
- (EXTRACT(YEAR FROM date2) * 12 + EXTRACT(MONTH FROM date2))
+ CASE
WHEN EXTRACT(MONTH FROM date2) <> EXTRACT(MONTH FROM date2+1) AND
EXTRACT(MONTH FROM date1) <> EXTRACT(MONTH FROM date1+1)
THEN 0
ELSE (CAST(1 AS FLOAT))/31 * (EXTRACT(DAY FROM date1) - EXTRACT(DAY FROM date2))
END
;
Then you simply multiply the result * 30 and cast it to an INT.

Find Quarter from first day of the week

I am trying to find the Quarter from the firstday of the week.
For example, The first day of the week is 12/31/2012
I want the quarter to say Q1 2013. But if I find the Qtr from the specific date it says Q4 2012.
I'm not sure this will work for all circumstances in your case, so please test out with different dates:
DECLARE #date DATETIME
SET #date = '2012-12-31'
SELECT
qtr = DATEPART(quarter, DATEADD(DAY, 7 - DATEPART(weekday, #Date), #date)),
YearNum = DATEPART(YEAR, DATEADD(DAY, 7 - DATEPART(weekday, #Date), #date))
This is for Oracle - annual week table... May give you some ideas:
-- ISO_WK# --
SELECT mydate
, TRUNC(mydate, 'w') wk_starts
, TRUNC(mydate, 'w') + 7 - 1/86400 wk_ends
, TO_NUMBER (TO_CHAR (mydate, 'IW')) ISO_wk#
FROM
(
SELECT TRUNC(SYSDATE, 'YEAR')-1 + LEVEL AS mydate -- 1st day of yr-1 day --
FROM dual
CONNECT BY LEVEL <=
(-- First day of curr year – first day of past year --
SELECT TRUNC(SYSDATE, 'YEAR')-TRUNC(ADD_MONTHS (SYSDATE, -12), 'YEAR') "Num of Days"
FROM dual
)
)
/