How to merge more than two (3) tables in SQL - php

In a Facebook-like notification system I have three tables, like this....
table1
________________________________
| id | value_1 | timestamp |
|----|-----------|-------------|
| id | value21 | 2014-05-21 |
| id | value22 | 2014-05-22 |
|____|___________|_____________|
table2
________________________________
| id | value_2 | timestamp |
|----|-----------|-------------|
| id | value30 | 2014-05-30 |
|____|___________|_____________|
table3
________________________________
| id | value_3 | timestamp |
|----|-----------|-------------|
| id | value01 | 2014-05-01 |
| id | value27 | 2014-05-27 |
|____|___________|_____________|
To return all these data in one row, I used SQL union...
(SELECT value_1 FROM table1)
UNION
(SELECT value_2 FROM table2)
UNION
(SELECT value_3 FROM table3)
...but it brings it all together in one column. Instead of that, I need the result to be in a table like this,
in order of Timestamp
________________________________________________________
| id | value_1 | value_2 | value_3 | timestamp |
|----|-----------|-----------|-----------|-------------|
| id | null | null | value01 | 2014-05-01 |
| id | value21 | null | null | 2014-05-21 |
| id | value22 | null | null | 2014-05-22 |
| id | null | null | value27 | 2014-05-27 |
| id | null | value30 | null | 2014-05-30 |
|____|___________|___________|___________|_____________|
Is it possible to do, only with SQL without handing over the job to the PHP engine?
Any ideas appreciated.

You can do this using union all and order by:
SELECT id, value_1, NULL as value_2, NULL as value_3, timestamp
FROM table1
UNION ALL
SELECT id, NULL, value_2, NULL, timestamp
FROM table2
UNION ALL
SELECT id, NULL, NULL, value_3, timestamp
FROM table3
ORDER BY timestamp;
Note that union all is more efficient than union because it does not return duplicates.

You need to add dummy columns in each part of the union:
SELECT value_1
, cast(null as <type_of_value2>) as value_2
, cast(null as <type_of_value3>) as value_3
, ts
FROM table1
UNION
SELECT cast(null as <type_of_value1>) as value_1
, value_2
, cast(null as <type_of_value3>) as value_3
, ts
FROM table2
UNION
SELECT cast(null as <type_of_value1>) as value_1
, cast(null as <type_of_value2>) as value_2
, value_3
, ts
FROM table3
ORDER BY ts

Related

Use outer alias in subquery with 4 Tables in mysql

