Suppose I have the following table:
id | value | start_time | stop_time
-----------------------------------------------------------------
1 | value1 | 06:00:00 | 11:00:00
2 | value2 | 12:00:00 | 13:00:00
I need to select records whose time range overlap with 05:00–11:30. That means the record to be returned is row 1.
I tried using a query like this but it fails when the given time range crosses midnight. Perhaps it's impossible to do it without involving the date.
SELECT * FROM table WHERE
(
(start_time >= '05:00:00' AND stop_time <= '11:30:00') ||
(start_time >= '05:00:00' AND start_time <= '05:00:00') ||
(start_time <= '05:00:00' AND stop_time >= '11:30:00') ||
(stop_time >= '11:30:00' AND stop_time <= '05:00:00')
)
I'm thinking of enumerating all the minutes for the given time range and filtering using BETWEEN but there may be a better solution.
Dave,
The easiest way which i can see for you to achieve the desired result will be to use 'TIME' type for start_time and 'stop_time` fields.
Then you can select like this:
SELECT * FROM `YOUR_TABLE_NAME_HERE` WHERE `start_time` > '05:00:00' AND `stop_time` < '11:30:00'
Every range will need to be represented with a conditional, like so:
IF(start_time < stop_time, start_time >= '05:00:00' AND stop_time <= '11:30:00', start_time <= '05:00:00' AND stop_time >= '11:30:00')
Related
I have an insurance policies table like this:
+-------------------------------------------------------------+
| id | cancellation_val | cancellation_interval | expire_date |
+-------------------------------------------------------------+
| 1 | 30 | day | 2019-06-09 |
| 2 | 2 | month | 2019-12-01 |
+-------------------------------------------------------------+
I need to get the ids of the policies that are going to expire based on cancellation, from today and within 4 months, calculating the last day of the month, like this pseudo-code:
'today' <= LAST_DAY( expire_date - cancellation_val/interval ) < 'today + 4 months'
Being not a pro I think I should use JOINs but I don't know how, after days of trying the only thing I achieved was this:
SELECT LAST_DAY(
DATE_FORMAT(
STR_TO_DATE(
(SELECT CASE cancellation_interval
WHEN "day" THEN date_sub(expire_date, INTERVAL cancellation_val DAY)
WHEN "month" THEN date_sub(data_scadenzaexpire_date, INTERVAL cancellation_val MONTH)
END
AS newDate
FROM insurance WHERE id=2
), '%Y-%m-%d'
), '%Y-%m-%d'
)
)
This is working but I don't need the "WHERE id=2" clause (because I need to process ALL rows of the table), and if I remove it I got error "subquery returns more than 1 row".
So how I can proceed? And using the result to stay between 'today' AND 'today + 4 months' ?
I think with some kind of JOIN I could do it in a easier way but I don't know how.
Thank you all
The problem is the structure of the query, not the LAST_DAY function.
We want to return the id values of rows that meet some condition. So the query would be of the form:
SELECT t.id
, ...
FROM insurance t
WHERE ...
HAVING ...
Introducing another SELECT keyword basically introduces a subquery. There are restrictions on subqueries... in the SELECT list, a subquery can return a single column and (at most) a single row.
So let's ditch that extra SELECT keyword.
We can derive the newdate as an expression of the SELECT list, and then we can reference that derived column in the HAVING clause. The spec said we wanted to return the id value, so we include that in the SELECT list. We don't have to return any other columns, but for testing/debugging, it can be useful to return the values that were used to derive the newdate column.
Something like this:
SELECT t.id
, LAST_DAY(
CASE t.cancellation_interval
WHEN 'day' THEN t.expire_date - INTERVAL t.cancellation_val DAY
WHEN 'month' THEN t.expire_date - INTERVAL t.cancellation_val MONTH
ELSE t.expire_date
END
) AS newdate
, t.expire_date
, t.cancellation_interval
, t.cancellation_val
FROM insurance t
HAVING newdate >= DATE(NOW())
AND newdate <= DATE(NOW()) + INTERVAL 4 MONTH
ORDER
BY newdate ASC
We don't have to include the newdate in the SELECT list; we could just replace occurrences of newdate in the HAVING clause with the expression.
We could also use an inline view to "hide" the derivation of the newdate column
SELECT v.id
, v.newdate
FROM ( SELECT t.id
, LAST_DAY(
CASE t.cancellation_interval
WHEN 'day' THEN t.expire_date - INTERVAL t.cancellation_val DAY
WHEN 'month' THEN t.expire_date - INTERVAL t.cancellation_val MONTH
ELSE t.expire_date
END
) AS newdate
FROM insurance t
) v
WHERE v.newdate >= DATE(NOW())
AND v.newdate <= DATE(NOW()) + INTERVAL 4 MONTH
ORDER
BY v.newdate ASC
check this query: remove the HAVING Line to see all rows
SELECT
IF(cancellation_interval = 'day',
i.expire_date - INTERVAL i.`cancellation_val` DAY,
i.expire_date - INTERVAL i.`cancellation_val` MONTH
) as cancellation_day,
i.*
FROM `insurance` i
HAVING cancellation_day < NOW() + INTERVAL 4 MONTH;
SAMPLES
MariaDB [test]> SELECT IF(cancellation_interval = 'day', i.expire_date - INTERVAL i.`cancellation_val` DAY, i.expire_date - INTERVAL i.`cancellation_val` MONTH ) as cancellation_day, i.* FROM `insurance` i HAVING cancellation_day < NOW() + INTERVAL 4 MONTH;
+------------------+----+------------------+-----------------------+-------------+
| cancellation_day | id | cancellation_val | cancellation_interval | expire_date |
+------------------+----+------------------+-----------------------+-------------+
| 2019-05-10 | 1 | 30 | day | 2019-06-09 |
+------------------+----+------------------+-----------------------+-------------+
1 row in set (0.001 sec)
When you use a SELECT query as an expression, it can only return one row.
If you want to process all the rows, you need to call LAST_DAY() inside the query, not on the result.
SELECT *
FROM insurance
WHERE CURDATE() <= LAST_DAY(
expire_date - IF(cancellation_interval = 'day',
INTERVAL cancellation_val DAY,
INTERVAL cancellation_val MONTH))
AND LAST_DAY(expire_date - IF(cancellation_interval = 'day',
INTERVAL cancellation_val DAY,
INTERVAL cancellation_val MONTH)) < CURDATE + INTERVAL 4 MONTH
Let's guess we've got the following table:
- id (int)
- active (int)
- active_from (datetime) (NULL)
- active_until (datetime) (NULL)
Now, what I want to get is all the active records. An active record implies:
- active = 1
- active_from <= current_date (IF IT'S NOT NULL)
- active_until >= current_date (IF IT'S NOT NULL)
I'm looking for a query that applies these 3 requirements in one single query. I'm currently using:
SELECT * FROM product WHERE active = 1 AND active_from <= NOW() AND active_until >= NOW();
I will only get the behavior I want with rows that don't have NULL active_from or active_until.
Note: I know it would be more appropiate to compare the current date after storing it in a variable (posing it this way because I'm filling it with PHP parameters).
Thank you in advance.
SELECT * FROM YourTableName
WHERE active = 1
AND (active_from < CURDATE()
OR active_from IS NULL)
AND (active_until > CURDATE()
OR active_until IS NULL);
Use ifnull in your query (encase active_from and active_until in it):
https://dev.mysql.com/doc/refman/5.7/en/control-flow-functions.html#function_ifnull
So, basically, if the value is null, use another date instead. Which date is up to you and the specific business logic you need (ex: 2100-01-01 or 1900-01-01 etc.)
SELECT * FROM product
WHERE active = 1
AND (active_from < CURDATE()
AND active_from IS NOT NULL)
AND (active_until > CURDATE()
AND active_until IS NOT NULL)
I'm trying to compute a column based on the value of time, i'm not exactly sure what's better to use if I should use an If condition or Case?
Here are my columns
| Time_In | Time_Out | Val1 | Val2 |
| 2014-07-19 04:00:04 | 2014-07-19 08:00:00 | 5 | 15 |
What i'm trying to do is if the time is if the time is between '08:00:00' to '17:00:00' (business hours) it will automatically multiply Val1 and Val2
If the time is '18:00:00' to '23:00:00' (after business hours)
The only step I'm able to get right now is the getting the time in the timestamp cause that's what i'll be comparing
`SELECT date_format(STR_TO_DATE(Time_In, '%Y-%m-%d %H:%i:%s'), '%H:%i:%s')
Output: 04:00:04
`SELECT date_format(STR_TO_DATE(Time_Out, '%Y-%m-%d %H:%i:%s'), '%H:%i:%s')
Output: 08:00:00
How should I construct this?
Thank You for the help
I tried it using the if condition first and this is working :)
SELECT
IF(`Time_In` >= CAST('13:00:01' AS time) AND `Time_Out` <= CAST('15:00:00' AS time),(Val11 * Val2),'false')
FROM table WHERE ID = '5'
And got the output 75 :D
As you assumed you can do that with a CASE construct.
convert your string to a DATETIME value with STR_TO_DATE
extract the TIME part with the TIME function
So you could do it, here with to example values of
'2014-07-19 12:00:00'
'2014-07-19 18:00:00'
with this statement (you would need only one CASE construct of course):
SELECT
CASE
WHEN Time(STR_TO_DATE('2014-07-19 12:00:00', '%Y-%m-%d %H:%i:%s')) BETWEEN '04:00:00' AND '17:00:00'
THEN Val1 * Val2
ELSE NULL
END result,
CASE
WHEN Time(STR_TO_DATE('2014-07-19 18:00:00', '%Y-%m-%d %H:%i:%s')) BETWEEN '04:00:00' AND '17:00:00'
THEN Val1 * Val2
ELSE NULL
END result2
FROM
example;
DEMO
|---------------------|
|DUN|NAME |BIRTHDAY |
--------------------
|A |MELAYU |21/2/2014|
|A |CINA |21/2/2002|
|B |CINA |21/2/2011|
|-------------------- |
My table name is maklumat. I want view the data based on age using sql. below are the example table to view.
|---------------------|
|DUN|AGE<3 |AGE 3-6 |
--------------------
|A |1 |1 |
|B |0 |1 |
|-------------------- |
THIS IS WHAT I'VE TRIED
SELECT jantina,dun, count(tarikh_lahir > 1954-04-07) as 60KEBAWAH, count(1950-04-07>=tarikh_lahir <= 1954-04-07) as 60HINGGA64, count(1945-04-07>=tarikh_lahir <= 1949-04-07) as 65HINGGA69, count(1940-04-07>=tarikh_lahir <= 1944-04-07) as 70HINGGA74,count(1935-04-07>=tarikh_lahir <= 1939-04-07) as 75HINGGA79,count(1930-04-07>=tarikh_lahir <= 1934-04-07) as 80HINGGA85, count(1925-04-07>=tarikh_lahir <= 1929-04-07) as 86HINGGA89, count(1920-04-07>=tarikh_lahir <= 1924-04-07) as 90HINGGA94, count(1915-04-07>=tarikh_lahir <= 1919-04-07) as 95HINGGA99, count(1910-04-07>=tarikh_lahir <= 1914-04-07) as 100HINGGA104,count(tarikh_lahir <1910-04-07) as 60KEBAWAH
FROM maklumat_ahli
WHERE jantina = 'lelaki' AND (
kematian_tarikh IS NULL
AND bayaran_pertama IS NULL
AND bayaran_kedua IS NULL
)
GROUP BY dun
SELECT M.DUN, COUNT(M.AGE) AS 'AGE<3', COUNT(M1.AGE) AS 'AGE 3-6' FROM MAKLUMAT M
LEFT JOIN MAKLUMAT M1 ON M.DUN = M1.DUN AND M1.AGE BETWEEN 3 AND 6
WHERE M.AGE < 3
GROUP BY M.DUN
First calculate the age in years. This is this year minus the birth year. Exception: The birthday date is yet to come this year. In this case we must subtract one. With the ages thus found we can start to count, which we can do with the sum function by adding one for every match.
select
dun,
sum(age < 3) as age0to2,
sum(age between 3 and 6) as age3to6
from
(
select
dun,
year(now()) - year(birthday) -
case when month(now()) * 100 + day(now()) < month(birthday) * 100 + day(birthday) then
1
else
0
end
as age
from maklumat
)
group by dun;
Here is why your select statement fails: MySQL knows a boolean datatype, which is zero for FALSE and non-zero for TRUE. When evaluating an expression the result is either 0 or 1 (or NULL).
The expression (tarikh_lahir > 1954-04-07) is 1 for birth dates after the date stated, 0 for birth dates before and only NULL when the birth date is NULL. Count counts all non-null values, zeros and ones alike, i.e. all dates given. This is not what you want.
Instead of COUNT you can use SUM, to sum the positive results (i.e. the 1s). Besides: The dates should be strings as far as I know, otherwise they would be treated as numeric expressions, i.e. 1954-04-07 = 1954 minus 04 minus 07.
SELECT
jantina,
dun,
sum(tarikh_lahir > '1954-04-07') as 60KEBAWAH,
sum('1950-04-07' >= tarikh_lahir <= '1954-04-07') as 60HINGGA64,
sum('1945-04-07' >= tarikh_lahir <= '1949-04-07') as 65HINGGA69,
sum('1940-04-07' >= tarikh_lahir <= '1944-04-07') as 70HINGGA74,
sum('1935-04-07' >= tarikh_lahir <= '1939-04-07') as 75HINGGA79,
sum('1930-04-07' >= tarikh_lahir <= '1934-04-07') as 80HINGGA85,
sum('1925-04-07' >= tarikh_lahir <= '1929-04-07') as 86HINGGA89,
sum('1920-04-07' >= tarikh_lahir <= '1924-04-07') as 90HINGGA94,
sum('1915-04-07' >= tarikh_lahir <= '1919-04-07') as 95HINGGA99,
sum('1910-04-07' >= tarikh_lahir <= '1914-04-07') as 100HINGGA104,
sum(tarikh_lahir <'1910-04-07') as 60KEBAWAH
FROM maklumat_ahli
WHERE jantina = 'lelaki'
AND kematian_tarikh IS NULL
AND bayaran_pertama IS NULL
AND bayaran_kedua IS NULL
GROUP BY jantina, dun;
BTW: Are names starting with digits allowed in MySQL? Otherwise you will have to find other names for 60HINGGA64 etc. or use quotes.
I have a table that has a field 'start_date' and also a field 'end_date'. Items are considered active when the current date is between 'start_date' and 'end_date'. How would I find all the items that would be considered active for the next 30 days.
Assuming start_date and end_date are DATETIME, and you want to compare the date AND time components, you could do something like this:
SELECT a.*
FROM atable a
WHERE a.start_date >= NOW()
AND a.end_date <= NOW() + INTERVAL 30 DAYS
If start_date and end_date are DATE (rather than DATETIME), you probably only want to compare the DATE portion (without regard to a time component)
SELECT a.*
FROM atable a
WHERE a.start_date >= DATE(NOW())
AND a.end_date <= DATE(NOW()) + INTERVAL 30 DAYS
You may actually want a less than comparison (rather than less than or equal to) on the end date. You need to determine what results you want on that edge case. And the interval may need to be specified as 29 days, depending on how you define "the next 30 days".
Q: What about items that started before NOW and will run for the next 30 days?
A: I think I understand what you were asking.
I think you are wanting to retrieve any rows where the any point in time between the start_date and end_date falls anytime between now and 30 days from now. I can see that my query above does not answer that question.
For now, I'm going to consider only cases where " start_date < end_date ", and shelve cases where start_date is not less than end_date, or where either start_date or end_date or both are NULL.
We can depict the possible cases something (rather crudely) like this:
The vertical bars represent NOW and NOW+30
The less than sign represents "start_date"
The greater than sign represents "end_date"
The dashes represent the range of DATETIME betwen start_date and end_date
NOW NOW+30
<---> | | case 1: end_date < NOW()
<---|----> | case 2: NOW() between start_date and end_date
<--|-------|----> case 3: NOW() > start_date and NOW+30 < end_date
| <---> | case 4: both start_date and end_date between NOW() and NOW()+30
| <--|---> case 5: start_date between NOW() and NOW()+30 and end_date > NOW+30
| | <---> case 6: start_date > NOW()+30
<---> | case e1: end_date = NOW()
<--|-------> case e2: start_date > NOW() AND end_date = NOW()+30
<---> | case e3: start_date = NOW() and end_date < NOW()+30
<-------> case e4: start_date = NOW() and end_date = NOW()+30
<-------|--> case e5: start_date = NOW() and end_date > NOW()+30
| <---> case e6: start_date > NOW() AND end_date = NOW()+30
| <---> case e7: start_date = NOW()+30
I think you are asking to return rows that satisfy cases 2 thru 5, which is all cases EXCEPT for case 1 and 6.
If we can write a predicate that tests for cases 1 and 6, and then negate that, it should give us what you want. Something like this:
To handle datetime with time component considered:
WHERE NOT ( end_date < NOW() OR start_date > NOW() + INTERVAL 30 DAYS )
if start_date and end_date are DATE, to compare just the DATE portion, wrap the NOW() function in the DATE() function:
WHERE NOT ( end_date < DATE(NOW()) OR start_date > DATE(NOW()) + INTERVAL 30 DAYS )
I shelved the oddball cases, of start_date > end_date, start_date = end_date, start_date IS NULL, end_date IS NULL, etc. I also omitted discussion of the edge cases (e1 thru e7), e.g. start_date = NOW(), end_date = NOW(), etc. For completeness, you probably want to check whether those cases are handled appropriately with this same predicate.
DOH!
It just dawned on me that this:
WHERE NOT ( a < n OR b > t )
is equivalent to this (at least for not null values)
WHERE ( a >= n AND b <= t )
So this predicate should be equivalent:
WHERE end_date >= NOW() AND start_date <= NOW() + INTERVAL 30 DAYS