I have a MS SQL table which contains a column containing month numbers (stored as an int).
I would like to get the number of minutes in the month using the month number.
After looking through Stack Overflow I came across the following code which I thought I could adapt to tell me the number of minutes by replacing the word DAY with MINUTE but to no avail.
DECLARE #ADate DATETIME
SET #ADate = GETDATE()
SELECT DAY(EOMONTH(#ADate)) AS DaysInMonth
The month is a 1 or 2 digit int depending on the month.
UPDATE:
I do have the year as well stored in another column.
Use DATEDIFF with MINUTE.
IF OBJECT_ID('tempdb..#Month') IS NOT NULL
DROP TABLE #Month
CREATE TABLE #Month (
MonthNumber INT,
Year INT)
INSERT INTO #Month (
MonthNumber,
Year)
VALUES
(1, 2018),
(2, 2018),
(2, 2016), -- Leap
(12, 2018)
SELECT
M.MonthNumber,
M.Year,
FirstDay = DATEFROMPARTS(M.Year, M.MonthNumber, 1),
FirstDayNextMonth = DATEADD(
MONTH,
1,
DATEFROMPARTS(M.Year, M.MonthNumber, 1)),
Minutes = DATEDIFF(
MINUTE,
DATEFROMPARTS(M.Year, M.MonthNumber, 1), -- FirstDay
DATEADD(MONTH, 1, DATEFROMPARTS(M.Year, M.MonthNumber, 1))) -- FirstDayNextMonth
FROM
#Month AS M
Results:
MonthNumber Year FirstDay FirstDayNextMonth Minutes
1 2018 2018-01-01 2018-02-01 44640
2 2018 2018-02-01 2018-03-01 40320
2 2016 2016-02-01 2016-03-01 41760
12 2018 2018-12-01 2019-01-01 44640
That's a deceptively tricky question. It's not only that the number of days in February changes in leap years. The number of hours in a date changes during the transition to Summer/Winter time. To calculate the correct difference in minutes between two dates you need to know the correct time offset as well.
Instead of looking up the offset for each date though, you can use the timezone. The timezone information contains all the rules needed to calculate the time offset for past and future dates, provided the OS's timezone information is kept up to date.
SQL Server 2016 and later support timezones with the AT TIME ZONE expression :
For example these queries:
select datetimefromparts(2018,3,25,0,0,0,0) at TIME ZONE 'E. Europe Standard Time'
select datetimefromparts(2018,3,26,0,0,0,0) at TIME ZONE 'E. Europe Standard Time'
Return
2018-03-25 00:00:00.000 +02:00
2018-03-26 00:00:00.000 +03:00
March 25 had 23 hours instead of 24. The difference of the two days in minutes is 1380 instead of the usual 1440:
select datediff(mi,
datetimefromparts(2018,3,25,0,0,0,0) at TIME ZONE 'E. Europe Standard Time',
datetimefromparts(2018,3,26,0,0,0,0) at TIME ZONE 'E. Europe Standard Time')
-----
1380
You can pass the timezone name as a parameter :
declare #mytimezone nvarchar(40)='E. Europe Standard Time'
select datetimefromparts(2018,3,25,0,0,0,0) at TIME ZONE #mytimezone
select datetimefromparts(2018,3,26,0,0,0,0) at TIME ZONE #mytimezone
To calculate the correct number of minutes in a month, you could use the difference in minutes between the current and the next month's 1st day on a specific timezone with AT TIME ZONE. This would ensure the correct offsets are used.
A query that calculates the difference in minutes correctly could look like this :
declare #mytimezone nvarchar(40)='E. Europe Standard Time'
select year,month,
datediff(mi,
datetimefromparts(M.Year,M.Month,1,0,0,0,0) at TIME ZONE #mytimezone,
dateadd(MONTH,1,datetimefromparts(M.Year,M.Month,1,0,0,0,0)) at TIME ZONE #mytimezone
)
from (values
(2016,2),
(2018,1),
(2018,2),
(2018,3),
(2018,4),
(2018,5),
(2018,6),
(2018,7),
(2018,8),
(2018,9),
(2018,10),
(2018,11),
(2018,12) ) M(Year,Month)
You can create a function to calculate the minutes. Either a scalar function that can be used in the SELECT clause :
CREATE FUNCTION MinutesInMonth
(
#Year INT,
#Month INT,
#timezone nvarchar(40)
)
RETURNS INT
AS
BEGIN
Return
datediff(mi,
datetimefromparts(#year,#month,1,0,0,0,0) at TIME ZONE #timezone,
dateadd(MONTH,1,datetimefromparts(#year,#month,1,0,0,0,0)) at TIME ZONE #timezone
)
END
...
select year,month,dbo.MinutesInMonth(year,month,#mytimezone)
from (values
(2018,10),
(2018,11),
(2018,12)
) M(Year,Month)
Or an inline table function that can be used in the FROM clause and gets inlined in the query itself :
CREATE FUNCTION MinutesInMonth
(
#Year INT,
#Month INT,
#timezone nvarchar(40)
)
RETURNS TABLE
RETURN (select datediff(mi,
datetimefromparts(#year,#month,1,0,0,0,0) at TIME ZONE #timezone,
dateadd(MONTH,1,datetimefromparts(#year,#month,1,0,0,0,0))
At TIME ZONE #timezone) as Minutes
)
select year,month,Minutes
from (values
(2016,2),
(2018,1),
(2018,2),
(2018,3),
(2018,4),
(2018,5),
(2018,6),
(2018,7),
(2018,8),
(2018,9),
(2018,10),
(2018,11),
(2018,12)
) M(Year,Month)
cross apply dbo.MinutesInMonth(year,month,#mytimezone)
Try this out :-
DECLARE #Year INT = 2016, #Month INT = 2;
DECLARE #Date DATE = DATETIMEFROMPARTS(#Year,#Month, 1, 0, 0, 0, 0) AT TIME ZONE 'Sri Lanka Standard Time';
SELECT DAY(EOMONTH(#Date)) * 24 * 60;
Alternatively you can create a scalar function as follows:-
CREATE FUNCTION GetMinutesOfMonth
(
#Year INT,
#Month INT,
#TimeZone VARCHAR(50) = NULL
)
RETURNS INT
AS
BEGIN
DECLARE #MinutesOfMonth INT, #CurrentTimeZone VARCHAR(50);
EXEC MASTER.dbo.xp_regread 'HKEY_LOCAL_MACHINE',
'SYSTEM\CurrentControlSet\Control\TimeZoneInformation',
'TimeZoneKeyName',#CurrentTimeZone OUT;
SET #TimeZone = ISNULL((SELECT [name] FROM SYS.time_zone_info WHERE [name] = #TimeZone), #CurrentTimeZone)
DECLARE #Date DATE = DATETIMEFROMPARTS(#Year, #Month, 1, 0, 0, 0, 0) AT TIME ZONE #TimeZone;
SELECT #MinutesOfMonth = DAY(EOMONTH(#Date)) * 24 * 60;
RETURN #MinutesOfMonth;
END
GO
and use it as:-
DECLARE #Year INT = 2016, #Month INT = 2;
SELECT dbo.GetMinutesOfMonth(#Year, #Month, 'Sri Lanka Standard Time');
If the timezone of the current machine is same as the dates being considered, you may use the function as follows:-
DECLARE #Year INT = 2016, #Month INT = 2;
SELECT dbo.GetMinutesOfMonth(#Year, #Month, DEFAULT);
Related
I have a query that converts week and year to date.
But it returns the exact date.
But what i want is, I need the date to be such a day, that is same as the first day of the year.
dateadd (week, PromisedWeek-1, dateadd (year, PromisedYear-1900, 0)) - 4 -
datepart(dw, dateadd (week, PromisedWeek-1, dateadd (year, PromisedYear-1900, 0)) - 4) + 1
Hypothetical example.
My current query does is:
If week is 4 and year 2017, returns 26-Sun-2017
My need:
If week is 4 and year 2017 and January 1st was a Wednesday, its should return 29-Wednesday-2017.
Hoping that you guys get what I am trying to explain.
I need the query to return such a date which has the same day as that of the current year's 1st day.
Unless I'm reading this wrong, you're making things too hard on yourself. Just do this:
declare #year int; set #year = 2017;
declare #week int; set #week = 4;
select dateadd(week, #week, dateadd(year, #year - 1900, 0))
Result:
2017-01-29 00:00:00.000
The inner dateadd() gives you the first day of the requested year (Jan 1), regardless of weekday. If it's a Wednesday, you'll get a Wednesday. This year, it's Sunday. The outer dateadd() then adds full weeks to that, so you end up with the same day of the week, just like you asked.
If that's not what you want, please explain how it needs to be more complicated.
This is pretty easy if you have a calendar table. A calendar table is a table that contains every date and its parts for a given range. I created mine for 2010 to 2020 with this code:
declare #start_dt as date = '1/1/2010';
declare #end_dt as date = '1/1/2020';
declare #dates as table (
date_id date primary key,
date_year smallint,
date_month tinyint,
date_day tinyint,
weekday_id tinyint,
weekday_nm varchar(10),
month_nm varchar(10),
day_of_year smallint,
quarter_id tinyint,
first_day_of_month date,
last_day_of_month date,
start_dts datetime,
end_dts datetime
)
while #start_dt < #end_dt
begin
insert into #dates(
date_id, date_year, date_month, date_day,
weekday_id, weekday_nm, month_nm, day_of_year, quarter_id,
first_day_of_month, last_day_of_month,
start_dts, end_dts
)
values(
#start_dt, year(#start_dt), month(#start_dt), day(#start_dt),
datepart(weekday, #start_dt), datename(weekday, #start_dt), datename(month, #start_dt), datepart(dayofyear, #start_dt), datepart(quarter, #start_dt),
dateadd(day,-(day(#start_dt)-1),#start_dt), dateadd(day,-(day(dateadd(month,1,#start_dt))),dateadd(month,1,#start_dt)),
cast(#start_dt as datetime), dateadd(second,-1,cast(dateadd(day, 1, #start_dt) as datetime))
)
set #start_dt = dateadd(day, 1, #start_dt)
end
select *
into Calendar
from #dates
Once you have a calendar table you can query for the correct weekday in the PromisedWeek
declare #PromisedYear as numeric
declare #PromisedWeek as int
set #PromisedYear = 2017
set #PromisedWeek = 4
select concat(date_day, '-', weekday_nm, '-', #PromisedYear)
from Calendar as c
where
weekday_id =
(select weekday_ID
from Calendar
where date_year = #PromisedYear
and day_of_year = 1
)
and datepart(week, date_id) = #PromisedWeek
and date_year = #PromisedYear
Turns out that January 1st 2017 was a Sunday and the 4th Sunday in 2017 was January 22nd, so the return is 22-Sunday-2017
I have a query for calculating first and last date in the week, according to given date. It is enough to set #dDate and the query will calculate first (monday) and last date (sunday) for that week.
Problem is, that is calculating wrong and I don't understand why.
Example:
#dDate = 2019-10-03 (year-month-day).
Result:
W_START W_END
2019-09-25 2019-10-01
But it should be:
2019-09-30 2019-10-06
Why is that?
Query:
set datefirst 1
declare #dDate date = cast('2019-10-16' as date)
select #dDAte
declare #year int = (select DATEPART(year, #dDAte))
select #year
declare #StartingDate date = cast(('' + cast(#year as nvarchar(4)) + '-01-01') as date)
select #StartingDate
declare #dateWeekEnd date = (select DATEADD(week, (datepart(week, cast(#dDate as date)) - 1), #StartingDate))
declare #dateWeekStart date = dateadd(day, -6, #dateWeekEnd)
select #dateWeekStart W_START, #dateWeekEnd W_END
Days of the week are so complicated. I find it easier to remember that 2001-01-01 fell on a Monday.
Then, the following date arithmetic does what you want:
select dateadd(day,
7 * (datediff(day, '2001-01-01', #dDate) / 7),
'2001-01-01' -- 2001-01-01 fell on a Monday
)
I admit this is something of a cop-out/hack. But SQL Server -- and other databases -- make such date arithmetic so cumbersome that simple tricks like this are handy to keep in mind.
I have to get/create date from the user input of week of month (week number in that month - 1st,2nd,3rd,4th and last) and day of week (sunday,monday..) in SQL server.
Examples:
4th Sunday of every month, Last Friday of every month, First Monday etc.
I was able to do it easily in .net but SQL server does seem limited in the date functions.
I am having to use lot of logic to get the date. To calculate the date using the above two parameters I had to use lot of datepart function.
Any suggestions on how to come up with the optimal SQL query for such a function?
I created a function other day for another OP GET Month, Quarter based on Work Week number
This function takes the current year as default it can be further modified to take Year as a parameter too.
an extension to that function can produce the results you are looking for ....
WITH X AS
(
SELECT TOP (CASE WHEN YEAR(GETDATE()) % 4 = 0 THEN 366 ELSE 365 END)-- to handle leap year
DATEADD(DAY
,ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) -1
, CAST(YEAR(GETDATE()) AS VARCHAR(4)) + '0101' )
DayNumber
From master..spt_values
),DatesData AS(
SELECT DayNumber [Date]
,DATEPART(WEEKDAY,DayNumber) DayOfTheWeek
,DATEDIFF(WEEK,
DATEADD(WEEK,
DATEDIFF(WEEK, 0, DATEADD(MONTH,
DATEDIFF(MONTH, 0, DayNumber), 0)), 0)
, DayNumber- 1) + 1 WeekOfTheMonth
FROM X )
SELECT * FROM DatesData
WHERE DayOfTheWeek = 6 -- A function would expect these two parameters
AND WeekOfTheMonth = 4 -- #DayOfTheWeek and #WeekOfTheMonth
Here is a general formula:
declare #month as datetime --set to the first day of the month you wish to use
declare #week as int --1st, 2nd, 3rd...
declare #day as int --Day of the week (1=sunday, 2=monday...)
--Second monday in August 2015
set #month = '8/1/2015'
set #week = 2
set #day = 2
select dateadd(
day,
((7+#day) - datepart(weekday, #month)) % 7 + 7 * (#week-1),
#month
)
You can also find the last, 2nd to last... etc with this reverse formula:
--Second to last monday in August 2015
set #month = '8/1/2015'
set #week = 2
set #day = 2
select
dateadd(
day,
-((7+datepart(weekday, dateadd(month,1,#month)-1)-#day)) % 7 - 7 * (#week-1),
dateadd(month,1,#month)-1
)
I want to do a mssql date range to return the data for an hour time range. Meaning, I want to return the data for the last hour, but not for the last hour from the current time.
Declare #today datetime
set #today=GETDATE()
select * from table1 where
datetime>= DATEADD(hh,-2,#Today)
For example, the current time is 11:50:00 a.m. I would like the query to return all the data between 10:00:00 a.m. to 10:59:00 a.m. My variables cannot be static. I would like it dynamic, so no matter what time of the day I run the query it only return the last hour data no matter what time is it now. So it could be any time between 11:00:00 a.m. until 11:59:00 a.m., I still want the result to return data from 10 a.m until 10:59 a.m.
Thank you
Here's a slightly simpler way:
DECLARE #d SMALLDATETIME;
SELECT #d = DATEADD(HOUR, DATEDIFF(HOUR, '20000101', GETDATE()) - 1, '20000101');
SELECT #d;
Now you can use #d in your query, e.g.
WHERE col >= #d AND col < DATEADD(HOUR, 1, #d);
This is an open-ended date range. Please don't think about the "end" of the range or consider this a BETWEEN query. BETWEEN 10:00 AND 10:59 is not a very wise approach, because you may miss data from 10:59:00.003 -> 10:59:59.997. Background info on why BETWEEN is evil.
Try this one :
declare #lowerRange datetime =
dateadd(hh,datepart(hour,dateadd(hh,-1,getdate())) ,
cast(cast(getdate() as date) as smalldatetime) )
declare #upperRange datetime = dateadd(hour,1,#lowerRange)
select * from yourtable where yourdate between #lowerRange and #upperRange
This returns an hour range, of the previous hour to now; meaning that if it was 11:35AM, it would return 10 AM to 11 AM:
DECLARE #today DATETIME, #hour DATETIME, #hourtwo DATETIME
SET #today = GETDATE()
-- Test other times
--SET #today = '2013-11-04 11:37.22'
SELECT #hour = DATEADD(hh,-2,#today)
SELECT #hourtwo = DATEADD(hh,-1,#today)
SELECT CONVERT(SMALLDATETIME,ROUND(CAST(#hour as float) * (24/1),0)/(24/1)) AS PreviousHourBegin
SELECT CONVERT(SMALLDATETIME,ROUND(CAST(#hourtwo as float) * (24/1),0)/(24/1)) AS PreviousHourEnd
This should work:
DECLARE #D DATETIME
SET #D = GETDATE()
SELECT #D AS 'Date',
DATEADD(HOUR,-1,DATEADD(MINUTE,-(DATEPART(MINUTE, #D)),DATEADD(SECOND,-(DATEPART(SECOND, #D)),DATEADD(MILLISECOND,-(DATEPART(MILLISECOND, #D)),#D)))) AS 'Range start',
DATEADD(MINUTE,-(DATEPART(MINUTE, #D)),DATEADD(SECOND,-(DATEPART(SECOND, #D)),DATEADD(MILLISECOND,-(DATEPART(MILLISECOND, #D)),#D))) AS 'Range end'
For the date:
2013-11-04 17:35:51.843
This will return a range like:
Start: 2013-11-04 16:00:00.000
End: 2013-11-04 17:00:00.000
For times between 00:00:00-01:00:00 it will get the range 23:00:00-00:00:00 from the previous day.
declare #today datetime
set #today=GETDATE()
select #today, DATEADD(HOUR, -2, DATEADD(HOUR, DATEDIFF(HOUR, 0, #today), 0)), DATEADD(MINUTE, -1, DATEADD(HOUR, -1, DATEADD(HOUR, DATEDIFF(HOUR, 0, #today), 0)))
result:
2013-11-04 17:42:17.933 2013-11-04 15:00:00.000 2013-11-04 15:59:00.000
I have a need to create a gross requirements report that takes how much supply and demand of a item in inventory from a start date onwards and 'buckets' it into different weeks of the year so that material planners know when they will need a item and if they have enough stock in inventory at that time.
As an example, today’s date (report date) is 8/27/08. The first step is to find the date for the Monday of the week the report date falls in. In this case, Monday would be 8/25/08. This becomes the first day of the first bucket. All transactions that fall before that are assigned to week #0 and will be summarized as the beginning balance for the report. The remaining buckets are calculated from that point. For the eighth bucket, there is no ending date so any transactions after that 8th bucket start date are considered week #8.
WEEK# START DATE END DATE
0.......None..........8/24/08
1.......8/25/08.......8/31/08
2.......9/1/08.........9/7/08
3.......9/8/08.........9/14/08
4.......9/15/08.......9/21/08
5.......9/22/08.......9/28/08
6.......9/29/08.......10/5/08
7.......10/06/08.....10/12/08
8.......10/13/08......None
How do I get the week #, start date, end date for a given date?
I've always found it easiest and most efficient (for SQL Server) to construct a table with one row for every week into the future through your domain horizon; and join to that (with a "WHERE GETDATE() >= MONDATE AND NOT EXISTS (SELECT 1 FROM table WHERE MONDATE < GETDATE())".
Anything you try to do with UDF's will be much less efficient and I find more difficult to use.
You can get Monday for any given date in a week as:
DATEADD(d, 1 - DATEPART(dw, #date), #date)
and you can write a stored procedure with the following body
-- find Monday at that week
DECLARE #currentDate SMALLDATETIME
SELECT #currentDate = DATEADD(d, 1 - DATEPART(dw, #date), #date)
-- create a table and insert the first record
DECLARE #weekTable TABLE (Id INT, StartDate SMALLDATETIME, EndDate SMALLDATETIME)
INSERT INTO #weekTable VALUES (0, NULL, #currentDate)
-- increment the date
SELECT #currentDate = DATEADD(d, 1, #currentDate)
-- iterate for 7 more weeks
DECLARE #id INT
SET #id = 1
WHILE #id < 8
BEGIN
INSERT INTO #weekTable VALUES (#id, #currentDate, DATEADD(d, 6, #currentDate))
SELECT #currentDate = DATEADD(ww, 1, #currentDate)
SET #id = #id + 1
END
-- add the last record
INSERT INTO #weekTable VALUES (8, #currentDate, NULL)
-- select the values
SELECT Id 'Week #', StartDate 'Start Date', EndDate 'End Date'
FROM #weekTable
When I pass
#date = '20080827'
to this procedure, I get the following
Week # Start Date End Date
0 NULL 2008-08-24 00:00:00
1 2008-08-25 00:00:00 2008-08-31 00:00:00
2 2008-09-01 00:00:00 2008-09-07 00:00:00
3 2008-09-08 00:00:00 2008-09-14 00:00:00
4 2008-09-15 00:00:00 2008-09-21 00:00:00
5 2008-09-22 00:00:00 2008-09-28 00:00:00
6 2008-09-29 00:00:00 2008-10-05 00:00:00
7 2008-10-06 00:00:00 2008-10-12 00:00:00
8 2008-10-13 00:00:00 NULL
--SQL sets the first day of the week as sunday and for our purposes we want it to be Monday.
--This command does that.
SET DATEFIRST 1
DECLARE
#ReportDate DATETIME,
#Weekday INTEGER,
#NumDaysToMonday INTEGER,
#MondayStartPoint DATETIME,
#MondayStartPointWeek INTEGER,
#DateToProcess DATETIME,
#DateToProcessWeek INTEGER,
#Bucket VARCHAR(50),
#DaysDifference INTEGER,
#BucketNumber INTEGER,
#NumDaysToMondayOfDateToProcess INTEGER,
#WeekdayOfDateToProcess INTEGER,
#MondayOfDateToProcess DATETIME,
#SundayOfDateToProcess DATETIME
SET #ReportDate = '2009-01-01'
print #ReportDate
SET #DateToProcess = '2009-01-26'
--print #DateToProcess
SET #Weekday = (select DATEPART ( dw , #ReportDate ))
--print #Weekday
--print DATENAME(dw, #ReportDate)
SET #NumDaysToMonday =
(SELECT
CASE
WHEN #Weekday = 1 THEN 0
WHEN #Weekday = 2 THEN 1
WHEN #Weekday = 3 THEN 2
WHEN #Weekday = 4 THEN 3
WHEN #Weekday = 5 THEN 4
WHEN #Weekday = 6 THEN 5
WHEN #Weekday = 7 THEN 6
END)
--print #NumDaysToMonday
SET #MondayStartPoint = (SELECT DATEADD (d , -1*#NumDaysToMonday, #ReportDate))
--print #MondayStartPoint
SET #DaysDifference = DATEDIFF ( dd , #MondayStartPoint , #DateToProcess )
--PRINT #DaysDifference
SET #BucketNumber = #DaysDifference/7
--print #BucketNumber
----Calculate the start and end dates of this bucket------
PRINT 'Start Of New Calc'
print #DateToProcess
SET #WeekdayOfDateToProcess = (select DATEPART ( dw , #DateToProcess ))
print #WeekdayOfDateToProcess
SET #NumDaysToMondayOfDateToProcess=
(SELECT
CASE
WHEN #WeekdayOfDateToProcess = 1 THEN 0
WHEN #WeekdayOfDateToProcess = 2 THEN 1
WHEN #WeekdayOfDateToProcess = 3 THEN 2
WHEN #WeekdayOfDateToProcess = 4 THEN 3
WHEN #WeekdayOfDateToProcess = 5 THEN 4
WHEN #WeekdayOfDateToProcess = 6 THEN 5
WHEN #WeekdayOfDateToProcess = 7 THEN 6
END)
print #NumDaysToMondayOfDateToProcess
SET #MondayOfDateToProcess = (SELECT DATEADD (d , -1*#NumDaysToMondayOfDateToProcess, #DateToProcess))
print #MondayOfDateToProcess ---This is the start week
SET #SundayOfDateToProcess = (SELECT DATEADD (d , 6, #MondayOfDateToProcess))
PRINT #SundayOfDateToProcess
The problem I see with the one bucket at a time approach is that its hard to make it scale,
If you join into a user defined function you will get better performance, you could use this a a starting point
Why not use a combination of DATEPART(year, date-column) and DATEPART(week, date-column) and group by these values. This works if the week in DATEPART is aligned on Mondays as ISO 8601 requires. In outline:
SELECT DATEPART(year, date_column) AS yyyy,
DATEPART(week, date_column) AS ww,
...other material as required...
FROM SomeTableOrOther
WHERE ...appropriate filters...
GROUP BY yyyy, ww -- ...and other columns as necessary...