Hello guys I have 4 tables as an key-value store in mysql
t1 (article): t2:
| id | date | | id | key | value |
------------- ---------------------------
| 1 | 2016 | | 1 | title | title1 |
| 2 | 2017 | | 1 | user_id | 1 |
| 3 | 2018 | | 2 | title | title2 |
------------- | 2 | user_id | 2 |
| 3 | title | title3 |
| 3 | user_id | 1 |
---------------------------
t1 (user): t2:
| id | date | | id | key | value |
------------- -------------------------
| 1 | NULL | | 1 | name | user1 |
| 2 | NULL | | 2 | name | user2 |
------------- -------------------------
SELECT t1.id,
GROUP_CONCAT(IF(t2.key='title',t2.value,NULL)) AS title,
t1.date,
GROUP_CONCAT(IF(t2.key='user_id',t2.value,NULL)) AS user_id,
(
SELECT GROUP_CONCAT(IF(t4.key='user_name',t4.value,NULL))
FROM t4
GROUP BY t4.id
HAVING t4.id = user_id
) AS user_name
FROM t1
INNER JOIN t2
ON t1.id = t2.id
GROUP BY t1.id
i want to print out the name of the user that is stored as an id in t2 like:
| id | title | date | user_id | user_name |
------------------------------------------------
| 1 | title1 | 2016 | 1 | user1 |
| 2 | title2 | 2017 | 2 | user2 |
| 3 | title3 | 2018 | 1 | user1 |
------------------------------------------------
i have tested WHERE clause and HAVING clause, but nothing works for me.
I found your table references way too confusing, so I used an interpretation of the sample data. I only needed 3 of the 4 tables by the way. Demo
MySQL 5.6 Schema Setup:
CREATE TABLE articles
(`id` int, `date` date)
;
INSERT INTO articles
(`id`, `date`)
VALUES
(1, '2016-01-01'),
(2, '2017-01-01'),
(3, '2018-01-01')
;
CREATE TABLE users
(`id` int, `date` date)
;
INSERT INTO users
(`id`, `date`)
VALUES
(1, NULL),
(2, NULL)
;
CREATE TABLE t2_upper
(`id` int, `key` varchar(7), `value` varchar(6))
;
INSERT INTO t2_upper
(`id`, `key`, `value`)
VALUES
(1, 'title', 'title1'),
(1, 'user_id', '1'),
(2, 'title', 'title2'),
(2, 'user_id', '2'),
(3, 'title', 'title3'),
(3, 'user_id', '1')
;
CREATE TABLE t2_lower
(`id` int, `key` varchar(4), `value` varchar(5))
;
INSERT INTO t2_lower
(`id`, `key`, `value`)
VALUES
(1, 'name', 'user1'),
(2, 'name', 'user2')
;
Query 1:
select a.id, tn.value article_title, a.date, tu.id user_id, u.value user_name
from articles a
inner join (
select
*
from t2_upper
where `key` = 'title'
) tn on a.id = tn.id
inner join (
select
*
from t2_upper
where `key` = 'user_id'
) tu on a.id = tu.id
inner join (
select
*
from t2_lower
where `key` = 'name'
) u on tu.value = u.id
Results:
| id | article_title | date | user_id | user_name |
|----|---------------|------------|---------|-----------|
| 1 | title1 | 2016-01-01 | 1 | user1 |
| 2 | title2 | 2017-01-01 | 2 | user2 |
| 3 | title3 | 2018-01-01 | 3 | user1 |

Calculating Time Difference Between Current Row and Previous Row

