Convert decimal minutes into Minutes:Seconds? - sql

Anyone know if there's a function to convert decimal minutes into Minutes:Seconds in
Snowflake?
For example:
input: 8.5 (as in 8.5 minutes)
output: 8:30 (as in 8 minutes 30 seconds)
Source: https://twitter.com/jasongrahn/status/1420768503133401095

Using TIMEADD to add number of seconds since midnight:
SELECT TIMEADD(second, 8.5 * 60, '00:00'::TIME) AS res;
--arg
Output:
RES
00:08:30

Putting the two excellent solutions already provided together:
SELECT TIME_FROM_PARTS(0,0,8.5*60)
This is nice because it's straight SQL - UDF's may not perform as quick
TIME_FROM_PARTS allows you to pass not just seconds but also minutes,nanoseconds etc.
If you go over 1 day just switch to TIMESTAMP_FROM_PARTS
Below I pass in 5.1 million nanoseconds returning 1 month, 28 days and 40 minutes. This demonstrates the extendibility of the built in functions that can always be used elegantly.

Just build a date from parts, and separate the decimals from the integer minutes.
You can create a SQL UDF for convenience:
create function from_decimal_minutes(minutes float)
returns time
as $$
time_from_parts(0, floor(minutes), 60*(minutes-floor(minutes)))
$$
;
select from_decimal_minutes(8.5);
-- 00:08:30
;

Related

Converting an nvarchar into a custom time format to query upon duration

I am working with a table that has a variety of column types in rather specific and strange formats. Specifically I have a column, 'Total_Time' that measures a duration in the format:
days:hours:minutes (d:hh:mm)
e.g 200:10:03 represents 200 days, 10 hours and 3 minutes.
I want to be able to run queries against this duration in order to filter upon time durations such as
SELECT * FROM [TestDB].[dbo].[myData] WHERE Total_Time < 0:1:20
Ideally this would provide me with a list of entries whose total time duration is less than 1 hour and 20 minutes. I'm not aware of how this is possible in an nvarchar format so I would appreciate any advice on how to approach this problem. Thanks in advance...
I would suggest converting that value to minutes, and then passing the parametrised value as minutes as well.
If we can assume that there will always be a days, hours, and minutes section (so N'0:0:10' would be used to represent 10 minutes) you could do something like this:
SELECT *
FROM (VALUES(N'200:10:03'))V(Duration)
CROSS APPLY (VALUES(CHARINDEX(':',V.Duration)))H(CI)
CROSS APPLY (VALUES(CHARINDEX(':',V.Duration,H.CI+1)))M(CI)
CROSS APPLY (VALUES(TRY_CONVERT(int,LEFT(V.Duration,H.CI-1)),TRY_CONVERT(int,SUBSTRING(V.Duration,H.CI+1, M.CI - H.CI-1)),TRY_CONVERT(int, STUFF(V.Duration,1,M.CI,''))))DHM(Days,Hours,Minutes)
CROSS APPLY (VALUES((DHM.Days*60*24) + (DHM.Hours * 60) + DHM.Minutes))D(Minutes)
WHERE D.[Minutes] < 80; --1 hour 20 minutes = 80 minutes
If you can, then ideally you should be fixing your design and just storing the value as a consumable value (like an int representing the number of minutes), or at least adding a computed column (likely PERSISTED and indexed appropriately) so that you can just reference that.
If you're on SQL Server 2022+, you could do something like this, which is less "awful" to look at:
SELECT *
FROM (VALUES(N'200:10:03'))V(Duration)
CROSS APPLY(SELECT SUM(CASE SS.ordinal WHEN 1 THEN TRY_CONVERT(int,SS.[value]) * 60 * 24
WHEN 2 THEN TRY_CONVERT(int,SS.[value]) * 60
WHEN 3 THEN TRY_CONVERT(int,SS.[value])
END) AS Minutes
FROM STRING_SPLIT(V.Duration,':',1) SS)D
WHERE D.[Minutes] < 80; --1 hour 20 minutes = 80 minutes;

