Working hours between two dates in Snowflake - snowflake-cloud-data-platform

How to calculate working hours between two dates in snowflake without creating tables?
i have tried function like (datediff) and timestamp but i could not reach the solution
i would like to get something like that
+---------------------+---------------------+---------------+
| create_Task | Solved_Task | BusinessHours |
+---------------------+---------------------+---------------+
| 2012-03-05 09:00:00 | 2012-03-05 15:00:00 | 6.000000 |
| 2012-03-05 10:00:00 | 2012-03-06 10:00:00 | 8.000000 |
| 2012-03-05 11:00:00 | 2012-03-06 10:00:00 | 7.000000 |
| 2012-03-05 10:00:00 | 2012-03-06 15:00:00 | 13.000000 |
| 2012-03-09 16:00:00 | 2012-03-12 10:00:00 | 2.000000 |
| 2012-03-06 16:00:00 | 2012-03-15 10:00:00 | 50.000000 |
| 2012-03-09 16:00:00 | 2012-03-19 10:00:00 | 42.000000 |
+---------------------+---------------------+---------------+
and i would like to specify the working hours so then i can calculate the business hours

One way to do this is by creating a working hours table. Then you can run a fairly simple query:
select
t.id
, sum(datediff(‘second’,
-- calculate the max of the two start time
(case when t.start <=
w.working_day_start_timestamp
then w.working_day_start_timestamp
else t.start
end),
-- calculate the min of the two end times
(case when t.end >=
w.working_day_end_timestamp
then w.working_day_end_timestamp
else t.end
end)
)) / 3600 -- convert to hourly
as working_hour_diff
from
working_days_times w,
cross join time_intervals t
where -- select all intersecting intervals
(
t.start <= w.working_day_end_timestamp
and
t.end >= w.working_day_start_timestamp
)
and -- select only working days
w.is_working_day
group by
t.id
If you need a function, this article describes the implementation of a Javascript UDF in Snowflake:
https://medium.com/dandy-engineering-blog/how-to-calculate-the-number-of-working-hours-between-two-timestamps-in-sql-b5696de66e51

Related

T-SQL Calculate time between changing status per hour

