SQL - Sum of minutes between two timestamps by month - sql

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))

Related

SQL query to get result for a date range when table got Year and Month fields

I have a table with Year and Month fields. I need to select the rows between two particular dates. How do I set this in the where condition?
You are a bit unclear on what "between" means in this context. I'm pretty sure that datefromparts() can be quite helpful:
select t.*
from t
where #date_start <= datefromparts(year, month, 1) and
#date_end >= datefromparts(year, month, 1);
You might also find eomonth() helpful, if the end of the month is an important date.
You can try with help month(),year() function of date
Here is the example:
select * from t where (t.month=month('2017-01-01') and t.year=year('2017-01-01')) and (t.month=month('2017-05-01') and t.year=year('2017-05-01'));
Below is the link for month() in sql.
https://learn.microsoft.com/en-us/sql/t-sql/functions/month-transact-sql
This might help you in solving your problem.
you can also try something like:
select
d.*
from
(select
t.*,
cast(rtrim(cast([month] as char(2))) + '/1/' + cast ([year] as char(4)) as date) as dt) d
where
dt >= #dateStart and
dt <= #dateEnd
If the year and month values are integers, you can cast them to varchar, then date, and use BETWEEN. An example with declaring startdate and enddate variables:
DECLARE #startdate date = '2015-07-01'
,#enddate date = '2016-07-01'
SELECT *
FROM #myTable
WHERE CAST(CAST(Year as varchar) + '-' + CAST(Month as varchar) + '-01' as date)
BETWEEN #startdate AND #enddate
If they are chars or varchars you need only cast to date and use BETWEEN:
DECLARE #startdate date = '2015-07-01'
,#enddate date = '2016-07-01'
SELECT *
FROM #myTable
WHERE CAST(Year + '-' + Month + '-01' as date)
BETWEEN #startdate AND #enddate
Try with "Between".
Something like this:
SELECT SALES_AMOUNT FROM TABLE WHERE YEAR BETWEEN 2015 AND 2016 AND MONTH BETWEEN 6 AND 8

SQL query to find employee aniversary