Calculate average age of items in ruby on rails

I wanting to find the average age of Ticket.opened. Opened is a scope on my ticket model.
This returns a nice result for one ticket.
time_ago_in_words(Ticket.last.created_at.to_time)
What I am wanting is to do something like this
age = []
Ticket.opened.each do |t|
age = time_ago_in_words(t.created_at.to_time)+[]
end
average_age = age/Ticket.opened.count
I am aware that this code is awful, but it is my best attempt at explaining what I am looking for.
If you use PostgreSQL then you could fetch average created_at time in a single SQL query:
avg_created_at = Ticket.opened.average('extract(epoch from tickets.created_at)')
time_ago_in_words(Time.at(avg_created_at))
EXTRACT(EPOCH FROM ...) function returns created_at value as the number of seconds since 1970-01-01 00:00:00 UTC. This can be used in an aggregate function, such as average
I'm sure MySQL allows such queries too.
I believe you can get this by converting the times to integers and calculating the average. Roughly speaking:
times = [2.days.ago, 3.days.ago, 4.days.ago]
average_ticket_time = Time.at(times.map(&:to_i).sum / times.count) # convert to ints and get average
time_ago = Time.now - average_ticket_time # time ago from now
readable_time_ago = (time_ago / 60 / 60).round # divide by 60 secs then 60 mins; round to get closest hour
# => 72 (hours ago)
Or in one line:
((Time.now - (Time.at(times.map(&:to_i).sum / times.count))) / 60 / 60).round
So, on average, 72 hours ago.
You can also divide by an additional 24 to get the time in days: 3 days in this case, which is what you'd expect in this simple example.
In your example, your times array would be the following:
times = Ticket.opened.map { |ticket| ticket.created_at.to_i }
# or perhaps the following, check the performace:
times = Ticket.opened.pluck(:created_at).map(&:to_i)
Hope that helps - let me know how you get on.

Vertica different calculations than in PostgreSQL

I have one query:
SELECT CAST(((stats.ts_spawn - 1427835600) / 86400) * 86400 +
1427835600 AS INTEGER) AS anon_1 FROM stats WHERE stats.ts_spawn >
1427835600 AND stats.ts_spawn < 1428440399 GROUP BY anon_1 order by anon_1;
I'm expecting to get start of the each day in a week.
Result in Postgresql:
1427835600
1427922000
1428008400
1428094800
1428181200
1428267600
1428354000
Vertica returns start of each hour of each day of the week:
1427839200
1427842800
1427846400
1427850000
... and so on, total 167 records(24 * 7 - 1)
I have no idea how to modify this query.
The second one is obviously resulting in a float not an integer in division. In Vertica documents we can read this:
the Vertica 6 release introduced a behavior change when dividing integers using the / operator
If you want the query to behave the same on both systems either change the configuration option as mentioned in that doc or use the Floor() function on the result of division.

Hours and Average Hours Worked per Day, by Department