I have following table for my web application and i want add another column to get time difference between current row and previous row. How can i achieve it?
Currently here is my sql call from php Application
$stmt = $db->prepare("SELECT device,lat,lon, speed, mode, DATE (`currentTime`) ,TIME_FORMAT(`currentTime`, '%H:%i:%s')
FROM myTable
WHERE device=? limit ?");
$stmt ->bind_param('ii', $device_Number ,$limit);
$stmt ->bind_result($device, $lat, $lon, $speed, $mode, $currentDate, $currentTime);
$stmt ->execute();
Here I give sample datas with datetime difference, here you are saving the data in 2 different column
so take date difference as 2 column 'timedifference' and 'daydifference'
testtime table
id date1 time1
1 2017-08-14 01:06:11
2 2017-08-14 01:09:13
3 2017-08-14 01:16:10
4 2017-08-14 01:21:00
5 2017-08-15 01:21:00
6 2017-08-15 02:13:00
Mysql Query is
SELECT A.id, A.time1, TIMESTAMPDIFF(SECOND,A.time1,B.time1) AS timedifference,
TIMESTAMPDIFF(DAY,A.date1,B.date1) AS daydifference
FROM testtime A INNER JOIN testtime B ON B.id = (A.id + 1)
ORDER BY A.id ASC
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(device INT NOT NULL
,lat DECIMAL(10,6) NOT NULL
,lon DECIMAL(10,6) NOT NULL
,speed DECIMAL(5,2)
,mode INT NOT NULL
,dt DATETIME NOT NULL
,PRIMARY KEY(device,dt)
);
INSERT INTO my_table VALUES
(117,1.415738,103.82360,28.8,3,'2017-07-12 22:07:40'),
(117,1.424894,103.82561,31.9,3,'2017-07-12 22:08:41'),
(117,1.429965,103.82674,10.9,3,'2017-07-12 22:09:47'),
(117,1.430308,103.82873, 5.2,3,'2017-07-12 22:10:47'),
(117,1.430542,103.83278,13.9,3,'2017-07-12 22:11:48'),
(117,1.430537,103.83325, 3.2,3,'2017-07-12 22:12:47');
SELECT x.*
, SEC_TO_TIME(TIME_TO_SEC(x.dt)-TIME_TO_SEC(MAX(y.dt))) diff
FROM my_table x
LEFT
JOIN my_table y
ON y.device = x.device
AND y.dt < x.dt
GROUP
BY x.device
, x.dt;
+--------+----------+------------+-------+------+---------------------+----------+
| device | lat | lon | speed | mode | dt | diff |
+--------+----------+------------+-------+------+---------------------+----------+
| 117 | 1.415738 | 103.823600 | 28.80 | 3 | 2017-07-12 22:07:40 | NULL |
| 117 | 1.424894 | 103.825610 | 31.90 | 3 | 2017-07-12 22:08:41 | 00:01:01 |
| 117 | 1.429965 | 103.826740 | 10.90 | 3 | 2017-07-12 22:09:47 | 00:01:06 |
| 117 | 1.430308 | 103.828730 | 5.20 | 3 | 2017-07-12 22:10:47 | 00:01:00 |
| 117 | 1.430542 | 103.832780 | 13.90 | 3 | 2017-07-12 22:11:48 | 00:01:01 |
| 117 | 1.430537 | 103.833250 | 3.20 | 3 | 2017-07-12 22:12:47 | 00:00:59 |
+--------+----------+------------+-------+------+---------------------+----------+
6 rows in set (0.00 sec)

MySQL query too slow, 70k rows 12 sec

This query runs for 12+ seconds on VPS. It joins 3 tables. Only first one "topcics" has about 70k rows, others are about 20 and "post_cc" about 1500.
SELECT topics.*, employee.username, accounts.ac_name, accounts.ac_mail
FROM topics
INNER JOIN employee ON employee.id_user = topics.id_owner
INNER JOIN accounts ON accounts.id_account = topics.id_account
WHERE topics.status IN ('1','3') AND ( topics.id_owner IN (12, 5) OR topics.id_post IN
(SELECT DISTINCT(id_post) FROM post_cc WHERE id_employee IN (12, 5) ) )
ORDER BY topics.creationdate DESC LIMIT 0,25
I have already tried (without any improvement) to remove subquery and first "employee" join. If I remove "accounts" join, query runs under 0.1 sec, but all tables data are needed for sorting purpose during paging.
Explain:
+----+--------------------+------------+-----------------+-----------------------+---------+---------+-----------------+-------+----------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+--------------------+------------+-----------------+-----------------------+---------+---------+-----------------+-------+----------------------------------------------+
| 1 | PRIMARY | topics | ALL | id_owner,id_account | NULL | NULL | NULL | 75069 | Using where; Using temporary; Using filesort |
| 1 | PRIMARY | accounts | ALL | PRIMARY | NULL | NULL | NULL | 5 | Using where; Using join buffer |
| 1 | PRIMARY | employee | eq_ref | PRIMARY | PRIMARY | 3 | topics.st_owner | 1 | Using where |
| 2 | DEPENDENT SUBQUERY | post_cc | unique_subquery | PRIMARY | PRIMARY | 8 | func,const | 1 | Using index; Using where |
+----+--------------------+------------+-----------------+-----------------------+---------+---------+-----------------+-------+----------------------------------------------+
I have added suggested keys as index, it improved time for 2 sec., but it's still too slow.
Shortened tables:
topics
+--------------------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------------+---------------------+------+-----+---------+----------------+
| id_post | int(10) unsigned | NO | PRI | NULL | auto_increment |
| id_account | int(10) unsigned | YES | MUL | 0 | |
| mail | varchar(256) | YES | MUL | NULL | |
| from_name | varchar(512) | YES | | NULL | |
| title | varchar(512) | YES | | NULL | |
| content | text | YES | | NULL | |
| id_owner | int(10) unsigned | YES | MUL | NULL | |
| creationdate | datetime | YES | | NULL | |
+--------------------+---------------------+------+-----+---------+----------------+
employee
+---------------------+-----------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------------------+-----------------------+------+-----+---------+----------------+
| id_employee | mediumint(8) unsigned | NO | PRI | NULL | auto_increment |
| id_user | mediumint(8) unsigned | NO | | NULL | |
| id_owner | tinyint(1) | YES | | 0 | |
| active | tinyint(1) | YES | | 1 | |
| username | varchar(64) | YES | | NULL | |
| email | varchar(128) | YES | | NULL | |
+---------------------+-----------------------+------+-----+---------+----------------+
accounts
+----------------------------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------------------------+---------------------+------+-----+---------+----------------+
| id_account | int(10) unsigned | NO | PRI | NULL | auto_increment |
| ac_mail | int(10) unsigned | YES | UNI | NULL | |
| ac_name | varchar(512) | YES | | NULL | |
| last_sync_time | datetime | YES | | NULL | |
+----------------------------+---------------------+------+-----+---------+----------------+
post_cc
+------------------------+---------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------------------+---------------------+------+-----+---------+-------+
| id_post | int(10) unsigned | NO | PRI | NULL | |
| id_employee | int(10) unsigned | NO | PRI | NULL | |
| notifications | tinyint(3) unsigned | YES | | 1 | |
+------------------------+---------------------+------+-----+---------+-------+
One likely suspect is that DEPENDENT SUBQUERY.
MySQL is processing that subquery for each row returned by the outer query (which isn't already filtered out by some other predicate.
To improve performance, consider re-writing that either as a JOIN operation or an EXISTS predicate.
To replace that with a JOIN operation, that will need to be an OUTER JOIN (rather than an INNER JOIN) because of the OR in the predicate.
As an example of one way to do that:
SELECT topics.*
, employee.username
, accounts.ac_name
, accounts.ac_mail
FROM topics
JOIN employee ON employee.id_user = topics.id_owner
JOIN accounts ON accounts.id_account = topics.id_account
LEFT
JOIN ( SELECT DISTINCT q.id_post
FROM post_cc q
WHERE q.id_employee IN (12, 5)
) p
ON p.id_post = topics.id_post
WHERE topics.status IN ('1','3')
AND ( topics.id_owner IN (12, 5)
OR p.id_post IS NOT NULL
)
ORDER BY topics.creationdate DESC LIMIT 0,25
I recommend you run an EXPLAIN on that, and see how that performs.
Another option is to consider an EXISTS predicate. Occassinally we can get this to perform better, but often times not.
SELECT topics.*
, employee.username
, accounts.ac_name
, accounts.ac_mail
FROM topics
JOIN employee ON employee.id_user = topics.id_owner
JOIN accounts ON accounts.id_account = topics.id_account
WHERE topics.status IN ('1','3')
AND ( topics.id_owner IN (12, 5)
OR EXISTS ( SELECT 1
FROM post_cc q
WHERE q.id_employee IN (12, 5)
AND q.id_post = topics.id_post
)
)
ORDER BY topics.creationdate DESC LIMIT 0,25
For performance, that's going to almost require a suitable covering index for the subquery in the EXISTS clause, for example:
ON post_cc (id_post, id_employee)
You can try running an EXPLAIN and see how that performs as well.
We see that MySQL isn't using an index on the topics table.
We might get MySQL to avoid an expensive "Using filesort" operation if we had an index with a leading column of creationdate.
And part of the problem is likely that OR in the predicate. We might try re-writing that query as two separate queries, and combining them with a UNION ALL set operation. But if we do that, we'd really like to see an index on topic being used (we probably won't improve performance by incurring two scans of 70,000 rows.
SELECT topics.*
, employee.username
, accounts.ac_name
, accounts.ac_mail
FROM topics
JOIN employee ON employee.id_user = topics.id_owner
JOIN accounts ON accounts.id_account = topics.id_account
WHERE topics.status IN ('1','3')
AND topics.id_owner IN (12, 5)
UNION ALL
SELECT topics.*
, employee.username
, accounts.ac_name
, accounts.ac_mail
FROM topics
JOIN employee ON employee.id_user = topics.id_owner
JOIN accounts ON accounts.id_account = topics.id_account
JOIN ( SELECT DISTINCT q.id_post
FROM post_cc q
WHERE q.id_employee IN (12, 5)
) p
ON p.id_post = topics.id_post
WHERE topics.status IN ('1','3')
AND ( topics.id_owner NOT IN (12, 5) OR topics.id_owner IS NULL )
ORDER BY 8 DESC LIMIT 0,25
With a query of that form, we're more likely to get MySQL use a suitable index on the topics table,
... ON topics (id_owner, status)
... ON topics (id_post, status, id_owner)
Why don't you use left join for post_cc table, and then use condition?
Something like this.
SELECT topics.*,
employee.username,
accounts.ac_name,
accounts.ac_mail
FROM topics
INNER JOIN employee ON employee.id_user = topics.id_owner
INNER JOIN accounts ON accounts.id_account = topics.id_account
LEFT JOIN post_cc ON id_employee IN (12, 5)
WHERE topics.status IN ('1','3')
AND ( topics.id_owner IN (12, 5) OR topics.id_post IN (post_cc.post_id)
ORDER BY topics.creationdate DESC LIMIT 0,25;
The culprit:
75069 (rows) | Using where; Using temporary; Using filesort
That's what we need to get rid of.
Possible solution: add an index on topics.creationdate.
As a side note, the query also has conditions on id_post, st_owner, and status, therefore a composite index on topics(creationdate, id_post, st_owner, status) (or any permutation of the last three columns -- test with your data set) might help even further. However, your query seems to pull most of your table anyways, so I expect a simple index will suffice.

SQL query help request

user table
.--------------------------------.
| userid| val | Create_Date |
.--------------------------------.
| U1 | foo | 2011-05-01 |
| U1 | foo | 2011-11-18 |
| | | |
| U2 | foo | 2011-11-18 |
| U2 | foo | 2011-11-28 |
| | | |
| U3 | foo | 2011-04-11 |
| U3 | foo | 2011-11-04 |
| | | |
| U4 | foo | 2011-11-21 |
| U4 | foo | 2011-11-28 |
.________________________________.
I have a table similar to the above one. What I need to get is the latest record, and make sure that the latest record is more than 5 days the current date.
for instance, the current date is 2011-11-29
the output of the SQL statement should be
.--------------------------------.
| userid| val | Create_Date |
.--------------------------------.
| U3 | foo | 2011-11-04 |
| U1 | foo | 2011-11-18 |
.________________________________.
my current SQL is able to get the latest records but unable to compare to the latest date.
please help me debug. thanks.
SELECT * FROM user_table t1
WHERE DATEDIFF(NOW(), (SELECT MAX(create_date)
FROM user_table t2
WHERE t2.userid = t1.userid)) > 5
thanks in advance.
You could use a subquery to filter out older records per user. Then you can use a where condition to demand that the latest record is more than five days old.
select *
from UserTable u
join (
select userid
, max(Create_Date) as LatestCreateDate
from YourTable
group by
userid
) filter
on filter.userid = u.userid
and filter.LatestCreateDate = u.Create_Date
where datediff(now(), u.Create_Date) > 5
Try:
select user_id, val, min(Create_date)
from table
group by user_id
having datediff(now(), min(Create_date)) > 5

Searching for availability with MySQL (and PHP)?

I have two MySQL (MyIsAm) tables that represent letting units and bookings:
LettingUnits (ID, Name, etc...)
LettingUnitBookings (ID, F_LU_ID, Start, End)
Where F_LU_ID is a foreign key to the unit.
What is the best way to search for units that are available during a certain time frame? The search is passed a Start, End and Duration.
Start = Earliest start of the booking
End = Latest end of the booking
Duration = Duration of the booking
I'd be interested to know if it's even possible to do this in MySQL, however if not then the best way to do it in PHP.
Example
In answer to the answers below I feel an example will help explain the problem.
A LettingUnit:
(123, "Foo Cottage")
Some LettingUnitBookings:
(400, 123, 01/01/09, 05/01/09) - a 5 day booking
(401, 123, 10/01/09, 20/01/09) - a 10 day booking
(402, 123, 25/01/09, 30/01/09) - a 5 day booking
If we search for:
Start = 01/01/09
End = 01/02/09
Duration = 5 (days)
Then we want the unit to show up. Because there is availability for a 5 day booking within the search range.
If the duration is 10 then the unit won't show up as there are no 10 consecutive unbooked days within the search range.
Here's a solution that seems to work:
SELECT t.*, DATEDIFF(t.LatestAvailable, t.EarliestAvailable) AS LengthAvailable
FROM
(SELECT u.*,
COALESCE(b1.End, #StartOfWindow) AS EarliestAvailable,
COALESCE(b2.Start, #EndOfWindow) AS LatestAvailable
FROM LettingUnits u
LEFT OUTER JOIN LettingUnitBookings b1
ON (u.ID = b1.F_LU_ID AND b1.End BETWEEN #StartOfWindow AND #EndOfWindow)
LEFT OUTER JOIN LettingUnitBookings b2
ON (u.ID = b2.F_LU_ID AND b2.Start BETWEEN #StartOfWindow AND #EndOfWindow
AND b2.Start >= b1.End) -- edit: new term
) AS t
LEFT OUTER JOIN LettingUnitBookings x
ON (t.ID = x.F_LU_ID AND x.Start < t.LatestAvailable AND x.End > t.EarliestAvailable)
WHERE x.ID IS NULL AND DATEDIFF(t.LatestAvailable, t.EarliestAvailable) >= #WindowSize;
The output is:
+-----+-------------+-------------------+-----------------+-----------------+
| ID | Name | EarliestAvailable | LatestAvailable | LengthAvailable |
+-----+-------------+-------------------+-----------------+-----------------+
| 123 | Foo Cottage | 2009-01-05 | 2009-01-10 | 5 |
| 123 | Foo Cottage | 2009-01-20 | 2009-01-25 | 5 |
| 456 | Bar Cottage | 2009-01-20 | 2009-01-31 | 11 |
+-----+-------------+-------------------+-----------------+-----------------+
Analyzing this with EXPLAIN shows that it employs indexes pretty well:
+----+-------------+------------+--------+---------------+---------+---------+-------+------+-------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+------------+--------+---------------+---------+---------+-------+------+-------------------------+
| 1 | PRIMARY | <derived2> | ALL | NULL | NULL | NULL | NULL | 9 | Using where |
| 1 | PRIMARY | x | ref | F_LU_ID | F_LU_ID | 8 | t.ID | 2 | Using where; Not exists |
| 2 | DERIVED | u | system | NULL | NULL | NULL | NULL | 1 | |
| 2 | DERIVED | b1 | ref | F_LU_ID | F_LU_ID | 8 | const | 0 | |
| 2 | DERIVED | b2 | ref | F_LU_ID | F_LU_ID | 8 | const | 0 | |
+----+-------------+------------+--------+---------------+---------+---------+-------+------+-------------------------+
Compare with the EXPLAIN report for the solution given by #martin clayton:
+----+--------------+---------------------+--------+---------------+---------+---------+------+------+---------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+--------------+---------------------+--------+---------------+---------+---------+------+------+---------------------------------+
| 1 | PRIMARY | lu | system | PRIMARY,ID | NULL | NULL | NULL | 1 | |
| 1 | PRIMARY | <derived2> | ALL | NULL | NULL | NULL | NULL | 4 | Using where |
| 2 | DERIVED | <derived3> | ALL | NULL | NULL | NULL | NULL | 4 | Using temporary; Using filesort |
| 2 | DERIVED | <derived5> | ALL | NULL | NULL | NULL | NULL | 4 | Using where; Using join buffer |
| 5 | DERIVED | LettingUnitBookings | ALL | NULL | NULL | NULL | NULL | 3 | |
| 6 | UNION | LettingUnitBookings | index | NULL | F_LU_ID | 8 | NULL | 3 | Using index |
| NULL | UNION RESULT | <union5,6> | ALL | NULL | NULL | NULL | NULL | NULL | |
| 3 | DERIVED | LettingUnitBookings | ALL | NULL | NULL | NULL | NULL | 3 | |
| 4 | UNION | LettingUnitBookings | index | NULL | F_LU_ID | 8 | NULL | 3 | Using index |
| NULL | UNION RESULT | <union3,4> | ALL | NULL | NULL | NULL | NULL | NULL | |
+----+--------------+---------------------+--------+---------------+---------+---------+------+------+---------------------------------+
In general, you want to avoid optimization plans that force Using filesort or Using temporary because these are performance killers. A query using GROUP BY is almost certain to cause this kind of optimization, at least in MySQL.
It's not pretty.
Join LettingUnitBookings to itself
Find the start and end of gaps between bookings for each F_LU_ID
Get the size of the gaps - the available 'slots'
Consider the case where there are no existing bookings that 'bracket' a suitable slot, add in outlier dates for this
Join that projection to the LettingUnits table and apply WHERE criteria (start, end, duration)
I've neglected to include BookingUnits that have no bookings at all.
Ends up looking like this:
SELECT #StartOfWindow := '2009-01-01',
#EndOfWindow := '2009-02-01',
#WindowSize := 5
;
SELECT
lu.Name,
Slots.*
FROM (
SELECT
lub1.F_LU_ID,
DATE_ADD( MAX( lub2.date_time ), INTERVAL 1 DAY ) AS StartOfSlot,
DATE_SUB( lub1.date_time, INTERVAL 1 DAY ) AS EndOfSlot,
DATEDIFF( lub1.date_time, MAX( lub2.date_time ) ) - 1 AS AvailableDays
FROM
( SELECT F_LU_ID, Start AS date_time FROM LettingUnitBookings
UNION
SELECT F_LU_ID, CAST( '9999-12-31' AS DATE ) FROM LettingUnitBookings
) AS lub1,
( SELECT F_LU_ID, End AS date_time FROM LettingUnitBookings
UNION
SELECT F_LU_ID, CAST( '1000-01-01' AS DATE ) FROM LettingUnitBookings
) AS lub2
WHERE
lub2.date_time <= lub1.date_time
AND lub2.F_LU_ID = lub1.F_LU_ID
GROUP BY
lub1.F_LU_ID,
lub1.date_time
) Slots
JOIN LettingUnits lu
ON lu.ID = Slots.F_LU_ID
WHERE
Slots.AvailableDays >= #WindowSize
AND (
( DATEDIFF( Slots.EndOfSlot, #EndOfWindow ) >= #WindowSize
AND DATEDIFF( #StartOfWindow, Slots.StartOfSlot ) >= #WindowSize
)
OR
( DATEDIFF( #EndOfWindow, Slots.StartOfSlot ) >= #WindowSize
AND DATEDIFF( Slots.EndOfSlot, #StartOfWindow ) >= #WindowSize
)
)
Gives
Name F_LU_ID StartOfSlot EndOfSlot AvailableDays
Foo Cottage 123 2009-01-06 2009-01-09 5
Foo Cottage 123 2009-01-21 2009-01-24 5
Hopefully that can be adapted to suit your needs.
Alternatively, if a booking can start on the same day that the previous booking ends, you can adapt slightly...
SELECT
lu.Name,
Slots.*
FROM (
SELECT
lub1.F_LU_ID,
MAX( lub2.date_time ) AS StartOfSlot,
lub1.date_time AS EndOfSlot,
DATEDIFF( lub1.date_time, MAX( lub2.date_time )) AS AvailableDays
FROM
( SELECT F_LU_ID, Start AS date_time FROM LettingUnitBookings
UNION
SELECT F_LU_ID, CAST( '9999-12-31' AS DATE ) FROM LettingUnitBookings
) AS lub1,
( SELECT F_LU_ID, End AS date_time FROM LettingUnitBookings
UNION
SELECT F_LU_ID, CAST( '1000-01-01' AS DATE ) FROM LettingUnitBookings
) AS lub2
WHERE
lub2.date_time <= lub1.date_time
AND lub2.F_LU_ID = lub1.F_LU_ID
GROUP BY
lub1.F_LU_ID,
lub1.date_time
) Slots
JOIN LettingUnits lu
ON lu.ID = Slots.F_LU_ID
WHERE
Slots.AvailableDays >= #WindowSize
AND
( DATEDIFF( Slots.EndOfSlot, #EndOfWindow ) >= #WindowSize
AND DATEDIFF( #StartOfWindow, Slots.StartOfSlot ) >= #WindowSize
)
OR
( DATEDIFF( #EndOfWindow, Slots.StartOfSlot ) >= #WindowSize
AND DATEDIFF( Slots.EndOfSlot, #StartOfWindow ) >= #WindowSize
)

Categories