Working days between two dates in Snowflake - sql

Is there any ways to calculate working days between two dates in snowflake without creating calendar table, only using "datediff" function

After doing research work on snowflake datediff function, I have found the following conclusions.
DATEDIFF(DAY/WEEK, START_DATE, END_DATE) will calculate difference, but the last date will be considered as END_DATE -1.
DATEDIFF(WEEK, START_DATE, END_DATE) will count number of Sundays between two dates.
By summarizing these two points, I have implemented the logic below.
SELECT
( DATEDIFF(DAY, START_DATE, DATEADD(DAY, 1, END_DATE))
- DATEDIFF(WEEK, START_DATE, DATEADD(DAY, 1, END_DATE))*2
- (CASE WHEN DAYNAME(START_DATE) != 'Sun' THEN 1 ELSE 0 END)
+ (CASE WHEN DAYNAME(END_DATE) != 'Sat' THEN 1 ELSE 0 END)
) AS WORKING_DAYS

Here's an article with a calendar table solution that also includes a UDF to solve this in Snowflake (the business days are hard-coded, so that does require some maintenance, but you don't have to maintain a calendar table at least):
https://medium.com/dandy-engineering-blog/how-to-calculate-the-number-of-working-hours-between-two-timestamps-in-sql-b5696de66e51

