How best to store time ranges like Tuesday 10-11am? - mysql

It's not a one-of, not only one particular Tuesday, so I won't use time stamps.
I mean something like "every Tuesday from 10am to 11am". What's the best way to store this to make it easy to check from my (Delphi) application if the current time is between those permitted times?

Sounds like the time range would simply be an attribute of whichever primary object you are working with, as such you could add a separate table to store the time range data, something like:
object_time_range
id
object_id
day
hour_start
hour_end
frequency_id
-- Update --
In hindsight I would probably abstract hour from the column names in favor of time, just in case you need to consider alternative time frames. And day should be day_id, even though its a static list, I always like going with ints in this situation.
object_time_range
id
object_id
day_id
time_start
time_end
frequency_id

Store the variable as datetime or timestamp,
you can use mysql date functions such as
date_format and date_add
to get the rows which fall within required dates and time
if you store it as text it would be very difficult.
E.g To get rows for the past one day
select * from tableName
where dateField > date_add(now(),interval -1 day) and
dateField < now()
rows for the past one Week
select * from tableName
where dateField > date_add(now(),interval -1 week) and
dateField < now()
Check date_add funciton

Related

Unable to create computed field with date_add in SQL

In my database, I have a table called 'fine', in that table I have three fields, issue_date, expiry_date and fine_amount. I want the expiry_date to be computed from the issue date. The expiry date should always have 20 days more than the issue_date, So I wrote the query as:
ALTER TABLE fine ADD
expiry_date AS DATE_ADD(CURRENT_DATE,INTERVAL 20 DAY)
But there is a syntax error. I can't seem to find the solution.
Also I want the fine_amount to be 10 * (difference in days between current date and expiry date if current days exceeds expiry date). How do I go about doing that?
You can't implement the fine logic using a computed column because the formula involves the current time which is non deterministic. From the MySQL documentation:
Literals, deterministic built-in functions, and operators are permitted. A function is deterministic if, given the same data in tables, multiple invocations produce the same result, independently of the connected user. Examples of functions that fail this definition: CONNECTION_ID(), CURRENT_USER(), NOW().
So your best bet probably is to just compute values for these columns at the time you actually select. For example:
SELECT issue_date,
DATE_ADD(issue_date, INTERVAL 20 DAY) AS expiry_date,
CASE WHEN NOW() > DATE_ADD(issue_date, INTERVAL 20 DAY)
THEN 10*DATEDIFF(NOW(), DATE_ADD(issue_date, INTERVAL 20 DAY))
ELSE 0 END AS fine_amount
FROM fine

Efficient SELECT query to find records within a month

