MySQL query to select events between start/end date - mysql

I have a MySQL table named 'events' that contains event data. The important columns are 'start' and 'end' which contain string (YYYY-MM-DD) to represent when the events starts and ends.
I want to get the records for all the active events in a time period.
Events:
------------------------------
ID | START | END |
------------------------------
1 | 2013-06-14 | 2013-06-14 |
2 | 2013-06-15 | 2013-08-21 |
3 | 2013-06-22 | 2013-06-25 |
4 | 2013-07-01 | 2013-07-10 |
5 | 2013-07-30 | 2013-07-31 |
------------------------------
Request/search:
Example: All events between 2013-06-13 and 2013-07-22 : #1, #3, #4
SELECT id FROM events WHERE start BETWEEN '2013-06-13' AND '2013-07-22' : #1, #2, #3, #4
SELECT id FROM events WHERE end BETWEEN '2013-06-13' AND '2013-07-22' : #1, #3, #4
====> intersect : #1, #3, #4
Example: All events between 2013-06-14 and 2013-06-14 :
SELECT id FROM events WHERE start BETWEEN '2013-06-14' AND '2013-06-14' : #1
SELECT id FROM events WHERE end BETWEEN '2013-06-14' AND '2013-06-14' : #1
====> intersect : #1
I tried many queries still I fail to get the exact SQL query.
Don't you know how to do that? Any suggestions?
Thanks!

If I understood correctly you are trying to use a single query, i think you can just merge your date search toghter in WHERE clauses
SELECT id
FROM events
WHERE start BETWEEN '2013-06-13' AND '2013-07-22'
AND end BETWEEN '2013-06-13' AND '2013-07-22'
or even more simply you can just use both column to set search time filter
SELECT id
FROM events
WHERE start >= '2013-07-22' AND end <= '2013-06-13'

You need the events that start and end within the scope. But that's not all: you also want the events that start within the scope and the events that end within the scope. But then you're still not there because you also want the events that start before the scope and end after the scope.
Simplified:
events with a start date in the scope
events with an end date in the scope
events with the scope startdate between the startdate and enddate
Because point 2 results in records that also meet the query in point 3 we will only need points 1 and 3
So the SQL becomes:
SELECT * FROM events
WHERE start BETWEEN '2014-09-01' AND '2014-10-13'
OR '2014-09-01' BETWEEN start AND end

Here lot of good answer but i think this will help someone
select id from campaign where ( NOW() BETWEEN start_date AND end_date)

SELECT id
FROM events
WHERE start <= '2013-07-22'
AND end >= '2013-06-13';
Or use MIN() and MAX() if you don't know the precedence.

SELECT *
FROM events
WHERE start <= '2013-07-22' OR end >= '2013-06-13'

SELECT *
FROM events
WHERE endDate >= #startDate AND startDate <= #endDate
For explanation refer to this diagram:
Suppose sample data is [startDate: 2020-10-01, endDate: 2020-20-01]
The user provides #startDate and #endDate to search
For overlap there are 4 scenarios and 1 variation (red/maroon lines)
For no overlap there are just 2 scenarios (green lines)
So, in order to get overlapping dates by providing start and end date, endDate must be greater than #startDate and startDate must be less than #endDate.

EDIT: I've squeezed the filter a lot. I couldn't wrap my head around it before how to make sure something really fit within the time period. It's this: Start date BEFORE the END of the time period, and End date AFTER the BEGINNING of the time period
With the help of someone in my office I think we've figured out how to include everyone in the filter.
There are 5 scenarios where a student would be deemed active during the time period in question:
1) Student started and ended during the time period.
2) Student started before and ended during the time period.
3) Student started before and ended after the time period.
4) Student started during the time period and ended after the time period.
5) Student started during the time period and is still active (Doesn't have an end date yet)
Given these criteria, we can actually condense the statements into a few groups because a student can only end between the period dates, after the period date, or they don't have an end date:
1) Student ends during the time period AND [Student starts before OR during]
2) Student ends after the time period AND [Student starts before OR during]
3) Student hasn't finished yet AND [Student starts before OR during]
(
(
student_programs.END_DATE >= '07/01/2017 00:0:0'
OR
student_programs.END_DATE Is Null
)
AND
student_programs.START_DATE <= '06/30/2018 23:59:59'
)
I think this finally covers all the bases and includes all scenarios where a student, or event, or anything is active during a time period when you only have start date and end date. Please, do not hesitate to tell me that I am missing something. I want this to be perfect so others can use this, as I don't believe the other answers have gotten everything right yet.