I'm trying to get an estimate of how many hours people worked during a set period of time. I want to show this by department and by what area they were working in. Right now I have this:
SELECT M.MemberDepartmentID,T.TaskName,
COUNT(DATEDIFF(HOUR, TT.StartTime, TT.EndTime)) 'Hours',
AVG(DATEDIFF(HOUR, TT.StartTime, TT.EndTime)) Average
FROM Member.TaskTracking TT
LEFT OUTER JOIN Member.Task T
ON TT.TaskID=T.TaskID
JOIN dbo.tblMember M
ON TT.MemberID=M.MemberID
WHERE M.FullTime=1
AND M.EmployeeSalary=1
AND (TT.StartTime >= '2013-10-01'
AND TT.EndTime < '2013-11-01')
GROUP BY M.MemberDepartmentID,T.TaskName
ORDER BY M.MemberDepartmentID,T.TaskName
I don't know how to confirm if it's correct, but some are definitely showing averages of zero even if there were hours worked. And some averages are way higher than the hours worked. For instance, here are some of my results:
MemberDepartmentID TaskName Hours Average
---------------------------------------------------
1 Packing 25 0
1 Picking 6 0
1 PreScanning 38 7
4 Picking 2 104
Suggestions?
First, it is important to note that DATEDIFF(HOUR) returns an integer, and it does not necessarily give a good reflection of how much time has actually passed. For example, these both yield 1:
SELECT DATEDIFF(HOUR, '03:59', '04:01'); -- 2 minutes (0.033333 hours)
SELECT DATEDIFF(HOUR, '03:01', '04:59'); -- 118 minutes (1.966666 hours)
And these both yield 0:
SELECT DATEDIFF(HOUR, '03:01', '03:59'); -- 58 minutes (0.966666 hours)
SELECT DATEDIFF(HOUR, '03:01', '03:02'); -- 1 minute (0.016666 hours)
Next, if you give SQL Server integers to divide, it's going to perform integer math. Meaning it will divide, but it will discard any remainder. This yields 0:
SELECT 3/4;
Even though really it's 0.75, and if it rounded up it should be 1. (Not that either of those results are particularly meaningful). Now, extend that to average.
DECLARE #d1 TABLE(a INT);
INSERT #d1 VALUES(3),(4);
SELECT AVG(a) FROM #d1;
This yields 3, not 3.5, which you would probably expect. For the same reasons as above.
Remembering that some of your tasks may have lasted up to 59 minutes, but would still yield an hour differential of 0, you could have, say, 4 tasks, three that lasted > 1 hour, and one that lasted < 1 hour. So your average calculation would essentially be:
SELECT (1+1+1+0)/4;
Which, as above, still yields 0.
If you want a meaningful average there, you should calculate the time spent more granularly than by hours. For example, you could perform the datediff in minutes:
SELECT DATEDIFF(MINUTE, '03:01', '04:59');
This yields 118. If you want to express that in hours, you could divide by 60.0 (the decimal is important) or multiply by 1.0:
SELECT DATEDIFF(MINUTE, '03:01', '04:59')/60.0;
SELECT 1.0*DATEDIFF(MINUTE, '03:01', '04:59')/60;
These both yield 1.966666. Much more meaningful to average such a result. So perhaps change your expression to:
Average = AVG(1.0*DATEDIFF(MINUTE, TT.StartTime, TT.EndTime)/60)
About the count, not sure what you're attempting to do there, but you may want to make similar adjustments to the calculation and probably consider using SUM. If you show some sample data and the results you expect, we can help more.
Also I recommend not escaping keyword aliases using 'single quotes' - some forms of this syntax are deprecated, and it makes your alias look like a string literal. First, try not to use keywords or otherwise invalid identifiers as aliases; but if you must, escape them with [square brackets].

how to get data whose expired within 45 days..?

HI all,
i have one sql table and field for that table is
id
name
expireydate
Now i want only those record which one is expired within 45 days or 30 days.
how can i do with sql query .?
I have not much more exp with sql .
Thanks in advance,
If you are using mysql then try DATEDIFF.
for 45 days
select * from `table` where DATEDIFF(now(),expireydate)<=45;
for 30 days
select * from `table` where DATEDIFF(now(),expireydate)<=30;
In oracle - will do the trick instead of datediff and SYSDATE instead of now().[not sure]
In sql server DateDiff is quite different you have to provide unit in which difference to be taken out from 2 dates.
DATEDIFF(datepart,startdate,enddate)
to get current date try one of this: CURRENT_TIMESTAMP or GETDATE() or {fn NOW()}
You can use a simple SELECT * FROM yourtable WHERE expireydate < "some formula calculating today+30 or 45 days".
Simple comparison will work there, the tricky part is to write this last bit concerning the date you want to compare to. It'll depend of your environment and how you stored the "expireydate" in the database.
Try Below:-
SELECT * FROM MYTABLE WHERE (expireydate in days) < ((CURRENTDATE in days)+ 45)
Do not execute directly! Depending of your database, way of obtaining a date in days will be different. Go look at your database manual or please precise what is your database.