I have a MySQL DB table with multiple date type fields. I need to do different SELECT queries on this table but I am not sure which way is the best to find records from the same month.
I know I can do the following:
SELECT *
FROM table
WHERE MONTH(somedate) = 5
AND YEAR(somedate) = 2015
But I keep reading that isn't efficient and that I should go with using actual dates, i.e.
SELECT *
FROM table
WHERE somedate BETWEEN '2015-05-01' AND '2015-05-31'
However, all I would have is the month and the year as variables coming in from PHP. How do I easily and quickly calculate the last day of the month if I go with second option?
Don't calculate the last day of the month. Calculate the first day of the next month instead.
Your query can be like this
WHERE t.mydatetimecol >= '2015-05-01'
AND t.mydatetimecol < '2015-05-01' + INTERVAL 1 MONTH
Note that we're doing a less than comparison, not a "less than or equal to"... this is very convenient for comparing TIMESTAMP and DATETIME columns, which can include a time portion.
Note that a BETWEEN comparison is a "less than or equal to". To get a comparison equivalent to the query above, we'd need to do
WHERE t.mydatetimecol
BETWEEN '2015-05-01' AND '2015-05-01' + INTERVAL 1 MONTH + INTERVAL -1 SECOND
(This assumes that the resolution of DATETIME and TIMESTAMP is down to a second. In other databases, such as SQL Server, the resolution is finer than a second, so there we'd have the potential of missing a row with value of '2015-05-31 23:59:59.997'. We don't have a problem like that with the less than the first day of the next month comparison... < '2015-06-01'
No need to do the month or date math yourself, let MySQL do it for you. If you muck with adding 1 to the month, you have to handle the rollover from December to January, and increment the year. MySQL has all that already builtin.
date('t', strtotime("$year-$month-01")) will give days in the month

mySQL gather data between hours of the day passing through midnight with date increment

I am trying to get data from a database between 8PM (say, today) and 2AM tomorrow.
I have been using clauses such as where hour(date_field)>=20 and hour(date_field) <23 to obtain data in the same day.
Here the date_field is datetime
All I want is to be able to tell SQL to get data after 8PM today, increment the datefield and then get data till 2AM tomorrow.
Any help will be appreciated.
The normal pattern for retrieving rows based on a datetime range is perform comparisons on the bare column, comparing the column value to constants derived from expressions.
To get rows for a single contiguous range, 8PM today to 2AM tomorrow, for example:
WHERE t.date_column >= DATE(NOW()) + INTERVAL 20 HOUR
AND t.date_column < DATE(NOW()) + INTERVAL 26 HOUR
To unpack that a little bit: NOW() returns current datetime, the DATE() function truncates the time portion to midnight, then we add back in enough hours to get '8PM today', or enough hours to get '2AM tomorrow'.
If you are meaning to retrieve multiple "8PM to 2AM" periods, for a whole series of days.
First, you'd want an upper and lower bound of the date_column to be retrieved (unless you want every possible date)
WHERE t.date_column >= '2014-08-01 20:00:00'
AND t.date_column < '2014-10-02 02:00:00'
From that, we need to filter out all of the rows that aren't between 8PM and 2AM. One convenient way to do that would be to "subtract" two hours from the datetime col, and check for hour >= 6PM.
AND HOUR(t.date_column + INTERVAL -2 HOUR) >= 18
Note that the expression involving date_column will need to be evaluated for EVERY row in the table, unless there are some other predicates that filter rows out. With a suitable index available, MySQL can use an index range scan operation for predicates of the form date_column >= const and date_column < const. (It can't do that when the column is wrapped in a function or expression.)

SQL SELECT logic

I've been having a bit of trouble thinking this problem through. I can't seem to define a SELECT query which is accurate enough to give me the result I want.
I am storing shift patterns in a table. These shift patterns don't have any restrictions on when they can start and finish (except that they cannot overlap each other per machine)
This is the structure of the table (5 rows of example data)
The only information I have to select with is:
The current time (e.g. 01:45)
The current weekday (e.g. Tuesday)
The issue is when a shift overlaps 00:00. So my question is this:
How would I select the current shift based on the current time and weekday?
Here is an SQL Fiddle of the scenario
Thanks!
You can do this with simple logic. If StartTime < EndTime, then you want to test for times between the two values. If Startime > EndTime, then you want to test for times not between the two values. So this solves the time problem:
SELECT *
FROM webreportshiftsetup
WHERE (StartTime < EndTime and time(now()) between StartTime and EndTime or
StartTime > EndTime and time(now()) not between StartTime and EndTime
) and
dayname(now()) in (StartWeekDay, EndWeekDay)
You have a similar problem with the weekdays. But your question is specifically about times and not weekdays. (That should perhaps be another question.)
If your shifts are one-day (i.e. you need to select only current day) you can do something like
SELECT * FROM shifts
WHERE startWeekDay = DATE_FORMAT(CURDATE(),'%W') AND NOW() BETWEEN startTime AND endTime
Otherwise, if your shift starts on Monday and finishes on Wednesday, and today is Tuesday, you will have trouble finding todays shift with a query. For that you should store days as number: 1- Monday, 2- Friday, ...
I would suggest that you change the schema. One option is to use the datetime type for start/end, instead of using varchar for everything.
Then you can query it like:
SELECT *
FROM webreportshiftsetup
WHERE NOW() BETWEEN StartDateTime AND EndDateTime
and so forth.
If you need this to be a repeating thing to where specific dates won't work, then you might make the StartDay/EndDay columns tinyint and give them a value of 1-7, with the smallest number being the first day of the week and the largest number representing the last day of the week. StartTime/EndTime would be date type. Querying that would look like:
SELECT *
FROM webreportshiftsetup
WHERE StartDay >=2 AND EndDay <=4 AND StartTime >= '2:00' AND EndTime <= '13:00'

MySQL Select rows <= now(), using separated time fields

I have a table 't' with date(yyyy-mm-dd), hour(1-12), minute(00-59), ampm(a/p), and timezone(pst/est) fields.
How can I select the rows that are <= now()? (ie. already happened)
Thank you for your suggestions!
edit: this does it without attention to the hour/minute/ap/tz fields:
SELECT * FROM t.date WHERE date <= now()
Here's one way to do it - combine all your seconds, minutes, etc into a date and compare to NOW(), making sure you do the comparison in the same time-zone. (Untested):
SELECT *
FROM t
LEFT JOIN y ON t.constant=y.constant
WHERE CONVERT_TZ(STR_TO_DATE(CONCAT(date,' ',hour,':',minute,' 'ampm),
'%Y-%m-%d %l:%i %p' ),
timezone,"SYSTEM") < NOW();
If your hour is 01 - 12 not 1-12 then use %h instead of %l in the STR_TO_DATE.
The STR_TO_DATE tries to stick your date and time columns together and convert them into a date.
The CONVERT_TZ(...,timezone,"SYSTEM") converts this date from whatever timezone is specified in the timezone column to system time.
This is then compared to NOW(), which is always in system time.
As an aside, perhaps you should make a single column date using MySQL's date datatype, as it's a lot easier to do arithmetic on that!
For reference, here is a summary of very useful mysql date functions where you can read up on those featuring in this answer.
Good luck!
SELECT * FROM t
WHERE `date`<=DATE_SUB(curdate(), INTERVAL 1 DAY)
OR (
`date`<=DATE_ADD(curdate(), INTERVAL 1 DAY)
AND
CONVERT_TZ(CAST(CONCAT(`date`,' ',IF(`hour`=12 AND ampm='a',0,if(ampm='a',`hour`,`hour`+12)),':',`minute`,':00') AS DATETIME),'GMT',`timezone`)<=NOW()
)
Rationale for date<=DATE_[ADD|SUB](curdate(), INTERVAL 1 DAY):
The fancy conversion is quite an expensive operation, so we don't want it to run on the complete table. This is why we pre-select against an UNCHANGED date field (possibly using an index). In no timezone can an event being more than a day in current timezone's past be in the future, and in no timezone can an event more than a day in the curent timezone's future be in the past.