try this
SELECT id FROM events WHERE start BETWEEN '2013-06-13' AND '2013-07-22'
AND end BETWEEN '2013-06-13' AND '2013-07-22'
DEMO HERE
output :
ID
1
3
4

If you would like to use INTERSECT option, the SQL is as follows
(SELECT id FROM events WHERE start BETWEEN '2013-06-13' AND '2013-07-22')
INTERSECT
(SELECT id FROM events WHERE end BETWEEN '2013-06-13' AND '2013-07-22')

In PHP and phpMyAdmin
$tb = tableDataName; //Table name
$now = date('Y-m-d'); //Current date
//start and end is the fields of tabla with date format value (yyyy-m-d)
$query = "SELECT * FROM $tb WHERE start <= '".$now."' AND end >= '".$now."'";

If anyone is searching for a situation when the current date is residing between two periods (start/end date) in Microsoft SQL, please find below
select id from campaign where (getdate() BETWEEN start_date AND end_date)

Related

SQl query to calculate number of active users at the end of everyday

I have three columns User_ID, New_Status and DATETIME.
New_Status contains 0(inactive) and 1(active) for users.
Every user starts from active status - ie. 1.
Subsequently table stores their status and datetime at which they got activated/inactivated.
How to calculate number of active users at the end of each date, including dates when no records were generated into the table.
Sample data:
| ID | New_Status | DATETIME |
+----+------------+---------------------+
| 1 | 1 | 2019-01-01 21:00:00 |
| 1 | 0 | 2019-02-05 17:00:00 |
| 1 | 1 | 2019-03-06 18:00:00 |
| 2 | 1 | 2019-01-02 01:00:00 |
| 2 | 0 | 2019-02-03 13:00:00 |
Format the date time value to a date only string and group by it
SELECT DATE_FORMAT(DATETIME, '%Y-%m-%d') as day, COUNT(*) as active
FROM test
WHERE New_Status = 1
GROUP BY day
ORDER BY day
In MySQL 8 you can use the row_number() window function to get the last status of a user per day. Then filter for the one that indicate the user was active GROUP BY the day and count them.
SELECT date(x.datetime),
count(*)
FROM (SELECT date(t.datetime) datetime,
t.new_status,
row_number() OVER (PARTITION BY date(t.datetime)
ORDER BY t.datetime DESC) rn
FROM elbat t) x
WHERE x.rn = 1
AND x.new_status = 1
GROUP BY x.datetime;
If not all days are in the table you need to create a (possibly derived) table with all days and cross join it.
Find out the last activity status of users whose activity was changed for each day
select User_ID, New_Status, DATE_FORMAT(DATETIME, '%Y-%m-%d')
from activity_table
where not exists
(
select 1
from activity_table at
where at.User_ID = activity_table.User_ID and
DATE_FORMAT(at.DATETIME, '%Y-%m-%d') = DATE_FORMAT(activity_table.DATETIME, '%Y-%m-%d') and
at.DATETIME > activity_table.DATETIME
)
order by DATE_FORMAT(activity_table.DATETIME, '%Y-%m-%d');
This is not the solution yet, but a very very useful information before solution. Note that here not all dates are covered yet and the values are individual records, more precisely their last values on each day, ordered by the date.
Let's get aggregate numbers
Using the query above as a subselect and aliasing it into a table, you can group by DATETIME and do a select sum(new_Status) as activity, count(*) total, DATETIME so you will know that activity - (total - activity) is the difference in comparison to the previous day.
Knowing the delta for each day present in the result
At the previous section we have seen how the delta can be calculated. If the whole query in the previous section is aliased, then you can self join it using a left join, with pairs of (previous date, current date), still having the gaps of dates, but not worrying about that just yet. In the case of the first date, its activity is the delta. For subsequent records, adding the previous day's delta to their delta yields the result you need. To achieve this you can use a recursive query, supported by MySQL 8, or, alternatively, you can just have a subquery which sums the delta of previous days (with special attention to the first date, as described earlier) will and adding the current date's delta yields the result we need.
Fill the gaps
The previous section would already perfectly work (assuming the lack of integrity problems), assuming that there were activity changes for each day, but we will not continue with the assumption. Here we know that the figures are correct for each date where a figure is present and we will need to just add the missing dates into the result. If the results are properly ordered, as they should be, then one can use a cursor and loop the results. At each record after the first one, we can determine the dates that are missing. There might be 0 such dates between two consequent dates or more. What we do know about the gaps is that their values are exactly the same as the previous record, that do has data. If there were no activity changes on a given date, then the number of active users is exactly the same as in the previous day. Using some structure, like a table you can generate the results you have with the knowledge described here.
Solving possible integrity problems
There are several possibilities for such problems:
First, a data item might exist prior to the introduction of this table's records were started to be spawned.
Second, bugs or any other causes might have made a pause in creating records for this activity table.
Third, the addition of user is or was not necessarily generating an activity change, since its popping into existence renders its previous state of activity undefined and subject to human standards, which might change over time.
Fourth, the removal of user is or was not necessarily generating an activity change, since its popping out of existence renders is current state of activity undefined and subject to human standards, which might change over time.
Fifth, there is an infinity of other issues which might cause data integrity issues.
To cope with these you will need to comprehensively analyze whatever you can from the source-code and the history of the project, including database records, logs and humanly available information to detect such anomalies, the time they were effective and figure out what their solution is if they exist.
EDIT
In the meantime I was thinking about the possibility of a user, who was active at the start of the day being deactivated and then activated again by the end of the day. Similarly, an inactive user during a day might be activated and then finally deactivated by the end of the day. For users that have more than an activation at the start of the day, we need to compare their activity status at the start and the end of the day to find out what the difference was.
SELECT
DATE(DATETIME),
COUNT(*)
FROM your_table
WHERE New_Status = 1
GROUP BY User_ID,
DATE(DATETIME)
For MySQL
WITH RECURSIVE
cte AS (
SELECT MIN(DATE(DT)) dt
FROM src
UNION ALL
SELECT dt + INTERVAL 1 DAY
FROM cte
WHERE dt < ( SELECT MAX(DATE(DT)) dt
FROM src )
),
cte2 AS
(
SELECT users.id,
cte.dt,
SUM( CASE src.New_Status WHEN 1 THEN 1
WHEN 0 THEN -1
ELSE 0
END ) OVER ( PARTITION BY users.id
ORDER BY cte.dt ) status
FROM cte
CROSS JOIN ( SELECT DISTINCT id
FROM src ) users
LEFT JOIN src ON src.id = users.id
AND DATE(src.dt) = cte.dt
)
SELECT dt, SUM(status)
FROM cte2
GROUP BY dt;
fiddle
Do not forget to adjust max recursion depth.
Here is what I believe is a good solution for this problem of yours:
SELECT SUM(New_Status) "Number of active users"
, DATE_FORMAT(DATEC, '%Y-%m-%d') "Date"
FROM TEST T1
WHERE DATE_FORMAT(DATEC,'%H:%i:%s') =
(SELECT MAX(DATE_FORMAT(T2.DATEC,'%H:%i:%s'))
FROM TEST T2
WHERE T2.ID = T1.ID
AND DATE_FORMAT(T1.DATEC, '%Y-%m-%d') = DATE_FORMAT(T2.DATEC, '%Y-%m-%d')
GROUP BY ID
, DATE_FORMAT(DATEC, '%Y-%m-%d'))
GROUP BY DATE_FORMAT(DATEC, '%Y-%m-%d');
Here is the DEMO