EDIT: I changed my example and made it more simple. First Quote is how the source table looks like, second quote is how the result should look like.
Hello everyone,
I have multiple parking that only sends changing states.
It sends a "1" when a car arrived at the parking, then it doesn't send anything until the car leaves again. At that moment the parking sends a "0". I need to do analysis over a long time, so it would be awesome to see the amount of time per hour or so to not get too many rows (compared by minute).
The data looks like this (as requested I reduce it to parking-ID 10 and just the last record from 19.12. and the records from 20.12.):
+------------+------------------+--------+-------------+
| Parking-ID | DateTime | Status | Comment |
+------------+------------------+--------+-------------+
| 10 | 20.12.2019 16:35 | 0 | Car left |
+------------+------------------+--------+-------------+
| 10 | 20.12.2019 08:22 | 1 | Car arrived |
+------------+------------------+--------+-------------+
| 10 | 19.12.2019 22:47 | 0 | Car left |
+------------+------------------+--------+-------------+
Now to not make it too easy for me, next to the "free" and "taken" status there is also a warm status. 1 hour after a car left the parking should be marked as "warm" because some cars have to come and go fast in a few minutes and this time range should be shown as "warm".
To not get too many rows (like for every minute), I would appreciate if it would be possible to get the summary per hour. For my analysis I should be able to see how many hours per day the parking was taken, how many hours it was warm and how many hours it was free.
So the result should look something like this (for Parking-ID 10 and for 20.12.2019):
+------------+------------------+--------+----------+---------+
| Parking-ID | DateTime | Status | Duration | Comment |
+------------+------------------+--------+----------+---------+
| 10 | 20.12.2019 23:00 | 0 | 1.00 | Free |
+------------+------------------+--------+----------+---------+
| 10 | 20.12.2019 22:00 | 0 | 1.00 | Free |
+------------+------------------+--------+----------+---------+
| 10 | 20.12.2019 21:00 | 0 | 1.00 | Free |
+------------+------------------+--------+----------+---------+
| 10 | 20.12.2019 20:00 | 0 | 1.00 | Free |
+------------+------------------+--------+----------+---------+
| 10 | 20.12.2019 19:00 | 0 | 1.00 | Free |
+------------+------------------+--------+----------+---------+
| 10 | 20.12.2019 18:00 | 0 | 1.00 | Free |
+------------+------------------+--------+----------+---------+
| 10 | 20.12.2019 17:00 | 0 | 0.42 | Free |
+------------+------------------+--------+----------+---------+
| 10 | 20.12.2019 17:00 | 2 | 0.58 | Warm |
+------------+------------------+--------+----------+---------+
| 10 | 20.12.2019 16:00 | 2 | 0.42 | Warm |
+------------+------------------+--------+----------+---------+
| 10 | 20.12.2019 16:00 | 1 | 0.58 | Taken |
+------------+------------------+--------+----------+---------+
| 10 | 20.12.2019 15:00 | 1 | 1.00 | Taken |
+------------+------------------+--------+----------+---------+
| 10 | 20.12.2019 14:00 | 1 | 1.00 | Taken |
+------------+------------------+--------+----------+---------+
| 10 | 20.12.2019 13:00 | 1 | 1.00 | Taken |
+------------+------------------+--------+----------+---------+
| 10 | 20.12.2019 12:00 | 1 | 1.00 | Taken |
+------------+------------------+--------+----------+---------+
| 10 | 20.12.2019 11:00 | 1 | 1.00 | Taken |
+------------+------------------+--------+----------+---------+
| 10 | 20.12.2019 10:00 | 1 | 1.00 | Taken |
+------------+------------------+--------+----------+---------+
| 10 | 20.12.2019 09:00 | 1 | 1.00 | Taken |
+------------+------------------+--------+----------+---------+
| 10 | 20.12.2019 08:00 | 1 | 0.63 | Taken |
+------------+------------------+--------+----------+---------+
| 10 | 20.12.2019 08:00 | 0 | 0.37 | Free |
+------------+------------------+--------+----------+---------+
| 10 | 20.12.2019 07:00 | 0 | 1.00 | Free |
+------------+------------------+--------+----------+---------+
| 10 | 20.12.2019 06:00 | 0 | 1.00 | Free |
+------------+------------------+--------+----------+---------+
| 10 | 20.12.2019 05:00 | 0 | 1.00 | Free |
+------------+------------------+--------+----------+---------+
| 10 | 20.12.2019 04:00 | 0 | 1.00 | Free |
+------------+------------------+--------+----------+---------+
| 10 | 20.12.2019 03:00 | 0 | 1.00 | Free |
+------------+------------------+--------+----------+---------+
| 10 | 20.12.2019 02:00 | 0 | 1.00 | Free |
+------------+------------------+--------+----------+---------+
| 10 | 20.12.2019 01:00 | 0 | 1.00 | Free |
+------------+------------------+--------+----------+---------+
| 10 | 20.12.2019 00:00 | 0 | 1.00 | Free |
+------------+------------------+--------+----------+---------+
Does someone have a good approach? I already searched and tried but couldn't find a working approach.
Thank you and best regards
First, your duration output is still wrong,if you cross check.
For example 20.12.2019 08:00 it should be 22.00 and 38.00.Clear this ?
Secondly,Two rows on for 20.12.2019 17:00 is not clear.Why it it will contain 2 rows ?Clear this also.
Create Calendar table in whatever way you want.
CREATE TABLE [dbo].[CalendarDate](
[Dates] [datetime2](0) NOT NULL
PRIMARY KEY CLUSTERED
(
[Dates] ASC
)
) ON [PRIMARY]
GO
insert into [CalendarDate] with(tablock)
select top (100000)
dateadd(day,ROW_NUMBER()over(order by (select null))
,'1950-01-01 00:00:00')
from sys.objects a, sys.objects b, sys.objects c
Then Create number table also
-- Real or #temp your wish
create Table #Number(Hrs int)
insert into #Number (Hrs)
select top 24 ROW_NUMBER()over(order by number)-1
from master..spt_values
Your table sample data.I have kept Parking status in seperate table,follow Normalization.
-- your real table
create table #Parking( ParkingID int, ParkingDateTime Datetime2(0),ParkingStatus tinyint )
insert into #Parking values(10,'2019-12-20 16:35',0),(10,'2019-12-20 08:22',1)
,(10,'2019-12-19 22:47',0)
-- It should be your real table
create table #ParkingStatus( ParkingStatus tinyint,StatusName varchar(50) )
insert into #ParkingStatus values(0,'Car left')
,(1,'Car arrived'),(2,'Free'),(3,'Taken')
,(4,'Warm')
The Script,
declare #From Datetime2(0)='2019-12-20'
declare #To Datetime2(0)=dateadd(second,-1,dateadd(day,1,#From))
-- Put require data in #temp table,since it will be use many times
create table #ParkingTemp(ParkingID int,ParkingDateTime Datetime2(0)
,ParkingDate Date,ParkingStatus tinyint )
insert into #ParkingTemp (ParkingID,ParkingDateTime
,ParkingDate,ParkingStatus)
select P.ParkingID,ParkingDateTime
,p.ParkingDateTime
,ParkingStatus
from #Parking P
where ParkingDateTime>=#From
and ParkingDateTime<=#To
;With CTE as
(
select ParkingID,ParkingDateTime ,count(*)+1 SplitCount
,ParkingStatus as InitialStatus
from #ParkingTemp
group by ParkingID,ParkingDateTime,ParkingStatus
)
, DistinctIDCTE as
(
select distinct ParkingID
from #ParkingTemp
)
, CTE1 as
(
select Dates
,dateadd(hour,hrs,Dates)ReportDateTime
,ParkingID
from [CalendarDate],#Number N,DistinctIDCTE
where dates>=#From and Dates<=#To
),
CTE2 as
(
select c.ParkingID
,dateadd(minute,-datepart(minute,ParkingDateTime),ParkingDateTime) ParkingDate
,ParkingDateTime,hrs as rownum,InitialStatus
from CTE C
cross apply(select hrs from #Number N where c.SplitCount>n.Hrs)ca
)
,CTE3 as
(
select parkingid,ParkingDateTime as FromDatetime
,ToDatetime
from #ParkingTemp C
cross apply(select top 1 ParkingDateTime as ToDatetime
from #ParkingTemp C1 where c.ParkingID=c1.ParkingID
and c1.ParkingStatus=0 and
c1.ParkingDateTime>c.ParkingDateTime
order by c1.ParkingDateTime )c1
where ParkingStatus=1
)
,CTE4 as
(
select c.ParkingID,c.ReportDateTime
from CTE1 C
outer apply(select top 1 FromDatetime ,ToDatetime
from CTE3 c1 where c.ParkingID=c1.ParkingID
and (ReportDateTime>= FromDatetime and ReportDateTime<=ToDatetime))ca
)
--select * from CTE2
,CTE5 as
(
select c4.ParkingID,c4.ReportDateTime
,case when rownum=0 and InitialStatus=1 then 2
when rownum=1 and InitialStatus=1 then 3
when rownum=0 and InitialStatus=0 then 4
when rownum=1 and InitialStatus=0 then 3
else 2 end as ParkingStatusid
,case when rownum=0 then datediff(minute,ReportDateTime,ParkingDateTime)
when rownum=1 then 60- datepart(minute,ParkingDateTime)
else 1.00 end Duration
,ParkingDateTime
,rownum,InitialStatus
from CTE4 c4
left join CTE2 c2 on c4.ParkingID=c2.ParkingID and c2.ParkingDate =c4.ReportDateTime
)
select c5.ParkingID,c5.ReportDateTime,c5.ParkingStatusid
,Duration,PS.StatusName AS Comment
from CTE5 c5
inner join #ParkingStatus ps on c5.ParkingStatusid=ps.ParkingStatus
order by ReportDateTime desc
Clean Up
drop table #Parking,#ParkingStatus,#Number,#ParkingTemp
Alternate and improve :
;WITH CTE
AS (SELECT ParkingID,
ParkingDateTime,
COUNT(*) + 1 SplitCount,
ParkingStatus AS InitialStatus
FROM #ParkingTemp
GROUP BY ParkingID,
ParkingDateTime,
ParkingStatus),
DistinctIDCTE
AS (SELECT DISTINCT
ParkingID
FROM #ParkingTemp),
CTE1
AS (SELECT Dates,
DATEADD(hour, hrs, Dates) ReportDateTime,
ParkingID
FROM [CalendarDate],
#Number N,
DistinctIDCTE
WHERE dates >= #From
AND Dates <= #To),
CTE2
AS (SELECT c.ParkingID,
DATEADD(minute, -DATEPART(minute, ParkingDateTime), ParkingDateTime) ParkingDate,
ParkingDateTime,
hrs AS rownum,
InitialStatus
FROM CTE C
CROSS APPLY
(
SELECT hrs
FROM #Number N
WHERE c.SplitCount > n.Hrs
) ca),
CTE5
AS (SELECT c4.ParkingID,
c4.ReportDateTime,
CASE
WHEN rownum = 0
AND InitialStatus = 1
THEN 2
WHEN rownum = 1
AND InitialStatus = 1
THEN 3
WHEN rownum = 0
AND InitialStatus = 0
THEN 4
WHEN rownum = 1
AND InitialStatus = 0
THEN 3
ELSE 2
END AS ParkingStatusid,
CASE
WHEN rownum = 0
THEN DATEDIFF(minute, ReportDateTime, ParkingDateTime)
WHEN rownum = 1
THEN 60 - DATEPART(minute, ParkingDateTime)
ELSE 1.00
END Duration,
ParkingDateTime,
rownum,
InitialStatus
FROM CTE1 c4
LEFT JOIN CTE2 c2 ON c4.ParkingID = c2.ParkingID
AND c2.ParkingDate = c4.ReportDateTime)
SELECT c5.ParkingID,
c5.ReportDateTime,
c5.ParkingStatusid,
Duration,
PS.StatusName AS Comment
FROM CTE5 c5
INNER JOIN #ParkingStatus ps ON c5.ParkingStatusid = ps.ParkingStatus
ORDER BY ReportDateTime DESC;
Note : clear my doubts.Throw diffrent sample data such within one hour there more than 2 parking staus for same parkingid.

How to sum up values in a column for each week number?

I need to sum up values from Money column for each WeekNumber.
Now I have view:
WeekNumber | DayTime | Money
---------------------------------------
1 | 2012-01-01 | 20.4
1 | 2012-01-02 | 30.5
1 | 2012-01-03 | 55.1
2 | 2012-02-01 | 67.3
2 | 2012-02-02 | 33.4
3 | 2012-03-01 | 11.8
3 | 2012-03-04 | 23.9
3 | 2012-03-05 | 34.3
4 | 2012-04-01 | 76.6
4 | 2012-04-02 | 90.3
Tsql:
SELECT datepart(week,DayTime) AS WeekNumber, DayTime, Money FROM dbo.Transactions
In conclusion, I would like to get something like this:
WeekNumber | DayTime | Sum
---------------------------------------
1 | 2012-01-01 | 106
2 | 2012-02-02 | 100.7
3 | 2012-03-03 | 470
4 | 2012-04-01 | 166.9
DayTime should be random for each Week Number but exists in column DayTime from view above.
Please, be free to write your ideas. Thanks.
SELECT datepart(week,DayTime) AS WeekNumber
, MIN(DayTime) DayTime --<-- Instead of random get first date from your data in that week
, SUM(Money) AS [Sum]
FROM dbo.Transactions
GROUP BY datepart(week,DayTime)
Try this
SELECT datepart(week,DayTime) AS WeekNumber, SUM(Money) FROM dbo.Transactions GROUP BY WeekNumber
As you will have number of rows for each week you cannot get DayTime with the same table. There are other ways to add that too like JOIN
Change your SQL to sum the money column. Like this
SELECT
datepart(week,DayTime) AS WeekNumber,
DayTime, Money = SUM(Money)
FROM dbo.Transactions
GROUP BY datepart(week,DayTime),DayTime
SELECT datepart(week, DayTime) AS WeekNumber
,MIN(DayTime)
,SUM(MONEY)
FROM dbo.Transactions
GROUP BY datepart(week, DayTime)

Create a View From Max1 of One Column and the Max2 of Another if there are multiple Max1 for group

From my table, I want to select for each project ID the ID with the latest deploymentDate and if there are two identical latest deployment dates for the same project ID, select the ID with the latest submittedOn datetime. So if my table looks like this:
id | projectId | deploymentDate | submittedOn |
1 | 1 | 2017-01-02 | 2017-01-02 13:00:00 |
2 | 1 | 2017-01-04 | 2017-01-04 11:00:00 |
3 | 2 | 2017-01-06 | 2017-01-06 17:00:00 |
4 | 2 | 2017-01-06 | 2017-01-01 12:00:00 |
5 | 3 | 2017-01-02 | 2017-01-02 13:30:00 |
6 | 3 | 2017-01-02 | 2017-01-05 15:00:00 |
7 | 3 | 2017-01-02 | 2017-01-04 10:00:00 |
The desired rows are:
id | projectId | deploymentDate | submittedOn |
2 | 1 | 2017-01-04 | 2017-01-04 11:00:00 |
3 | 2 | 2017-01-06 | 2017-01-06 17:00:00 |
6 | 3 | 2017-01-02 | 2017-01-05 15:00:00 |
You can try the below. Adjust the sorting in the row_number as needed.
select
a.id,
a.projectid,
a.deploymentdate,
a.submittedOn
from project a
inner join
(select
a.id,
row_number() over (partition by projectid order by deploymentdate desc, submittedOn desc, id) as rid
from project
) as b
on b.id = a.id
and b.rid = 1
This would work:
select t.id, latest.*
from tab t join (
select projectid, max(deploymentdate) deploymentdate, max(submittedon) submittedon
from tab
group by projectid
) latest on t.projectid = latest.projectid and t.deploymentdate = latest.deploymentdate and t.submittedon = latest.submittedon
I found the latest based on the project id and then, joined with the source table to find the corresponding id.

Issue with Running Total Still

I still have an issue with working out the best way to calculate a running balance.
I am going to be using this code in a Rent Statement that I am going to produce in SSRS, but the problem I am having is that I can't seem to work out how to achieve a running balance.
SELECT rt.TransactionId,
rt.TransactionDate,
rt.PostingDate,
rt.AccountId,
rt.TotalValue,
rab.ClosingBalance,
ROW_NUMBER()OVER(PARTITION BY rt.AccountId ORDER BY rt.PostingDate desc) AS row,
CASE WHEN ROW_NUMBER()OVER(PARTITION BY rt.AccountId ORDER BY rt.PostingDate desc) = 1
THEN ISNULL(rab.ClosingBalance,0)
ELSE 0 end
FROM RentTransactions rt
--all accounts for the specific agreement
INNER JOIN (select raa.AccountId
from RentAgreementEpisode rae
inner join RentAgreementAccount raa on raa.AgreementEpisodeId = rae.AgreementEpisodeId
where rae.AgreementId=1981
) ij on ij.AccountId = rt.AccountId
LEFT JOIN RentBalance rab on rab.AccountId = rt.AccountId AND rt.PostingDate BETWEEN rab.BalanceFromDate AND isnull(rab.BalanceToDate,dateadd(day, datediff(day, 0, GETDATE()), 0))
What this gives me are the below results- I have included the results below -
So my code is sorting my transactions in the order I want and also is row numbering them in the correct order as well.
Where the Row Number is 1 - I need it to pull back the balance on that account at that point in time, which is what I am doing....BUT I am then unsure how I then get my code to start subtracting the proceeding row - so in this case The current figure of 1118.58 would need the Total Value in Row 2 = 91.65 subtracted from it - so the running balance for row 2 would be 1026.93 and so on...
Any help would be greatly appreciated.
Assuming you have all the transactions being returned in your query you can calculate a running total using the over clause, you just need to start at the beginning of your dataset rather than working backwards from your current balance:
declare #t table(d date,v decimal(10,2));
insert into #t values ('20170101',10),('20170102',20),('20170103',30),('20170104',40),('20170105',50),('20170106',60),('20170107',70),('20170108',80),('20170109',90);
select *
,sum(v) over (order by d
rows between unbounded preceding
and current row
) as RunningTotal
from #t
order by d desc
Output:
+------------+-------+--------------+
| d | v | RunningTotal |
+------------+-------+--------------+
| 2017-01-09 | 90.00 | 450.00 |
| 2017-01-08 | 80.00 | 360.00 |
| 2017-01-07 | 70.00 | 280.00 |
| 2017-01-06 | 60.00 | 210.00 |
| 2017-01-05 | 50.00 | 150.00 |
| 2017-01-04 | 40.00 | 100.00 |
| 2017-01-03 | 30.00 | 60.00 |
| 2017-01-02 | 20.00 | 30.00 |
| 2017-01-01 | 10.00 | 10.00 |
+------------+-------+--------------+

Payment analysis - SQL server query

Please can someone give me some pointers.
We are required to send out a Statutory Arrears Notification.
The criteria for sending it out is when an account has missed 2 full payments or the equivalent of. I.e if they are due to pay £100 a month but only paid £50 over 4 months, they are due the notice.
I have pulled a query which separates out the Repayment Schedule into date ranges, and between each range I have tallied up the payments made within that period. Also for each result I have tallied up DueToDate and PaidToDate.
The issue I have is working out some form of scoring system for each row, then at the end of the query, tally up to give an overall score which determines if the notice is due or not.
Results structure is like this.
DueDate | DateFrom | DateTo | AmountDue | AmountPaid | DueToDate | PaidToDate
If you mean that you already have a query that produces a result set like the following
DueDate | DateFrom | DateTo | AmountDue | AmountPaid | DueToDate | PaidToDate
20120301 | 20120201 | 20120229 | 100.00 | 50.00 | 100.00 | 50.00
20120401 | 20120301 | 20120331 | 100.00 | 50.00 | 200.00 | 100.00
20120501 | 20120401 | 20120430 | 100.00 | 50.00 | 300.00 | 150.00
20120601 | 20120501 | 20120531 | 100.00 | 50.00 | 400.00 | 200.00
Then here are two ways forward depending on whether the AmountDue per month is constant. If it is, then you can use
select *
from QueryResult
where DueToDate - PaidToDate >= 2 * AmountPaid;
If it is not constant, then you can use LAG() in SQL Server 2012 to add AmountPaid from the prior row to the current
;WITH Lagged AS (
select *, PriorAmount = LAG(AmountPaid, 1, 0) OVER (order by DueDate)
from QueryResult
)
select *
from Lagged
where DueToDate - PaidToDate >= AmountPaid + PriorAmount;
Or just keep a running total in yet another column in your original query, e.g.
DueDate | DateFrom | DateTo | AmountDue | AmountPaid | DueToDate | PaidToDate | TwoPeriods
20120301 | 20120201 | 20120229 | 100.00 | 50.00 | 100.00 | 50.00 | 100.00
20120401 | 20120301 | 20120331 | 100.00 | 50.00 | 200.00 | 100.00 | 200.00
20120501 | 20120401 | 20120430 | 100.00 | 50.00 | 300.00 | 150.00 | 200.00
20120601 | 20120501 | 20120531 | 100.00 | 50.00 | 400.00 | 200.00 | 200.00

Resources