The best way to count the number of Sundays between two dates is possibly as follows:
CREATE OR REPLACE FUNCTION SUNDAYS_BETWEEN(a DATE,b DATE)
RETURNS INTEGER
AS $$
FLOOR( (DAYOFWEEKISO(a) + DATEDIFF('days',a,b)) / 7 ,0)
$$
The above is better than using DATEDIFF(WEEK because the output of that function changes if the WEEK_START session parameter is altered away from the legacy default of 0

I have a way to calculate the number of business hours that elapse between a start time and end time but it only works if you make the following assumptions.
Asssume only 1 time zone for all timestamps
Any start or end times that occur outside of business hours should be rounded to nearest business hour time. (I.e. Assuming a schedule of 10:00am - 6:00 pm, timestamps occurring from midnight to 9:59am should be rounded to 10am, times after 6:00pm should be set to the next day at 10:00am)
Timestamps that occur on the weekends should be set to the opening time of the next business day. (In this case Monday at 10:00am)
My model does not account for any holidays.
If these 4 conditions are met then the following code should be enough for a rough estimate of business hours elapsed.
(DATEDIFF(seconds, start_time, end_time) --accounts for the pure number of seconds in between the two dates
- (DATEDIFF(DAY, start_time,end_time) * 16 * 60*60) --For every day between the two dates, we need to subtract out X number of hours. Where X is the number of hours not worked in a day. (i.e. for a standard 8 hour work day, set X =16. For a 10 hour day, set X = 14, etc.) We multiple by (60*60*16) to convert days into seconds.
- (DATEDIFF(WEEK, businness_hours_wait_time_start_at_est, businness_hours_first_touch_at_est)*(8*2*60*60)) --This accounts for the fact that weekends are not work days. Which is why we need to subtract an additional 8 hours for Saturday and Sunday.
)/(60*60*8) --We then divide by 60*60*8 to convert the business seconds into business days. We use 8 hours here instead of 24 hours since our "business day" is only 8 hours long.

Related

how to get data for last calender week in redshift

I have a below query that I run to extract material movements from the last 7 days.
Purpose is to get the data for the last calender week for certain reports.
select
*
From
redshift
where
posting_date between CURRENT_DATE - 7 and CURRENT_DATE - 1
That means I need to run the query on every Monday to get the data for the former week.
Sometimes I am too busy on Monday or its vacation/bank holiday. In that case I would need to change the query or pull the data via SAP.
Question:
Is there a function for redshift that pulls out the data for the last calender week regardless when I run the query?
I already found following solution
SELECT id FROM table1
WHERE YEARWEEK(date) = YEARWEEK(NOW() - INTERVAL 1 WEEK)
But this doesnt seem to be working for redshift sql
Thanks a lot for your help.
Redshift offers a DATE_TRUNC('week', datestamp) function. Given any datestamp value, either a date or datetime, it gives back the date of the preceding Sunday.
So this might work for you. It filters rows from the Sunday before last, up until but not including, the last Sunday, and so gets a full week.
SELECT id
FROM table1
WHERE date >= DATE_TRUNC('week', NOW()) - INTERVAL 1 WEEK
AND date < DATE_TRUNC('week', NOW())
Pro tip: Every minute you spend learning your DBMS's date/time functions will save you an hour in programming.

Get Number of Months and Days Between Dates

In PL/SQL I have 2 dates and I need to find out the number of months between them and the days as well. For example date 1 is 1/10/2022 and date 2 is 2/12/2022 that would be 1 month and 2 days. I'm pretty secure in obtaining the number of months, but the days number has been a thorn in my side. Sometimes it comes out correct, sometimes it comes out short and other times it comes out too far. I would imagine it is because of the different number of days in the months, but I can't prove that just yet. Any help is appreciated.
Oracle provides a months_between function to do the calculation.
That isn't a good idea as the number of days in a month varies, it's not exactly known what the decimal part of the number represents.
select months_between(date '2022-04-03', date '2022-01-01') from dual;
MONTHS_BETWEEN(DATE'2022-04-03',DATE'2022-01-01')
3.06451612903225806451612903225806451613
If you assume every month has 30 days, then comparisons over larger date ranges (years) will be out by more and more days the larger the difference gets.
However, if you combine methods, using months_between to get the months, and then assume 30 days for a month to get the days part from the remainder, it's more consistent over longer periods…
with dates as (select date '2022-01-01' as date_from, date '2022-04-03' as date_to from dual)
select months_between(date_to, date_from) ,trunc(months_between(date_to, date_from)) as months ,round(mod(months_between(date_to, date_from),1)*30) as days
from dates
MONTHS_BETWEEN(DATE_TO,DATE_FROM) MONTHS DAYS
3.06451612903225806451612903225806451613 3 2

Querying sales, grouped by weeks

I'm trying to put together a query in SQLLite for my app and wanted to ask for a little help please.
The query I want to write:
"I'm a user with UserID x. For my personal work week, which can start on any day D that I want (Sunday to Saturday, Monday to Sunday, etc), I'd like to see which work week was my worst, which was my best, in terms of sales.
To keep it simple "worst" and "best" simply means the highest and lowest sales summed for that week.
If everyone's work week started on Sunday, this is easy, but this is not the case. I have to overcome a SQL challenge of grouping all of the rows in the database table not just by week, but a custom week (users define which day starts and ends the week).
As an example, if my work week starts on Sunday, then this past week, the week of May 28th is a Sunday and is the beginning of my work week (and it ends this Saturday June 3rd). I would follow this pattern for all records in the table.
However, a different user could have their work week start on Monday, May 29th, and end on Friday, June 2nd.
So this means for user 1, I'd want to group his rows from the starting day of Sunday, and the ending day of Saturday (and then aggregate them all, and take the first and last records for sales).
For user 2, however, I'd want to group his records in the date ranges of Monday to Sunday
Here is where I'm at so far. I'm close I think.
(Note that I store the date as a unix timestamp in milliseconds, hence the division by 1000 and the unixepoch part). The +d part is actually an integer based on the start of the day, but I haven't figured out what that number should really be. Just when I think I get it, it fails for someone else's day.
SELECT 'Date', SUM(Amount) 'Amount'
FROM Sales WHERE UserID = x
GROUP BY CAST(( julianday((datetime(CreationDate / 1000, 'unixepoch', 'localtime')) + d) / 7 ) AS INT)
Does anyone think they could give me a hand? :)
Thanks so much!
EDIT
Thank you so much for your help!
For the +d question (what should the value 'd' be to offset)?
Here is what I found after testing, and this works as far as I can tell. I understand Sqllite uses 0 as Sunday, 1 as Monday, etc, and I understand we were grouping and dividing by 7 (for 7 days in the week), but any idea why these would be the right values for 'd' as the offset? It seems to be working now. I see the pattern goes 2,1,0,6,5,4,3 but kinda strange order to go in eh?
if (day == Sunday) //if your work week starts on Sunday, d=2
return 2
else if (day == Monday)
return 1
else if (day == Tuesday)
return 0
else if (day == Wednesday)
return 6
else if (day == Thursday)
return 5
else if (day == Friday)
return 4
else if (day == Saturday)
return 3
You are very close.
You are adding d to the datetime. I don't know whether this actually adds days. I could not find out what happens if you add an integer to a datetime in SQLite. To play it save, add the day to the julian day instead. You don't have to first get a datetime and from this the julian day by the way, you can do that in one step:
julianday(CreationDate / 1000, 'unixepoch', 'localtime') + d
This is the only real flaw I see in your query.
the Julian day is a fractional number such as 2457907.5. When you invoke a division with / on it, you get a fractional result. I see that you convert this result to INT, but I would suggest to convert to INT first and only then divide which would make this an integer division implicitly.
cast(julianday(CreationDate / 1000, 'unixepoch', 'localtime') + d as int) / 7
This is just for readability; I get a day number (2457907 rather than some decimal 2457907.5) and integer-divide by 7 (e.g. 2457907 / 7 = 351129).
The whole query:
SELECT
MIN(DATE(CreationDate / 1000, 'unixepoch', 'localtime')) AS from_date,
MAX(DATE(CreationDate / 1000, 'unixepoch', 'localtime')) AS till_date,
SUM(Amount) AS total
FROM Sales
WHERE UserID = x
GROUP BY CAST(JULIANDAY(CreationDate / 1000, 'unixepoch', 'localtime') + d as INT) / 7
ORDER BY SUM(Amount);
from_date and till_date don't always represent the full seven days though, but only the worked days (e.g. in a week from Sunday to Saturday, but worked only Monday, Wednesday and Friday, it would show the dates for Monday and Friday). It would take slightly more work to show the real week. (I better don't try this now, for it's so easy to be one day off, when not being able to try the query.)
EDIT: Here is my try on the start and end days of the weeks. When we invoke DATE on a floating point value, this value is considered a Julian day. (Maybe it would work with the integer, too, I can not be sure from the documentation.)
SELECT
DATE(CAST(CAST(JULIANDAY(CreationDate / 1000, 'unixepoch', 'localtime') + d as INT) / 7 as REAL)) AS from_date,
DATE(CAST(CAST(JULIANDAY(CreationDate / 1000, 'unixepoch', 'localtime') + d as INT) / 7 as REAL), '+6 day') AS till_date,
MIN(DATE(CreationDate / 1000, 'unixepoch', 'localtime')) AS first_working_day,
MAX(DATE(CreationDate / 1000, 'unixepoch', 'localtime')) AS last_working_day,
SUM(Amount) AS total
FROM Sales
WHERE UserID = x
GROUP BY CAST(JULIANDAY(CreationDate / 1000, 'unixepoch', 'localtime') + d as INT) / 7
ORDER BY SUM(Amount);
Please try to execute below query:
select
min(to_char(to_date(order_date,'mm/dd/yyyy'),'Day'))
keep(dense_rank first order by sum(sales) desc) best_day,
min(to_char(to_date(order_date,'mm/dd/yyyy'),'Day'))
keep(dense_rank last order by sum(sales) desc)worst_day
from orders
where userid=x
group by to_char(to_date(order_date,'mm/dd/yyyy'),'Day');