Check if date range is in month (calendar)

I'm trying to check with sql if a date range (with start and end date) is in a month (regarding year). Month and year are given variables (GET-variables).
This is my table:
+----+------+-------+-----+
| id | name | start | end |
+----+------+-------+-----+
Because an appointment could be just one date (start and end would be the same date), I have to consider this.
My query
SELECT
id,
name,
start,
end
FROM appointments
WHERE (MONTH(start) = ? AND YEAR(end) = ?) OR (MONTH(start) = ? AND YEAR(end) = ?)
ORDER BY start
The problem with this query is that it just checks the start-month and the end-month not the months between. For example if an appointment is over 5 months, this query fails.
If you want to know if a particular month is in the range, then I would suggest turning the dates into a YYYYMM format. This makes the logic relatively easy to express:
where ?*100 + ? between year(start)*100 + month(start) and
year(end)*100 + month(end)

SQL - Query for a meeting room

I need a help to create a query to return if the period selected already has a meeting in the room.
I have table rep_reuniao with the following design
ID --> int
ROOM --> int
DATE_BEGIN --> DATETIME
DATE_END --> DATETIME
Now an insert of a row in the table rep_reuniao looks like this
ID --> 1
ROOM --> 2
DATE_BEGIN --> 2014-02-21 17:00:00
DATE_END --> 2014-02-21 18:00:00
I will create an Jquery Ajax script to return a true or false JSON response based on the selected datetime.
So the query will have the ROOM, DATE_BEGIN in this format 2014-02-21 17:00 and DATE_END in the same format 2014-02-21 18:00
I Have checked another responses, but the most of them is a query to show the Rooms available. In my desing, a room is always available, except when exist a register in the table.
Example 1
ROOM 1 is already in use from 2014-02-21 17:00:00 to 2014-02-21 18:00:00
Need create a query to return a value (Anything) if the user select a room in:
BEGIN = 2012-02-21 16:30:00
END = 2012-02-21 17:30:00
See? The room in use in the table begins 17:00 so today my query easly return a value if the begin selected by the user is the same. But the End date selected by the user is between the time already in use.
Well, the selected time by the user cannot be in use lookin into the begin date and end date. I have no ideas right now, anyone can help me?
You need to check if one date range overlaps another date range.
New Date Range = |-----------|
Test1 = |=====|
Test2 = |=====|
Test3 = |=====|
Test4 = |=====|
Test5 = |=====|
Test6 = |=================|
Only Test1 and Test5 do not overlap.
Sorry if has any English errors. If someone are able to fix, I'll appreciate.
The correct logic over overlapping timeframes is that two time frames overlap when both these conditions are true:
the first starts before the other ends
the first ends after the other starts
In SQL, these can be expressed easily:
SELECT r.*
FROM rep_reuniao r
WHERE #UserBegin <= r.DATE_END AND
#UserEnd >= r.DATE_BEGIN;
Note that this can return multiple rows, when multiple meetings occur during the specified timeframe.
If you want true/false or 0/1, then use aggregation and case:
SELECT (case when count(*) = 0 then 'false' else 'true' end) as HasOverlappingRooms
FROM rep_reuniao r
WHERE #UserBegin <= r.DATE_END AND
#UserEnd >= r.DATE_BEGIN;
Try this, if the query returns any rows that means the room is already booked.
A simple explanation is the user gives #UserBegin and #UserEnd time that they need the room for.
And if the #UserBegin is between a row's begin and end then it is already booked.
Similarly, if the #UserEnd is between a row's begin and end, even then the room is booked.
SELECT *
FROM rep_reuniao r
WHERE (#UserBegin BETWEEN r.DATE_BEGIN AND r.DATE_END)
OR (#UserEnd BETWEEN r.DATE_BEGIN AND r.DATE_END)
OR (#UserBegin <= r.DATE_BEGIN AND #UserEnd >= r.DATE_END)
From here you can extend this to return a bool like shown
SELECT CASE WHEN EXISTS ( /* The above query */ ) THEN 0 ELSE 1
This should return 1 or 0, when the room is available or not respectively.
You could try the following query. It should catch when a date in the table falls within your range, or when you range falls within a date in the table:
SELECT CASE WHEN COUNT(*) > 1 THEN 1 ELSE 0 END AS AlreadyBooked
FROM rep_reuniao r
WHERE (#UserBegin BETWEEN r.DATE_BEGIN AND r.DATE_END)
OR (r.DATE_BEGIN BETWEEN #UserBegin AND #UserEnd)
The query checks for any rows where either #UserBegin falls within the range in the row or where the row's start date falls withing #userBegin - #UserEnd. It will find cases where the ranges partially overlap, or where either range falls within the other.

Query a MySQL Database and Group By Date Range to Create a Chart

I'm looking to create the following chart from a MySQL database. I know how to actually create the chart (using excel or similar program), my problem is how to get the data needed to create the chart. In this example, I can see that on January 1, 60 tickets were in the state illustrated by the green line.
I need to track the historical state of tickets of a project through a date range. The date range is determined by a project manager (in this case it's January 1st through January 9th).
For each ticket, I have the following set of historical data. Each time something changes in the ticket (state, description, assignee, customer update, and other attributes not shown in this problem), a "timestamp" entry is made in the database.
ticket_num status_changed_date from_state to_state
123456 2011-01-01 18:03:44 -- 1
123456 2011-01-01 18:10:26 1 2
123456 2011-01-01 14:37:10 2 2
123456 2011-01-02 07:55:44 2 3
123456 2011-01-03 06:12:18 3 2
123456 2011-01-04 19:03:43 3 3
123456 2011-01-05 02:05:24 3 4
123456 2011-01-06 18:13:28 4 4
123456 2011-01-07 13:14:48 4 5
123456 2011-01-09 01:35:39 5 5
How can I query the database for a given time (determined by my script) and find out what state each of the tickets are in?
For example: To produce the chart shown above, given the date 2011-01-02 12:00:00, how many tickets were in the state "2"?
I've tried querying the database with specific dates and ranges, but can't figure out the proper way to get the data to create the chart. Thanks in advance for any help.
I'm not exactly sure I know what you want. But . . .
Assuming a table definition like:
create table ticket_data (ticket_num int,
status_changed_date datetime,
from_state int,
to_state int);
The following, for example would give you the number of values per day:
select date(status_changed_date) as status_date, count(*)
from ticket_data
group by status_date;
Now, if you want just from_state = 2, just add a where clause in to that effect. If you want just the ones on Jan 2, then add in where date(status_changed_date) = '2011-01-02'
Or, if you you're looking for the distinct number of tickets per day then, change count(*) to count(distinct ticket_num)
Is this what you're asking? SQL Fiddle here
Ok so if you are trying to get a count of records in a certain state at a certain time, I think a stored proc might be necessary.
CREATE PROCEDURE spStatesAtDate
#Date datetime,
#StateId int
AS
BEGIN
SET NOCOUNT ON;
SELECT COUNT(*) as Count
FROM ticket_table t1
WHERE to_state = #StateId AND status_changed_date < #Date
AND status_changed_date = (SELECT MAX(status_changed_date) FROM ticket_table t2 where t2.ticket_num=t1.ticket_num AND status_changed_date < #Date)
END
then to call this for the above example, you're query would look like
EXEC spStatesAtDate #Date='2011-01-02 12:00:00', #StateId=2
You can use a subquery to select the last modification date before a given point grouped by ticket_num and then select the states at this time.
SELECT
ticket_num,
to_state,
status_changed_date
FROM
tickets
WHERE
status_changed_date IN (
SELECT MAX(status_changed_date)
FROM tickets
WHERE status_changed_date < '2012-02-01 01:00:00'
GROUP BY ticket_num
)
It all boils down to common question: how to get list of items and their most recent statuses. So. Given one issue, we can get its most recent status with query:
select to_state
from ticket_states
where ticket_num = t.ticket_num
order by status_changed_date desc
limit 1
Next, we need to get all applicable distinct issue ids, which is a simple distinct select:
select distinct ticket_num from ticket_states
With these two subqueries we can already start building. For example, current list of issues and their latest statuses before specified date would be:
select t.ticket_num
, (select to_state
from ticket_states
where ticket_num = t.ticket_num
and status_changed_date <= '2012-01-01'
order by status_changed_date desc
limit 1) as last_state
from (select distinct ticket_num
from ticket_states) t;
All issues, which were non-existant at at the specified time will have last_state set to null.
This probably isn't the best way of doing this, but it is first which came to mind. I'll leave other stuff to you. Also I should mention that this is not a very efficient solution also.

mysql: just select something happen in the weekend

I have a table, store all user created events, and with a field "start_time" and a "end_time"
How to select all events just happens in weekend?
Use DAYOFWEEK():
Returns the weekday index for date (1 = Sunday, 2 = Monday, …, 7 = Saturday). These index values correspond to the ODBC standard.
Something like
SELECT * FROM events WHERE DAYOFWEEK(start_time) = 7
OR DAYOFWEEK(start_time) = 1
...
I would suggest using the BETWEEN operator. Something like:
SELECT ... FROM EVENTS WHERE
START_TIME BETWEEN :weekend_start AND :weekend_end
OR END_TIME BETWEEN :weekend_start AND :weekend_end
(where you then figure out the start and end times for the weekend in question, and bind them in appropriately)
The above will find anything that at least overlaps with the weekend. If the event must be entirely contained with the weekend (i.e. can't start before and can't start after) then just change the OR to an AND.