I need to find anniversary date and anniversary year of employees and send email in every 14 days.But I have a problem with last week of December when using the following query if start date and end date are in different years.
Select * from Resource
where (DATEPART(dayofyear,JoinDate)
BETWEEN DATEPART(dayofyear,GETDATE())
AND DATEPART(dayofyear,DateAdd(DAY,14,GETDATE())))
Instead of comparing to a dayofyear (which resets to zero at jan 1st and is the reason your query breaks within 14 days of the end of the year) you could update the employee's joindate to be the current year for the purpose of the query and just compare to actual dates
Select * from Resource
-- Add the number of years difference between joinDate and the current year
where DATEADD(year,DATEDIFF(Year,joinDate,GetDate()),JoinDate)
-- compare to range "today"
BETWEEN GetDate()
-- to 14 days from today
AND DATEADD(Day,14,GetDate())
-- duplicate for following year
OR DATEADD(year,DATEDIFF(Year,joinDate,GetDate())+1,JoinDate) -- 2016-1-1
BETWEEN GetDate()
AND DATEADD(Day,14,GetDate())
Test query:
declare #joindate DATETIME='2012-1-1'
declare #today DATETIME = '2015-12-26'
SELECT #joinDate
where DATEADD(year,DATEDIFF(Year,#joinDate,#today),#JoinDate) -- 2015-1-1
BETWEEN #today -- 2015-12-26
AND DATEADD(Day,14,#today) -- 2016-01-09
OR DATEADD(year,DATEDIFF(Year,#joinDate,#today)+1,#JoinDate) -- 2016-1-1
BETWEEN #today -- 2015-12-26
AND DATEADD(Day,14,#today) -- 2016-01-09
(H/T #Damien_The_Unbeliever for a simple fix)
The above correctly selects the joinDate which is in the first week of Jan (note I've had to fudge #today as Ive not managed to invent time travel).
The above solution should also solve the issue with leap years that was hiding in your original solution.
Update
You expressed in comments the requirement to select AnniversaryDate and Years of service, you need to apply some CASE logic to determine whether to add 1 (year or date) to your select
select *,
CASE
WHEN DATEADD(YEAR,DATEDIFF(Year,JoinDate,GETDATE()),JoinDate) < GetDate()
THEN DATEDIFF(Year,JoinDate,GETDATE())+1
ELSE DATEDIFF(Year,JoinDate,GETDATE())
END as [Years],
CASE WHEN DATEADD(YEAR,DATEDIFF(Year,JoinDate,GETDATE()),JoinDate) < GetDate()
THEN DATEADD(YEAR,DATEDIFF(Year,JoinDate,GETDATE())+1,JoinDate)
ELSE DATEADD(YEAR,DATEDIFF(Year,JoinDate,GETDATE()),JoinDate)
end as [AnniversaryDate]
.... // etc
You could do this:
Select * from Resource
where DATEPART(dayofyear,JoinDate)
BETWEEN DATEPART(dayofyear,GETDATE())
AND DATEPART(dayofyear,DateAdd(DAY,14,GETDATE()))
OR
DATEPART(dayofyear,JoinDate)
BETWEEN (DATEPART(dayofyear,GETDATE()) + 365)
AND (DATEPART(dayofyear,DateAdd(DAY,14,GETDATE())) + 365)
Try this:
DECLARE #Today DATE = GETDATE() --'12/25/2013'
DECLARE #Duration INT = 14
;WITH Recur AS
(
SELECT #Today AS RecurDate
UNION ALL
SELECT DATEADD(DAY, 1, RecurDate)
FROM Recur
WHERE DATEDIFF(DAY, #Today, RecurDate)+1 < #Duration
)
SELECT
r.*
FROM
Resource r
JOIN Recur
ON CONVERT(VARCHAR(5), JoinDate, 101) = CONVERT(VARCHAR(5), RecurDate, 101)
WHERE JoinDate < #Today
You can use the SQL DATEADD() function with week number parameter
Here is how you can use it:
DECLARE #date date = getdate()
Select * from Resource
where
JoinDate BETWEEN #date AND DATEADD(ww,2,#date)

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)

What is a good way to find gaps in a set of datespans?

What is a way to find gaps in a set of date spans?
For example, I have these date spans:
1/ 1/11 - 1/10/11
1/13/11 - 1/15/11
1/20/11 - 1/30/11
Then I have a start and end date of 1/7/11 and 1/14/11.
I want to be able to tell that between 1/10/11 and 1/13/11 there is a gap so the start and end date is not possible. Or I want to return only the datespans up to the first gap encountered.
If this can be done in SQL server that would be good.
I was thinking to go through each date to find out if it lands in a datespan... if it does not then there's a gap on that day.
Jump to 2nd last code block for: *I want to be able to tell that
between 1/10/11 and 1/13/11 there is
a gap so the start and end date is*
not possible.
Jump to last code block for: *I want to return only
the datespans up to the first gap
encountered.*
First of all, here's a virtual table to discuss
create table spans (date1 datetime, date2 datetime);
insert into spans select '20110101', '20110110';
insert into spans select '20110113', '20110115';
insert into spans select '20110120', '20110130';
This is a query that will list, individually, all the dates in the calendar
declare #startdate datetime, #enddate datetime
select #startdate = '20110107', #enddate = '20110114'
select distinct a.date1+v.number
from spans A
inner join master..spt_values v
on v.type='P' and v.number between 0 and datediff(d, a.date1, a.date2)
-- we don't care about spans that don't intersect with our range
where A.date1 <= #enddate
and #startdate <= A.date2
Armed with this query, we can now test to see if there are any gaps, by
counting the days in the calendar against the expected number of days
declare #startdate datetime, #enddate datetime
select #startdate = '20110107', #enddate = '20110114'
select case when count(distinct a.date1+v.number)
= datediff(d,#startdate, #enddate) + 1
then 'No gaps' else 'Gap' end
from spans A
inner join master..spt_values v
on v.type='P' and v.number between 0 and datediff(d, a.date1, a.date2)
-- we don't care about spans that don't intersect with our range
where A.date1 <= #enddate
and #startdate <= A.date2
-- count only those dates within our range
and a.date1 + v.number between #startdate and #enddate
Another way to do this is to just build the calendar from #start
to #end up front and look to see if there is a span with this date
declare #startdate datetime, #enddate datetime
select #startdate = '20110107', #enddate = '20110114'
-- startdate+v.number is a day on the calendar
select #startdate + v.number
from master..spt_values v
where v.type='P' and v.number between 0
and datediff(d, #startdate, #enddate)
-- run the part above this line alone to see the calendar
-- the condition checks for dates that are not in any span (gap)
and not exists (
select *
from spans
where #startdate + v.number between date1 and date2)
The query returns ALL dates that are gaps in the date range #start - #end
A TOP 1 can be added to just see if there are gaps
To return all records that are before the gap, use the query as a
derived table in a larger query
declare #startdate datetime, #enddate datetime
select #startdate = '20110107', #enddate = '20110114'
select *
from spans
where date1 <= #enddate and #startdate <= date2 -- overlaps
and date2 < ( -- before the gap
select top 1 #startdate + v.number
from master..spt_values v
where v.type='P' and v.number between 0
and datediff(d, #startdate, #enddate)
and not exists (
select *
from spans
where #startdate + v.number between date1 and date2)
order by 1 ASC
)
Assuming MySQL, something like this would work:
select #olddate := null;
select start_date, end_date, datediff(end_date, #olddate) as diff, #olddate:=enddate
from table
order by start_date asc, end_date asc
having diff > 1;
Basically: cache the previous row's end_date in the #olddate variable, and then do a diff on that "old" value with the currel enddate. THe having clause will return only the records where the difference between two rows is greater than a day.
disclaimer: Haven't tested this, but the basic query construct should work.
I want to be able to tell that between
1/10/11 and 1/13/11 there is a gap so
the start and end date is not
possible.
I think you're asking this question: does the data in your table have a gap between the start date and the end date?
I created a one-column table, date_span, and inserted your date spans into it.
You can identify a gap by counting the number of days between start date and end date, and comparing that the the number of rows in date_span for the same range.
select
date '2011-01-14' - date '2011-01-07' + 1 as elapsed_days,
count(*) from date_span
where cal_date between '2011-01-07' and '2011-01-14';
returns
elapsed_days count
-- --
8 6
Since they're not equal, there's a gap in the table "date_span" between 2011-01-07 and 2011-01-14. I'll stop there for now, because I'm really not certain what you're trying to do.

Calculating in SQL the first working day of a given month

I have to calculate all the invoices which have been paid in the first 'N' days of a month. I have two tables
. INVOICE: it has the invoice information. The only field which does matter is called 'datePayment'
. HOLYDAYS: It is a one column table. Entries at this table are of the form "2009-01-01",
2009-05-01" and so on.
I should consider also Saturdays and Sundays
(this might be not a problem because I could insert those days at the Hollidays table in order to consider them as hollidays if neccesary)
The problem is to calculate which is the 'payment limit'.
select count(*) from invoice
where datePayment < PAYMENTLIMIT
My question is how to calculate this PAYMENTLIMIT. Where PAYMENTLIMIT is 'the fifth working day of every month'.
The query should be run under Mysql and Oracle therefore standard SQL should be used.
Any hint?
EDIT
In order to be consistent with the title of the question the pseudo-query should the read as follows:
select count(*) from invoice
where datePayment < FIRST_WORKING_DAY + N
then the question can be reduced to calculate the FIRST_WORKING_DAY of every month.
You could look for the first date in a month, where the date is not in the holiday table and the date is not a weekend:
select min(datePayment), datepart(mm, datePayment)
from invoices
where datepart(dw, datePayment) not in (1,7) --day of week
and not exists (select holiday from holidays where holiday = datePayment)
group by datepart(mm, datePayment) --monthnr
Something like this might work:
create function dbo.GetFirstWorkdayOfMonth(#Year INT, #Month INT)
returns DATETIME
as begin
declare #firstOfMonth VARCHAR(20)
SET #firstOfMonth = CAST(#Year AS VARCHAR(4)) + '-' + CAST(#Month AS VARCHAR) + '-01'
declare #currDate DATETIME
set #currDate = CAST(#firstOfMonth as DATETIME)
declare #weekday INT
set #weekday = DATEPART(weekday, #currdate)
-- 7 = saturday, 1 = sunday
while #weekday = 1 OR #weekday = 7
begin
set #currDate = DATEADD(DAY, 1, #currDate)
set #weekday = DATEPART(weekday, #currdate)
end
return #currdate
end
I'm not 100% sure about whether the "weekday" numbers are fixed or might depend on your locale on your SQL Server. Check it out!
Marc
Rather than a Holidays table of days to exclude, we use the calendar table approach: one row for every day the application will ever need (thirty years spans a modest 11K rows). So not only does it have an is_weekday column, it has other things relevant to the enterprise e.g. julianized_date. This way, every possible date would have a ready-prepared value for first_working_day_this_month and finding it involves a simple lookup (which SQL products tend to be optimized for!) rather than 'calculating' it each time on the fly.
We have dates table in our application (filled with all dates and date parts for some tens of years), what allows various "missing" date manipulations, like (in pseudo-sql):
select min(ourdates.datevalue)
from ourdates
where ourdates.year=<given year> and ourdates.month=<given month>
and ourdates.isworkday
and not exists (
select * from holidays
where holidays.datevalue=ourdates.datevalue
)
Ok, at a first stab, you could put the following code into a UDF and pass in the Year and Month as variables. It can then return TestDate which is the first working day of the month.
DECLARE #Month INT
DECLARE #Year INT
SELECT #Month = 5
SELECT #Year = 2009
DECLARE #FirstDate DATETIME
SELECT #FirstDate = CONVERT(varchar(4), #Year) + '-' + CONVERT(varchar(2), #Month) + '-' + '01 00:00:00.000'
DROP TABLE #HOLIDAYS
CREATE TABLE #HOLIDAYS (HOLIDAY DateTime)
INSERT INTO #HOLIDAYS VALUES('2009-01-01 00:00:00.000')
INSERT INTO #HOLIDAYS VALUES('2009-05-01 00:00:00.000')
DECLARE #DateFound BIT
SELECT #DateFound = 0
WHILE(#DateFound = 0)
BEGIN
IF(
DATEPART(dw, #FirstDate) = 1
OR
DATEPART(dw, #FirstDate) = 1
OR
EXISTS(SELECT * FROM #HOLIDAYS WHERE HOLIDAY = #FirstDate)
)
BEGIN
SET #FirstDate = DATEADD(dd, 1, #FirstDate)
END
ELSE
BEGIN
SET #DateFound = 1
END
END
SELECT #FirstDate
The things I don`t like with this solution though are, if your holidays table contains all days of the month there will be an infinite loop. (You could check the loop is still looking at the right month) It relies upon the dates being equal, eg all at time 00:00:00. Finally, the way I calculate the 1st of the month past in using string concatenation was a short cut. There are much better ways of finding the actual first day of the month.
Gets the first N working days of each month of year 2009:
select * from invoices as x
where
datePayment between '2009-01-01' and '2009-12-31'
and exists
(
select
1
from invoices
where
-- exclude holidays and sunday saturday...
(
datepart(dw, datePayment) not in (1,7) -- day of week
/*
-- Postgresql and Oracle have programmer-friendly IN clause
and
(datepart(yyyy,datePayment), datepart(mm,datePayment))
not in (select hyear, hday from holidays)
*/
-- this is the MSSQL equivalent of programmer-friendly IN
and
not exists
(
select * from holidays
where
hyear = datepart(yyyy,datePayment)
and hmonth = datepart(mm, datePayment)
)
)
-- ...exclude holidays and sunday saturday
-- get the month of x datePayment
and
(datepart(yyyy, datePayment) = datepart(yyyy, x.datePayment)
and datepart(mm, datePayment) = datepart(mm, x.datePayment))
group by
datepart(yyyy, datePayment), datepart(mm, datePayment)
having
x.datePayment < MIN(datePayment) + #N -- up to N working days
)
Returns the first Monday of the current month
SELECT DATEADD(
WEEK,
DATEDIFF( --x weeks between 1900-01-01 (Monday) and inner result
WEEK,
0, --1900-01-01
DATEADD( --inner result
DAY,
6 - DATEPART(DAY, GETDATE()),
GETDATE()
)
),
0 --1900-01-01 (Monday)
)
SELECT DATEADD(day, DATEDIFF (day, 0, DATEADD (month, DATEDIFF (month, 0, GETDATE()), 0) -1)/7*7 + 7, 0);
select if(weekday('yyyy-mm-01') < 5,'yyyy-mm-01',if(weekday('yyyy-mm-02') < 5,'yyyy-mm-02','yyyy-mm-03'))
Saturdays and Sundays are 5, 6 so you only need two checks to get the first working day