On my database table I have 2 columns, start_date and end_date.
Sample data would be:
-------------------------------
start_date | end_date
-------------------------------
2017-11-01 2017-11-02
2017-11-03 2017-11-07
2017-11-20 2017-11-28
2017-11-13 2017-12-02
-------------------------------
I need to find if there are 5 consecutive days that are not yet used, which in this case, there is:
(2017-11-08 to 2017-11-13)
I'm using PHP and MySQL.
Thanks in advance!
You'd need to check for edge cases depending on your actual data and if there were no overlap dates, but this is a good start for the provided data.
Assuming table and data as defined as below:
CREATE TABLE
`appointments`
(
`appointment_id` INT PRIMARY KEY AUTO_INCREMENT,
`start_date` DATE,
`end_date` DATE
);
INSERT INTO
`appointments`
(`start_date`, `end_date`)
VALUES
('2017-11-01', '2017-11-02'),
('2017-11-03', '2017-11-07'),
('2017-11-20', '2017-11-28'),
('2017-11-13', '2017-12-02');
If you order the rows, and take the lag from the end date before it, and take any gaps of 5 or more. In SQL Server there are LAG functions, but here's a way of doing the same. Then once you have a table of all rows and their corresponding gaps, you take the start date of that period, and create the gap period from the number of days between. Since TIMESTAMPDIFF is inclusive, you need to subtract a day.
SET #end_date = NULL;
SELECT
DATE_ADD(`start_date`, INTERVAL -(`gap_from_last`-1) DAY) AS `start_date`,
`start_date` AS `end_date`
FROM
(
SELECT
`appointment_id`,
CASE
WHEN #end_date IS NULL THEN NULL
ELSE TIMESTAMPDIFF(DAY, #end_date, `start_date`)
END AS `gap_from_last`,
`start_date`,
#end_date := `end_date` AS `end_date` -- Save the lag date from the row before
FROM
`appointments`
ORDER BY
`start_date`,
`end_date`
) AS `date_gap` -- Build table that has the dates and the number of days between
WHERE
`gap_from_last` > 5;
Provides:
start_date | end_date
------------------------
2017-11-08 | 2017-11-13
Edit: Oops! Forgot the SQLFiddle (http://sqlfiddle.com/#!9/09cfce/16)
SELECT x.end_date + INTERVAL 1 DAY unused_start
, MIN(y.start_date) unused_end
FROM appointments x
JOIN appointments y
ON y.start_date >= x.end_date
GROUP
BY x.start_date
HAVING DATEDIFF(MIN(y.start_date),unused_start) >= 5;
Related
I am currently developing an application that displays documents and allows the members to search for these documents by a number of different parameters, one of them being date range.
The problem I am having is that the database schema was not developed by myself and the creator of the database has created a 'date' table with fields for 'day','month','year'.
I would like to know how I can select a specific day, month, year from the table and create a date object in SQL so that I can compare dates input by the user using BETWEEN.
Below is the structure of the date table:
CREATE TABLE IF NOT EXISTS `date` (
`deposition_id` varchar(11) NOT NULL default '',
`day` int(2) default NULL,
`month` int(2) default NULL,
`year` int(4) default NULL,
PRIMARY KEY (`deposition_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
When you have integer values for year, month and day you can make a DATETIME by combining MAKEDATE() and DATE_ADD(). MAKEDATE() with a constant day of 1 will give you a DATETIME for the first day of the given year, and then you can add to it the month and day with DATE_ADD():
mysql> SELECT MAKEDATE(2013, 1);
+-------------------+
| MAKEDATE(2013, 1) |
+-------------------+
| 2013-01-01 |
+-------------------+
mysql> SELECT DATE_ADD(MAKEDATE(2013, 1), INTERVAL (3)-1 MONTH);
+---------------------------------------------------+
| DATE_ADD(MAKEDATE(2013, 1), INTERVAL (3)-1 MONTH) |
+---------------------------------------------------+
| 2013-03-01 |
+---------------------------------------------------+
mysql> SELECT DATE_ADD(DATE_ADD(MAKEDATE(2013, 1), INTERVAL (3)-1 MONTH), INTERVAL (11)-1 DAY);
| DATE_ADD(DATE_ADD(MAKEDATE(2013, 1), INTERVAL (3)-1 MONTH), INTERVAL (11)-1 DAY) |
+----------------------------------------------------------------------------------+
| 2013-03-11 |
+----------------------------------------------------------------------------------+
So to answer the OP's question:
SELECT * FROM `date`
WHERE DATE_ADD(DATE_ADD(MAKEDATE(year, 1), INTERVAL (month)-1 MONTH), INTERVAL (day)-1 DAY)
BETWEEN '2013-01-01' AND '2014-01-01';
You can use STR_TO_DATE() function.
The simplest way to do this is:
DATE(CONCAT_WS('-', year, month, day))
LPAD is not necessary as #pbarney pointed out earlier. If you are comparing with another date object, it's not strictly necessary to wrap it with DATE as MySQL will cast it automatically:
some_date_field > CONCAT_WS('-', year, month, day)
To build a sortable date string from that, you'll need CONCAT to join the bits together and LPAD to make sure the month and day fields are two digits long. Something like this:
CONCAT(`year`,'-',LPAD(`month`,2,'00'),'-',LPAD(`day`,2,'00'))
Once you have that, you should be able to use BETWEEN, as they'll be in a sortable format. However if you still need to convert them to actual datetime fields, you can wrap the whole thing in UNIX_TIMESTAMP() to get a timestamp value.
So you'd end up with something like this:
SELECT UNIX_TIMESTAMP(CONCAT(`year`,'-',LPAD(`month`,2,'00'),'-',LPAD(`day`,2,'00'))) as u_date
WHERE u_date BETWEEN timestamp_1 and timestamp_2
However, be aware that this will be massively slower than if the field was just a simple timestamp in the first place. And you should definitely make sure you have an index on the year, month and day fields.
Expanding this answer, here's my take on it:
DELIMITER $$
CREATE FUNCTION fn_year_month_to_date(var_year INTEGER,
var_month enum('01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12')
)
RETURNS DATE
BEGIN
RETURN (MAKEDATE(var_year, 1) + INTERVAL (var_month - 1) MONTH);
END $$
DELIMITER ;
SELECT fn_year_month_to_date(2020, 12)
;
Try to use CONCAT() and make it one field and compare .
Am not sure you can compare it as date after concatenation.
You can compare as integer.
concatinate year month day and make an integer like this 20101017 and compare.
Hopefully :)
Sorry if the title is a bit vague..
I have a database looking like this:
orderid | roomname | date(DATE) | from(TIME) | to(TIME)
Example-data:
1231 | E12 | 2013-04-05 | 07:00:00 | 10:00:00
1671 | E12 | 2013-04-05 | 13:00:00 | 14:00:00
I'm for example searching up a certain date and obviously getting all reservations on that day.
As you can see on the example-data, the room is available between 10:00:00 and 13:00:00. How can I catch this?
I was thinking about looping through time 07:00:00-16:00:00 (with one query for each) and check if I get any results from sql. If I do get results, I will know that the room is busy, but since there are unknowns here (ex. 08:00:00 and 09:00:00 doesn't exists), I will get false-positives on this.
Any tips here?
One way would be using a 'calendar table', or if you're only ever interested in one day a 'clock table' would do. The following illustrates (roughly) how you'd use it.
SELECT clock.time AS available
FROM clock
LEFT JOIN bookings ON clock.time BETWEEN bookings.from AND bookings.to
AND bookings.date = '2013-01-01'
WHERE bookings.id IS NULL
http://www.brianshowalter.com/calendar_tables is an example of how to create a calendar in MySQL
With this data:
create table rooms
(
orderid int not null,
roomname varchar(8) not null,
date date not null,
`from` time not null,
`to` time not null
);
insert into rooms values (1231, 'E12', '2013-04-05', '07:00', '10:00');
insert into rooms values (1671, 'E12', '2013-04-05', '13:00', '14:00');
to get the available time interval/slot, you can issue this query:
SELECT DATE_FORMAT(r1.`to`, '%T') AS `From`, DATE_FORMAT(min(r2.`from`), '%T') AS `To`
FROM
rooms r1 JOIN rooms r2
ON r1.`to`< r2.`from`
WHERE r1.date = '2013-04-05' AND r1.roomname = 'E12'
GROUP BY r1.`to`
HAVING
NOT EXISTS (SELECT NULL FROM rooms r3
WHERE r1.`to` < r3.`to`
AND min(r2.`from`) > r3.`from`)
the above query will return:
10:00:00 13:00:00
Here's the SQL fiddle: http://sqlfiddle.com/#!2/3c124/25
Note: the above query was kindly adapted from this answer by #fthiella:
https://stackoverflow.com/a/14139835/114029
With this additional query:
SELECT (COUNT(*) = 0) AS Available
FROM rooms
WHERE roomname = 'E12' AND date = '2013-04-05' AND
(
(`from` < MAKETIME(10,00,00) AND `to` > MAKETIME(10,00,00)) OR
(`from` < MAKETIME(13,00,00) AND `to` > MAKETIME(13,00,00))
)
It'll return 1, that is, there's no reservation between the given start time (from) and end time (to) and so the room is available.
Here's the SQL Fiddle to play with the data: http://sqlfiddle.com/#!2/3c124/1
I have a scenario where I need to pull up delivery dates based on a table below (Example)
job_id | delivery_date
1 | 2013-01-12
2 | 2013-01-25
3 | 2013-02-15
What I'm trying to do is show the user all the delivery dates that start with the earliest (in this case it would be 2013-01-12) and add an another 21 days to that. Basically, the output I would expect it to show of course, the earliest date being the starting date 2013-01-12 and 2013-01-25. The dates past the February date are of no importance since they're not in my 21 date range. If it were a 5 day range, for example, then of course 2013-01-25 would not be included and only the earliest date would appear.
Here is main SQL clause I have which only shows jobs starting this year forward:
SELECT date, delivery_date
FROM `job_sheet`
WHERE print_status IS NULL
AND job_sheet.date>'2013-01-01'
Is it possible to accomplish this with 1 SQL query, or must I go with a mix of PHP as well?
You can use the following:
select *
from job_sheet
where print_status IS NULL
and delivery_date >= (select min(delivery_date)
from job_sheet)
and delivery_date <= (select date_add(min(delivery_date), interval 21 day)
from job_sheet)
See SQL Fiddle with Demo
If you are worried about the dates not being correct, if you use a query then it might be best to pass in the start date to your query, then add 21 days to get the end date. Similar to this:
set #a='2013-01-01';
select *
from job_sheet
where delivery_date >= #a
and delivery_date <= date_add(#a, interval 21 day)
See SQL Fiddle with Demo
SELECT date,
delivery_date
FROM job_sheet
WHERE print_status IS NULL
AND job_sheet.date BETWEEN (SELECT MIN(date) FROM job_sheet) AND
(SELECT MIN(date) FROM job_sheet) + INTERVAL 21 DAY
SELECT j.job_id
, j.delivery_date
FROM `job_sheet` j
JOIN ( SELECT MIN(d.delivery_date) AS earliest_date
FROM `job_sheet` d
WHERE d.delivery_date >= '2013-01-01'
) e
ON j.delivery_date >= e.earliest_date
AND j.delivery_date < DATE_ADD(e.earliest_date, INTERVAL 22 DAY)
AND j.print_status IS NULL
ORDER BY j.delivery_date
(The original query has a predicate on job_sheet.date; the query above references the d.delivery_date... change that if it is supposed to be referencing the date column instaed.)
If the intent is to only show delivery_date values from today forward, then change the literal '2013-01-01' to an expression that returns the current date, e.g. DATE(NOW())
I have a table of users which has a date field for their birthday:
buddy_auto_id int(11) PK
user_id varchar(128)
buddy_user_id varchar(128)
buddy_name varchar(128)
buddy_bday date
buddyuser_id varchar(20)
active enum('Yes','No')
requestsentby int(11)
whenrequested timestamp
I'm trying to find the 3 users whose birthdays fall soonest compared to todays date and then display the number of days until their birthday ordered by soonest first.
Is this possible within a SQL query or do I have to pull it out and let PHP do the equation?
Many thanks
We first need to calculate the next birthday, then order by that value:
select *,
buddy_bday + interval
if(
month(buddy_bday) < month(now()) or
(month(buddy_bday) = month(now()) and day(buddy_bday) < day(now())),
year(now())+1,
year(now())
) - year(buddy_bday) year as next_bday
from buddies order by next_bday - date(now());
The long if statement figures out whether the buddy already had his/her birthday this year.
This should be possible with SQL. You need to compare the current date with a birthday expressed as from this year or next year, depending on whether we've past it in the current year. Once you have this "next birthday" date, use DATEDIFF function to determine number of days distant from current date.
SELECT *,
DATEDIFF(
# determine date of next birthday by adding age in years to birthday year
DATE_ADD(buddy_bday, INTERVAL
YEAR(CURDATE())-YEAR(buddy_bday)
# add a year if we celebrated birthday already this year
+(MONTH(buddy_bday)<MONTH(CURDATE()) OR (MONTH(buddy_bday)=MONTH(CURDATE()) AND DAY(buddy_bday) < DAY(CURDATE())))
YEAR),
CURDATE())
AS days_to_next_bday
FROM user_table
ORDER BY days_to_next_bday
LIMIT 3;
You should use a DAYOFYEAR function. Try this query -
SELECT buddy_auto_id , buddy_bday FROM table_name
WHERE DAYOFYEAR(buddy_bday) - DAYOFYEAR(NOW()) > 0
ORDER BY DAYOFYEAR(buddy_bday) - DAYOFYEAR(NOW())
LIMIT 3;
So, this query works only for current year.
EDITED query 2:
This one works for all dates.
CREATE TABLE birtdays(
buddy_auto_id INT(11) NOT NULL AUTO_INCREMENT,
buddy_bday DATE DEFAULT NULL,
PRIMARY KEY (buddy_auto_id)
);
INSERT INTO birtdays VALUES
(1, '2011-10-04'),
(2, '2011-03-01'),
(3, '2011-11-29'),
(4, '2011-11-10'),
(5, '2011-12-29'),
(6, '2011-11-30'),
(7, '2011-12-08'),
(8, '2011-09-17'),
(9, '2011-12-01'),
(10, '2011-12-11');
SELECT buddy_auto_id, buddy_bday
FROM
birtdays, (SELECT #day_of_year:=DAYOFYEAR(NOW())) t
ORDER BY
DAYOFYEAR(buddy_bday + INTERVAL YEAR(NOW()) - YEAR(buddy_bday) YEAR) - #day_of_year
+ IF (DAYOFYEAR(buddy_bday + INTERVAL YEAR(NOW()) - YEAR(buddy_bday) YEAR) - #day_of_year > 0, 0, DAYOFYEAR(STR_TO_DATE(CONCAT(YEAR(NOW()), '-12-31'), '%Y-%m-%d')))
LIMIT 3;
+---------------+------------+
| buddy_auto_id | buddy_bday |
+---------------+------------+
| 6 | 2011-11-30 |
| 7 | 2011-12-08 |
| 10 | 2012-02-11 |
+---------------+------------+
I have a field called dPostTime, which has the date and time stored of an entry.
If I wanted to create a page that showed me the number of entries per hour, would I need to have 24 queries, one for each hour, or what's the best way to do this.
I'd like the chart to appear as follows:
5-6pm - 24 posts
6-7pm - 56 posts
7-8pm - 34 posts
8-9pm - 35 posts
etc......
MySQL doesn't have recursive functionality, so you're left with using the NUMBERS table trick -
Create a table that only holds incrementing numbers - easy to do using an auto_increment:
DROP TABLE IF EXISTS `example`.`numbers`;
CREATE TABLE `example`.`numbers` (
`id` int(10) unsigned NOT NULL auto_increment,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Populate the table using:
INSERT INTO NUMBERS
(id)
VALUES
(NULL)
...for as many values as you need.
Use DATE_ADD to construct a list of dates, increasing the days based on the NUMBERS.id value. Replace "2010-01-01" and "2010-01-02" with your respective start and end dates (but use the same format, YYYY-MM-DD HH:MM:SS) -
SELECT x.dt
FROM (SELECT DATE_FORMAT(DATE_ADD('2010-01-01', INTERVAL (n.id - 1) HOUR), '%H:%i') AS dt
FROM numbers n
WHERE DATE_ADD('2010-01-01', INTERVAL (n.id - 1) HOUR) <= '2010-01-02' ) x
LEFT JOIN onto your table of data based on the datetime portion.
SELECT x.dt,
COALESCE(COUNT(a.dPostTime), 0) AS numPerHour
FROM (SELECT DATE_FORMAT(DATE_ADD('2010-01-01', INTERVAL (n.id - 1) HOUR), '%H') AS dt
FROM numbers n
WHERE DATE_ADD('2010-01-01', INTERVAL (n.id - 1) HOUR) <= '2010-01-02' ) x x
LEFT JOIN YOUR_TABLE a ON DATE_FORMAT(a.dPostTime, '%H') = x.dt
GROUP BY x.dt
ORDER BY x.dt
Why Numbers, not Dates?
Simple - dates can be generated based on the number, like in the example I provided. It also means using a single table, vs say one per data type.
SELECT COUNT(*),
HOUR(`dPostTime`) AS `hr`
FROM `table`
GROUP BY `hr`
After that in php format hr to be equal 'hr - 1' - hr