datetime manipulation: replace all dates with 00:00 time with 24:00 the previous day

I have a table described here: http://sqlfiddle.com/#!3/f8852/3
The date_time field for when the time is 00:00 is wrong. For example:
5/24/2013 00:00
This should really be:
5/23/2013 24:00
So hour 00:00 corresponds to the last hour of the previous day (I didn't create this table but have to work with it). Is there way quick way when I do a select I can replace all dates with 00:00 as the time with 24:00 the previous day? I can do it easily in python in a for loop but not quite sure how to structure it in sql. Appreciate the help.
All datetimes are instants in time, not spans of a finite length, and they can exist in only one day. The instant that represents Midnight is by definition, in the next day, the day in which it is the start of the day, i.e., a day is closed on its beginning and open at its end, or, to phrase it again, valid allowable time values within a single calendar date vary from 00:00:00.00000, to 23:59:59.9999.
This would be analogous to asking that the minute value within an hour be allowed to vary from 1 to 60, instead of from 0 to 59, and that the value of 60 was the last minute of the previous hour.
What you are talking about is only a display issue. Even if you could enter a date as 1 Jan 2013 24:00, (24:00:00 is not a legal time of day) it would be entered as a datetime at the start of the date 2 Jan, not at the end of 1 Jan.
One thing that illustrates this, is to notice that, because of rounding (SQL can only resolve datetimes to within about 300 milleseconds), if you create a datetime that is only a few milleseconds before midnight, it will round up to midnight and move to the next day, as can be seen by running the following in enterprise manager...
Select cast ('1 Jan 2013 23:59:59.999' as datetime)
SQL server stoers all datetimes as two integers, one that represents the number days since 1 Jan 1900, and the other the number of ticks (1 tick is 1/300th of a second, about 3.33 ms), since midnight. If it has been zero time interval since Midnight, it is stll the same day, not the previous day.
If you have been inserting data assuming that midnight 00:00:00 means the end of the day, you need to fix that.
If you need to correct your existing data, you need to add one day to every date in your database that has midnight as it's time component, (i.e., has a zero time component).
Update tbale set
date_time = dateAdd(day, 1, date_time)
Where date_time = dateadd(day, datediff(day, 0, date_time), 0)

Determining if a leap day falls between two days with DB2 SQL

I have a table with two dates, "Start_Date" and "End_Date". In DB2 SQL, is there a way to determine if a leap day falls between these two dates?
Thank you!
Sure, you can do this using some date math and the DAYS function, by comparing the number of days between the the start and end date to the number of days between the start date and end date when they've both been shifted by 1 year.
If the number of days between the two dates is the same in both cases, then no leap day has occurred. If the number of days differs, then there has been at least 1 leap day.
This expression will return the number of leap days:
select
( DAYS(end_date + 1 year) - DAYS(start_date + 1 year) ) -
( DAYS(end_date) - DAYS(start_date) )
from
sysibm.sysdummy1
This should work as long as end_date >= start_date.
It's trivial to encapsulate this into a scalar User Defined Function.