I've read a few similar questions then mine, where I could find queries that were pretty much the same I'm using. But I had to ask, because I would like to understand why this is not working:
I have the following data:
id category_id start_date end_date image campaign_id published
1 1 2011-07-05 2011-07-5 a.gif 3 1
2 1 2011-07-01 2011-07-15 c.gif 3 1
3 37 2011-07-01 2011-07-04 d.gif 3 1
I expect to get rows 1 and 2, from this query:
SELECT id, category_id, start_date, end_date FROM categories_campaigns WHERE start_date <= NOW() AND end_date >= NOW();
From what I've experienced, the only row returned is the second. Also this one, gives me the second,
SELECT category_id, start_date, end_date FROM categories_campaigns WHERE end_date >= NOW();
The next one returns me all 3,
SELECT category_id, start_date, end_date FROM categories_campaigns WHERE start_date <= NOW();
The datatype for both columns are DATE. So, my question is, how to solve this ? Why is this happening ? Or I've got an obvious error that I'm not finding on what to look for.
Working query
SELECT
category_id, start_date, end_date
FROM
categories_campaigns
WHERE
start_date <= DATE(NOW()) and end_date >= DATE(NOW());
I think you could use this:
SELECT category_id, start_date, end_date FROM categories_campaigns WHERE left(now(),10) between start_date and end_date;
I suppose start_date and end_date are of date datatype. But NOW() returns a date-time value.
Use CUR_DATE() instead of NOW()
Related
I'm trying to solve this challenge: https://www.hackerrank.com/challenges/sql-projects/problem.
I tried the following:
SELECT
(SELECT start_date
FROM projects
WHERE
(SELECT DATE_ADD(start_date, INTERVAL -1 DAY)) NOT IN (SELECT start_date FROM projects)
ORDER BY start_date ASC) AS start_date,
(SELECT end_date
FROM projects
WHERE
(SELECT DATE_ADD(end_date, INTERVAL 1 DAY)) NOT IN (SELECT end_date FROM projects)
ORDER BY end_date ASC) AS end_date
FROM projects p
ORDER BY DATEDIFF(end_date, start_date) ASC, start_date ASC
Nonetheless,I got the following error: 'Subquery returns more than 1 row' Despite using the NOT IN operator.
However, when I tried executing only this part of the code:
SELECT start_date
FROM projects p
WHERE (SELECT (DATE_ADD(start_date, INTERVAL -1 DAY)) NOT IN (SELECT start_date FROM projects)
ORDER BY start_date ASC
It worked fine.
What could be the problem?
The two subquery for start _date and end_date could return a different numbers of rows adn any way the db engine not allow so called "parallel query"
in this case you should gets all the date involved and the left join for the subquery
select t1.start_date, t2.end_date
from (
SELECT start_date
FROM projects
WHERE DATE_ADD(start_date, INTERVAL -1 DAY) NOT IN (SELECT start_date FROM projects)
UNION
SELECT end_date
FROM projects
WHERE SELECT DATE_ADD(start_date, INTERVAL -1 DAY) NOT IN (SELECT end_date FROM projects)
) t
left join (
SELECT start_date
FROM projects
WHERE DATE_ADD(start_date, INTERVAL -1 DAY) NOT IN (SELECT start_date FROM projects)
) t1 on t.start_date = t1.start_date
left join (
SELECT end_date
FROM projects
WHERE DATE_ADD(start_date, INTERVAL -1 DAY) NOT IN (SELECT start_date FROM projects)
) t2 on t.start_date = t2.start_date
order by t1.syaty_date
You select project rows. Per project row you select a start date. The query for the this start date looks like this:
(SELECT start_date ... ORDER BY start_date ASC)
Do you really think it is one start_date you are selecting here? Why then the ORDER BY clause? This subquery returns multiple rows and this is why you are getting the error.
This query does not selects one start date, but all start dates for which not exists the previous date in the table. It doesn't even relate to the project row in the main query.
It seems you want to find all start dates that have no predecessor and all end dates that have no follower. These are two data sets you can select from. So the subqueries don't belong in the SELECT clause where you say which columns to select, but in the FROM clause where you say from which data sets to select.
You would then have to join the two sets. The join criteria would be the rows' positions in the ordered data sets (first start date belongs to first end date, second start date belongs to second end date, ...). For this you need a way to number these data rows.
Such a task is easy to solve with ROW_NUMBER. This is only featured since MySQL 8.
SELECT s.start_date, e.end_date
FROM
(
SELECT start_date, ROW_NUMBER() OVER (ORDER BY start_date) AS rn
FROM projects
WHERE start_date - INTERVAL 1 DAY NOT IN (SELECT start_date FROM projects)
) s
JOIN
(
SELECT end_date, ROW_NUMBER() OVER (ORDER BY end_date) AS rn
FROM projects
WHERE start_date + INTERVAL 1 DAY NOT IN (SELECT end_date FROM projects)
) e USING (rn)
ORDER BY s.start_date;
This kind of problem is called gaps & islands. There are other ways to solve this, but I think that above query plainly builds up on yours and is thus easy to understand.
Here is another answer that may explain better what you are doing.
You can:
select
start_date,
end_date,
start_date - interval 1 day as prev_day,
1 as one
from projects;
The select clause contains what you want to select from a projects row. For the first row you will get its start date, end date, its start date minus one day, and a 1 we call "one" here. For the second row you will get its start date (which is probably another start date than the one of the first row), its end date, its start date minus one day, and again a 1 we call "one".
You can
select
(select start_date) as start_date,
(select end_date) as end_date,
(select start_date - interval 1 day) as prev_day,
(select 1) as one
from projects;
which doesn't change anything and only obfuscates things. (This is what you do here: (SELECT DATE_ADD(end_date, INTERVAL 1 DAY)).
You cannot
select
(select start_date from projects) as start_date,
(select end_date from projects) as end_date,
(select start_date - interval 1 day from projects) as prev_day,
(select 1 from projects) as one
from projects;
because here you are not selecting one value for the first project row's start date, but all start dates from the table. Same for its end date etc. of course, same for the second row etc. This is what you are doing here:
SELECT
(SELECT start_date FROM projects ...) AS start_date,
(SELECT end_date FROM projects ...) AS end_date
FROM projects p
and this is why you are getting the error "Subquery returns more than 1 row".
I have a table with columns ID, START_DATE and END_DATE. What I want to do is to execute a query which orders the resulting rows in the following order:
If the current date is 2020-12-14 then the result should be
START_DATE END_DATE
2020-12-8 2020-12-18 -----> Time periods which contains current day and are ordered by start_date in ASCENDING order.
2020-12-9 2020-12-15
2020-12-10 2020-12-17
2020-12-15 2020-12-17 -----> Time periods with START DATE > current date and are ordered by start_date in ASCENDING order.
2020-12-16 2020-12-22
2020-12-21 2020-12-25
2020-12-9 2020-12-13 ----->Time periods with END DATE < current date and are ordered by start_date in DESCENDING order.
2020-12-6 2020-12-8
2020-12-1 2020-12-9
How can I accomplish this?
You need in something similar to
ORDER BY CASE WHEN CURRENT_DATE BETWEEN start_date AND end_date
THEN 1
WHEN start_date > CURRENT_DATE
THEN 2
ELSE 3 END,
CASE WHEN end_date > CURRENT_DATE
THEN start_date + 0
ELSE DATEDIFF(CURRENT_DATE, start_date)
END
fiddle
start_date + 0 needed for to convert the result of this CASE to numeric. Without it the CASE alternatives combines date and numeric values which causes the final datatype to be a string - with wrong sorting order.
You can use conditional ordering as follows:
Select * from your_table
Order by
case when CURRENT_DATE between start_date and end_date OR start_date > CURRENT_DATE
then 1
When end_date < CURRENT_DATE
then 2
end,
case when CURRENT_DATE between start_date and end_date or start_date > CURRENT_DATE
then date_diff(start_date, CURRENT_DATE)
When end_date < CURRENT_DATE
then date_diff(CURRENT_DATE,start_date)
end
In MySQL, I would write this as:
order by (curdate() between start_date and end_date) desc,
(curdate() > curdate()) desc
case expressions are not needed because MySQL treats boolean values as a number, with 1 for true and 0 for false.
There is a table concerts which contains fields start_date with the format: 2019-06-08 10:00:00 and field duration - duration in minutes. I need to find concerts which intersect with special datetime. I'm using:
SELECT
id, duration, start_date,
DATE_ADD(date, INTERVAL + duration MINUTE) as end_date
FROM
concerts
WHERE
start_date <= '2019-06-08 10:00:00'
and end_date >= '2019-06-08 10:00:00'
I can calcucate end_date but then I can't use it inside where block, only inside having.
How can I use OR expression with where and having? I've found out I can use all conditions inside having block, but it's bad for performance. Any suggestions?
P.S One more if I can use where OR having inside one query, or how can I replace it
You can't access a column declared in the select clause in the where clause. Instead, you can repeat the expression.
But actually, I think that this does what you want, ie check which concerts were on-going at a specific point in time:
SELECT
id,
duration,
start_date,
start_date + interval duration minute as end_date
FROM concerts
WHERE '2019-06-08 10:00:00' between start_date and start_date + interval duration minute
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
I have two dates say start_date = 2013-04-12 and end_date = 2013-04-30
and my table contains row with start_date = 2013-04-12 and end_date = 2013-04-16
I want to fetch records whose start date is greater 2013-04-12 and end_date is less than 2013-04-30. Which includes above record having 2013-04-16 as end_date
I tried with this query
SELECT * FROM TABLE_NAME WHERE (start_date <='2013-04-12' AND end_date >='2013-04-30') which dont give any result however if i put end_date 2013-04-15 it works fine
SELECT
*
FROM
(
SELECT
'2013-04-12' as `start_date`,
'2013-04-16' as `end_date`
) `sub`
WHERE
(`start_date` BETWEEN '2013-04-12' AND '2013-04-30')
AND
(`end_date` BETWEEN '2013-04-12' AND '2013-04-30')
BETWEEN is more reliable
If you want to have start_date GREATER OR EQUAL 2013-04-12 and end_date LESSER OR EQUAL, than your operators are wrong:
start_date <='2013-04-12' AND end_date >='2013-04-30'
you should use >= for start_date and <= for end_date
like this:
start_date >='2013-04-12' AND end_date <='2013-04-30'
I think you are using wrong operators ... start_date greater than(>=) end_date less than equal to (<=)
SELECT * FROM TABLE_NAME WHERE (start_date >='2013-04-12' AND end_date <='2013-04-30')
I would do:
SELECT * FROM table_name WHERE start_date BETWEEN '2013-04-12' AND '2013-04-30'
Try this,
SELECT * FROM TABLE_NAME WHERE Convert(varchar,start_date,101) >'04/12/2013') AND convert(varchar,end_date,101) <'04/30/2013')