Best way to store time interval values in MySQL? - mysql

Stupid easy problem, but I haven't been able to find an elegant solution. I want to store time intervals in a MySQL columns, for instance:
1:40 (for one hour, 40 minutes)
0:30 (30 minutes).
Then be able to run queries, summing them. Right now I store them as INT values (1.40), but I have to manually do the addition (unless I'm missing an easier way).
The TIME column type only stores upto 900 hours (about, I think), so that's (almost) useless for me since I am tracking upwards of hundreds of thousands of hours (I store one field with a summation of many different entries).
Thanks!

store just the minutes, so 1:40 gets stored as 100. this makes for easy addition: 100 + 30 = 130. when you display, do the math to convert back to hours:minutes. 130 minutes -> 2:10.

I would simply store them in an INT field as seconds or minutes (or whatever the lowest time value you are working with).
As an example, you want to store the following time value 1 hr 34 min 25 seconds:
That is 5665 seconds. So just store the value as 5665 in an INT field
Later, lets say you want to store the time value 56 min 7 seconds:
That's 3367 seconds.
Sum up everything later: 5665 + 3367 = 9032 seconds
Convert that back to hours, minutes, seconds, you get 2 hrs 30 min 32 seconds. Yes, you have to do the math to make the conversion, but it's a very simple calculation and it's not at all machine intensive since you are only doing it once, either before you store the INT or once, after you retrieve the INT.

You can definitely use an int field for this, especially since MySQL provides the DATE_ADD and DATE_SUB functions for using integer units of time alongside date and datetime types in date artihmetic.
For example, if you have a datetime column eventDateTime and you have a duration column durationMinutes you can calculate the ending datetime using:
DATE_ADD(eventDateTime,INTERVAL durationMinutes MINUTE)

I know this is not the kind of time interval you are talking about, when I when I searched around this is about the only relevant SO question I found, so to help out other lost souls like myself I thought I would post what I am doing now.
I am storing a Subscription length, which rather than precise values like "number of seconds" or even minutes is either Days or Months. So in my case I have a "duration" INT and a "duration_unit" ENUM of ('days','months').
So for a "6 month" subscription rather than trying to calculate how many minutes are in a 6 months (which varies depending on which 6 months you are talking about) I just store "6" and "months".
With PHP's mktime() method it is easy and more accurate to calculate time intervals of +6 months.

If you want to store them as hours, and thus keep the integer small, you can take the number of minutes and divide by 60.
So 1:40 becomes 100/60 = 1.66 , :30 becomes 30/60 = .5. Now you can just add them up normally:
1.66 + .5 = 2.16
and then you have the number of hours as the whole, and the decimal part is the number of minutes over 60, so that would be
.16 * 60 = 10
so it's 2:10
Your next best option is to see if MySQL can do base60.

Related

How to do repeat on all non-numeric characters using the preceding number in the string

I am trying to get data from a rostering system. I am not sure if this is just a bad design or if that is common set up but the full day roster is broken down into 15 minute intervals starting at midnight and this is all stored in one field in the below format.
32,20C2L16C
The "," marks no activity so 32/4 means that this person started at 8am, then worked for 5 hours before taking 30 minute break for lunch (L) and then doing another 4 hours of work. I am trying to calculate the hours worked in a separate column - it would give me 9 hours here.
I was thinking that one way to solve it would be to do repeat on all the non-numeric characters to get this:
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,CCCCCCCCCCCCCCCCCCCCLLCCCCCCCCCCCCCCCC
And then to simply count all Cs and divide by 4. Is it possible to do repeat like this in MySQL? Would there be a better way that I may be missing?

msyql ROUNDING for FLAT RATE charging

Need help with rounding values, I know ROUND() exists but it's not quite what I needed.
So, I'm calculating call rates per minute, in which in a flat rate basis, any call goes more than 60seconds will be charged for the next minute.
My query goes SEC_TO_TIME(billedsec) to get the minute equivalent.
I have 2 issues:
How to round off to a full minute for values that goes more than a minute say, if 1min and 15sec (00:01:15) should be 2mins or 13seconds must be a full minute.
How to calculate TIME format to an int or float to get the actual rate
e.g 00:01:00*rate? Is it straightfoward approach?
Thanks guys
You can use
FLOOR((billedsec+59)/60)
to get the time rounded up to the next minute. 13 seconds will go to 72/60=1 minute, 60 seconds will go to 119/60=1 minute, 61 seconds will go to 120/60=2 minutes, 75 seconds will go to 134/60=2 minutes etc.

Converting Integer to Time

We have an database that has a table which stores schedule information, including a clock in and clock out column. The problem is that whomever created this table made these to attributes as integers instead of time or datetime (This could also have been forced by the software that creates the schedule). So, for instance, instead of saying 8:00:00, it says 480 (the number of minutes that have passed that day). 18:00 (6 PM) shows 1080, Midnight is 1440, etc.
I have to query this for a report and do a calculation based off of the scheduled punches vs the actual hours worked which is stored as a time datatype. I am trying to convert the integer minutes into time of day in the select statement in order to have a CTE I can work with for multiple comparisons. So far this is what I've come up with (keep in mind the schedule is done by quarter hour increments, hence the .25, .5, .75, etc.)
SELECT CASE CAST(ClockIN AS decimal) / 60
WHEN .25 THEN CAST('00:15:00.0000000' AS time)
WHEN .5 THEN CAST('00:30:00.0000000' AS time)
WHEN .75 THEN CAST('00:75:00.0000000' AS time)
WHEN 1 THEN CAST('01:00:00.0000000' AS time)
.....
WHEN 24 THEN CAST('00:00:00.0000000' AS time)
END AS ClockIn
I am trying to have clean code and not do too many calculations that take up resources every time the report is ran. Is there an easier way to do all of this, am I not thinking out of the box enough? So far this is the only way I can think of to accomplish what I need, which is an integer representing passed time converted to a timestamp Any advice would be appreciated!
You can create a time and then use DATEADD to just add minutes to the time. So at 480 minutes, you'd add it to the new time created "00:00" and it should leave you at 8:00 AM.
DATEADD(minute, ClockIN, '00:00')

Database design - How much data to store, performance vs quality

There is some value, x, which I am recording every 30 seconds, currently into a database with three fields:
ID
Time
Value
I am then creating a mobile app which will use that data to plot charts in views of:
Last hour
Last 24 hours.
7 Day
30 Day
Year
Obviously, saving every 30 seconds for the last year and then sending that data to a mobile device will be too much (it would mean sending 1051200 values).
My second thought was perhaps I could use the average function in MySQL, for example, collect all of the averages for every 7 days (creating 52 points for a year), and send those points. This would work, but still MySQL would be trawling through creating averages and if many users connect, it's going to be bad.
So simply put, if these are my views, then I do not need to keep track of all that data. Nobody should care what x was a year ago to the precision of every 30 seconds, this is fine. I should be able to use "triggers" to create some averages.
I'm looking for someone to check what I have below is reasonable:
Store values every 30s in a table (this will be used for the hour view, 120 points)
When there are 120 rows are in the 30s table (120 * 30s = 60 mins = 1 hour), use a trigger to store the first half an hour in a "half hour average" table, remove the first 60 entries from the 30s table. This new table will need to have an id, start time, end time and value. This half hour average will be used for the 24 hour view (48 data points).
When the half hour table has more than 24 entries (12 hours), store the first 6 as an average in a 6 hour average table and then remove from the table. This 6 hour average will be used for the 7 day view (28 data points).
When there are 8 entries in the 6 hour table, remove the first 4 and store this as an average day, to be used in the 30 day view (30 data points).
When there are 14 entries in the day view, remove the first 7 and store in a week table, this will be used for the year view.
This doesn't seem like the best way to me, as it seems to be more complicated than I would imagine it should be.
The alternative is to keep all of the data and let mysql find averages as and when needed. This will create a monstrously huge database. I have no idea about the performance yet. The id is an int, time is a datetime and value is a float. Is 1051200 records too many? Now is a good time to add, I would like to run this on a raspberry pi, but if not.. I do have my main machine which I could use.
Your proposed design looks OK. Perhaps there are more elegant ways of doing this, but your proposal should work too.
RRD (http://en.wikipedia.org/wiki/Round-Robin_Database) is a specialised database designed to do all of this automatically, and it should be much more performant than MySQL for this specialised purpose.
An alternative is the following: keep only the original table (1051200 records), but have a trigger that generates the last hour/day/year etc views every time a new record is added (e.g. every 30 seconds) and store/cache the result somewhere. Then your number-crunching workload is independent of the number of requests/clients you have to serve.
1051200 records may or may not be too many. Test in your Raspberry Pi to find out.
Let me give a suggestion on the physical layout of your table, regardless on whether you decide to keep all data or "prune" it from time to time...
Since you generate a new row "every 30 seconds", then Time can serve as a natural key without fear of exceeding the resolution of the underlying data type and causing duplicated keys. You don't need ID in this scenario1, so your table is simply:
Time (PK)
Value
And since InnoDB tables are clustered, not having secondary indexes2 means the whole table is stored in a single B-Tree, which is as efficient as it gets from storage and querying perspective. On top of that, Value is automatically covered, which may not have been the case in your original design unless you specifically designed your index(es) for that.
Using time as key can be tricky in general, but I think may be worth it in this particular case.
1 Unless there are other tables that reference it through FOREIGN KEYs, or you have already written too much code that depends on it.
2 Which would be necessary in the original design to support efficient aggregation.

Good pattern for storing ages, in MySQL, without reference to a date of birth

I have some data on fatalities which I'm trying to store, and I'm trying to come up with a reasonable scheme for storing the age of the person when they died.
I don't have DoB data for any of them, but I do have date of death generally (although not always very precisely) and I have data of varying accuracy for their age at death.
Some typical source data might be:
between 20 and 29 years old (or "in their 20s")
5 years old
2 months old
40 days old
adult
child
elderly
I have typically been storing this in three fields...
age_min (integer years)
age_max (integer years)
age_category (enum - baby, child, adult, elderly)
...but clearly this doesn't capture the 2 months old or 40 days old very well, both of which would simply end up as 0 years in my current schema, which is needlessly throwing away information.
It is very important that the database is honest about the precision to which information is known. So converting 2 months into 60 days, for example, would be a bad thing, because it implies a level of precision the source data didn't provide - converting it into 60-90 days might be ok.
I also considered adding a units field so I'd have...
age_min (integer)
age_min_unit (enum - days, months, years)
but the problem with this is it makes comparisons annoying. 24 months == 2 years, but dealing with that just makes a lot of code much more complex than I suspect it needs to be.
I could store all ages in days, with a min and a max, but then the complexity becomes converting that back into something human readable which isn't clunky and doesn't express a greater degree of precision than I actually have.
So for example, 40 days might end up being rendered at 1 month, 10 days which is actually a little less precise than saying 40 days.
Ok just adding it answer for future
Can you try to use the age_min and age_max in days and also carry one more field as "human_readable_age_text" which reads , say "40 days"
Been there, done that. The least ambiguous and easiest to process is to convert everything to days and add a +/- tolerance. That way everything can be stored in 2 fields and all situations are covered. Obviously you have to convert to human readable format before display.
If you have date of birth and date of death the tolerance becomes 0.
Thus the following input values will yield the indicated stored values.
5 years: 2007 183 (ie. 5.5 x 365 = 2007 days. 365/2 = +/-183 days.)
2 months: 75 15
9 years 7 months: 3512 15
child: First value is midpoint of your preferred "child" age range in days. (1-12?, 3-18?). Tolerance is half that.
baby: Same again. Decide on what constitutes a "baby" (0-2?) and generate the values accordingly.
Store the value as min+max+unit. 'adult','child'... etc can be represented as a unit of age for which the min and max would be ignored.
Then you need to find the answer to philosophical questions like "Who is older: a child or a person between 5 and 12 years old?".
When you have the answer to those for all of the possible combo's of age types you will be able to tell if it's possible to use a canonical representation of the age (e.g. days) for comparing.
If its possible - you can add an additional field with the age in days (or seconds, or something...) to use for comparing/sorting. The compare field can be calculated with a trigger, or in the app.
If its not possible - you will need a custom comparator for sorting, afaik that can't be done in MySQL so you will probably have to do all sorting and comparing in the app.