How to calculate the difference in days between dates - sql

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.

Related

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|

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

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^

Generate a day date according to existing month+year+daydiff

I'm using SQL Server 2008.
I got a table with these 5 columns:
StartMonth, StartYear, EndMonth, EndYear, DaysBetween
I don't have the day of these dates and that's what I?m trying to generate.
For example:
12 2008 1 2009 8
I want to create a random date (start date and end date , format as dd/mm/yyyy) which will include the day and will make scene upon the data I have under days between
E.g., if I know that I got 8 days (DaysBetween) and the startmonth is 12, the date must be
from 24/12/2008 cause if I add 8 days I get the EndMonth (1/2009)
If I would choose the date 2/12/2008 I would get 10/12/2008 and its not good cause the month is still 12.... and I need 1 (2009)
How can I generate valid dates ?
Something like this?
WITH N(n) AS (
SELECT a*6+b FROM
(VALUES(0),(1),(2),(3),(4),(5))a(a),
(VALUES(1),(2),(3),(4),(5),(6))b(b)
), T(StartMonth,StartYear,EndMonth,EndYear,DaysBetween) AS (
SELECT 12, 2008, 1, 2009, 8
)
SELECT *
FROM (
SELECT *, DATEADD (dd, -n, DATEADD (mm, StartMonth, DATEADD (yy, StartYear - 1900, '19000101'))) AS dt
FROM T
INNER JOIN N ON DaysBetween >= n
) T
WHERE MONTH (dt) = StartMonth
AND YEAR (dt) = StartYear
AND MONTH (DATEADD (dd, DaysBetween, dt)) = EndMonth
AND YEAR (DATEADD (dd, DaysBetween, dt)) = EndYear
Here is a sample:
WITH CTE_Start AS
(
SELECT DATEADD(dd,-(ABS(CHECKSUM(NewId())) % DaysBetween+1),dateadd(mm, (EndYear - 1900) * 12 + EndMonth - 1,0)) StartDate, DaysBetween
FROM dbo.This5Columns
)
SELECT StartDate, DATEADD(dd,DaysBetween,StartDate) AS EndDate
FROM CTE_Start
ABS(CHECKSUM(NewId())) % DaysBetween+1
is used to get random number of days between 1 and DaysBetween,
dateadd(mm, (EndYear - 1900) * 12 + EndMonth - 1,0)
Gets the first date of end month (earliest possible end date)
Then you subtract the two to get your StartDate, and add DaysBetween again to get the EndDate
SQLFiddle DEMO
You need to parts to solve this:
Create a random value between 1 and the DaysBetween
Use the DateAdd function to pick a date X days before the first of the next month (X being the value from the previous part).
To create a random value try something like this:
SELECT Cast(( DAYSBETWEEN ) * Rand(Cast(Newid() AS VARBINARY)) + 1
AS
TINYINT) AS 'randomNumber'
FROM TABLE
Then add the dateadd function:
SELECT Dateadd(DD, Cast(( DAYSBETWEEN ) * Rand(Cast(Newid() AS
VARBINARY))
+ 1 AS
TINYINT), Dateadd(MONTH, 1,
Cast(
Cast(STARTMONT AS VARCHAR(2)) + '-01-'
+ Cast(#StartYear AS VARCHAR(4)) AS DATE)))
FROM TABLE

SQL - Sum of minutes between two timestamps by month

I am looking for an SQL query for the sum of minutes between start and end date for a particular month.
Eg.
I'm looking for the amount of minutes used in February.
Start Date Time: 27-02-13 00:00:00
End Date Time: 05-03-13 00:00:00
Because im only looking for the sum of february it should only give me the sum of 3 days (in minutes) and not the extra 5 days going into march.
I have no way to validate it but it should looks like:
SELECT DATEDIFF(minute, startDate, CASE when endDate > EOMONTH(startDate) THEN EOMONTH(startDate) ELSE endDate END) FROM ...
GL!
I left it in steps to illustrate each process. You can of course easily collapse this down, but I'll leave it up to you to do that.
Here's my solution http://www.sqlfiddle.com/#!3/b4991/1/0
SELECT *
, DATEDIFF(minute, StartDAte, NewEndDate) AS TotalMinutes
FROM
(
SELECT *
, CASE WHEN TempDate > EndDate THEN EndDate ELSE TempDate END AS NewEndDate -- Either EOM or old EndDate, whichever is smaller
FROM
(
SELECT *
, DATEADD(month, 1, CAST(Year + '-' + Month + '-1' AS DATETIME)) AS TempDate -- first day of the next month
FROM
(
select *
, CAST(DATEPART(month, StartDate) AS char(2)) AS Month
, CAST(DATEPART(year, StartDate) AS char(4)) AS Year
from tbl
) t0
) t1
) t2
First I get the year and month from the original StartDate. I then construct a first-of-the-month date from that. I then add one month to that to get me the first-of-the-month of the next month. Then I check if that new date is > or < the previous EndDate. I take the smaller of the two dates. Then I use the original StartDate and whichever is smaller between the TempDate and EndDate to determine my total minutes.
See Also EOMONTH: http://msdn.microsoft.com/en-us/library/hh213020.aspx
Look into using DATEDIFF -- this will just help you to get started:
SELECT DATEDIFF(minute, starttime, endtime)
http://msdn.microsoft.com/en-us/library/ms189794.aspx
To get the last day of the start month, use DATEADD:
SELECT DATEADD(second,-1,DATEADD(month, DATEDIFF(month,0,starttime)+1,0))
SQL Fiddle Demo
I recently had to solve a similar problem, I added two new functions to help with this:
CREATE FUNCTION [dbo].[GREATESTDATE]
(
-- Add the parameters for the function here
#Date1 DATETIME,
#Date2 DATETIME
)
RETURNS DATETIME
AS
BEGIN
IF (#Date1 < #Date2)
RETURN #Date2
ELSE
RETURN #Date1
END
and...
CREATE FUNCTION [dbo].[LEASTDATE]
(
-- Add the parameters for the function here
#Date1 DATETIME,
#Date2 DATETIME
)
RETURNS DATETIME
AS
BEGIN
IF (#Date1 > #Date2)
RETURN #Date2
ELSE
RETURN #Date1
END
Then use them like:
DATEDIFF(D,dbo.GREATESTDATE(#StartDate1,#StartDate2),dbo.LEASTDATE(#EndDate1,#EndDate2))

Most efficient way to calculate the first day of the current Financial Year?

What's the most efficient way to calculate the first day of the current (Australian) Financial Year?
The Australian FY begins on 01-July.
E.g.
SELECT dbo.FinancialYearStart('30-Jun-2011') returns 01-Jul-2010.
SELECT dbo.FinancialYearStart('01-Jul-2011') returns 01-Jul-2011.
SELECT dbo.FinancialYearStart('02-Jul-2011') returns 01-Jul-2011.
One DATEADD, one DATEDIFF, and a division:
SELECT DATEADD(year,DATEDIFF(month,'19010701','20110630')/12,'19010701')
Basically, you count the number of months since some arbitrary financial year's start date (I've picked 1901), divide that number by 12 (ignoring the remainder), and add that many years back to the same arbitrary year's start date.
I don't know if this is the most efficient, but it's fast at least...
create function dbo.FinancialYearStart
(
#CurrentDate datetime
)
returns datetime
as
begin
declare #CurrentYear int
,#FYDateThisYear datetime
,#FYDatePrevYear datetime
set #CurrentYear = datepart(year, #CurrentDate)
set #FYDateThisYear = '01-Jul-' + cast(#CurrentYear as varchar(4))
set #FYDatePrevYear = '01-Jul-' + cast(#CurrentYear-1 as varchar(4))
if #CurrentDate < #FYDateThisYear
begin
return #FYDatePrevYear
end
return #FYDateThisYear
end
Extract the year and month from the date. Then do year = year + FLOOR((month-7) / 6)
Then your date is 1-jul-year
(You don't actually need to store them as variables.)
Something like: CONCATENATE('01-jul-', YEAR(date) + FLOOR((MONTH(date)-7) / 6)
A somewhat sophisticated method (maybe a tiny little bit too much):
SELECT
DATEADD(month,
(MONTH(GETDATE()) - 1) / 6 * 12 - 6,
CAST(CAST(YEAR(GETDATE()) AS varchar) AS datetime)
)
Clunky but it works
select
cast('01-Apr-' +
cast(
case
when datepart(mm,getdate()) in (4,5,6,7,8,9,10,11,12)
then DATEPART(yy,getdate())
else DATEPART(yy,getdate())-1
end as varchar
) as datetime
) as fy_start
SELECT cast(cast(YEAR(getdate())-
(case
when MONTH(GETDATE()) between 1 and 6 then 1
else 0
end) as varchar)+'0701' as date)