add batch remove batch split batch comment selection show hidden batches hide batch highlight batch
db<>fiddle
donate feedback about
By using db<>fiddle, you agree to license everything you submit by Creative Commons CC0.
select @@version;
@@version
10.4.20-MariaDB
CREATE TABLE `unit` (
`id` int(11) NOT NULL,
`parent_unit_id` int(11) DEFAULT NULL,
`name` VARCHAR(100) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

ALTER TABLE `unit`
ADD PRIMARY KEY (`id`),
ADD KEY `IDX_C3A3B3698AF5044B` (`parent_unit_id`);
ALTER TABLE `unit`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=1;
ALTER TABLE `unit`
ADD CONSTRAINT `FK_C3A3B3698AF5044B` FOREIGN KEY (`parent_unit_id`) REFERENCES `unit` (`id`);
INSERT INTO `unit` (parent_unit_id, name) VALUES
(NULL, "Car"),
(1, "Tire 1"),
(1, "Tire 2");

CREATE TABLE `unit_calendar_entry` (
`id` int(11) NOT NULL,
`unit_id` int(11) NOT NULL,
`entry_start` datetime NOT NULL,
`entry_end` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

ALTER TABLE `unit_calendar_entry`
ADD PRIMARY KEY (`id`),
ADD KEY `IDX_7260175CA40A2C8` (`unit_id`);
ALTER TABLE `unit_calendar_entry`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=1;
ALTER TABLE `unit_calendar_entry`
SELECT * FROM unit;

id parent_unit_id name
1 null Car
2 1 Tire 1
3 1 Tire 2
SELECT * FROM unit_calendar_entry;
id unit_id entry_start entry_end
1 1 2021-07-31 00:00:00 2021-08-03 00:00:00
2 2 2021-08-03 00:00:00 2021-08-07 00:00:00
3 3 2021-08-04 00:00:00 2021-08-06 00:00:00
4 3 2021-08-09 00:00:00 2021-08-10 00:00:00
-- all times from table "unit_calendar_entry"
select unit_id, entry_start, entry_end
from unit_calendar_entry
union -- plus those for the tires when the car is booked
select p.id, u.entry_start, u.entry_end
from unit p
join unit q
on q.id = p.parent_unit_id
join unit_calendar_entry u
on q.id = u.unit_id
union -- plus those for the car when one of the tires is booked
select p.parent_unit_id as unit_id, u.entry_start, u.entry_end
from unit p
join unit_calendar_entry u
on p.id = u.unit_id
and p.parent_unit_id is not null
order by unit_id, entry_start;
unit_id entry_start entry_end
1 2021-07-31 00:00:00 2021-08-03 00:00:00
1 2021-08-03 00:00:00 2021-08-07 00:00:00
1 2021-08-04 00:00:00 2021-08-06 00:00:00
1 2021-08-09 00:00:00 2021-08-10 00:00:00
2 2021-07-31 00:00:00 2021-08-03 00:00:00
2 2021-08-03 00:00:00 2021-08-07 00:00:00
3 2021-07-31 00:00:00 2021-08-03 00:00:00
3 2021-08-04 00:00:00 2021-08-06 00:00:00
3 2021-08-09 00:00:00 2021-08-10 00:00:00
with subtab as (
select unit_id, entry_start, entry_end
from unit_calendar_entry
union
select p.id, u.entry_start, u.entry_end
from unit p
join unit q
on q.id = p.parent_unit_id
join unit_calendar_entry u
on q.id = u.unit_id
union
select p.parent_unit_id as unit_id, u.entry_start, u.entry_end
from unit p
join unit_calendar_entry u
on p.id = u.unit_id
and p.parent_unit_id is not null
order by unit_id, entry_start
)
select c.*, sum(case when prev_end < entry_start then 1 else 0 end) over (order by unit_id, entry_start) as grouping
from (
select subtab.*, max(entry_end) over (partition by unit_id order by entry_start rows between unbounded preceding and 1 preceding) as prev_end from subtab
) c;

unit_id entry_start entry_end prev_end grouping
1 2021-07-31 00:00:00 2021-08-03 00:00:00 null 0
1 2021-08-03 00:00:00 2021-08-07 00:00:00 2021-08-03 00:00:00 0
1 2021-08-04 00:00:00 2021-08-06 00:00:00 2021-08-07 00:00:00 0
1 2021-08-09 00:00:00 2021-08-10 00:00:00 2021-08-07 00:00:00 1
2 2021-07-31 00:00:00 2021-08-03 00:00:00 null 1
2 2021-08-03 00:00:00 2021-08-07 00:00:00 2021-08-03 00:00:00 1
3 2021-07-31 00:00:00 2021-08-03 00:00:00 null 1
3 2021-08-04 00:00:00 2021-08-06 00:00:00 2021-08-03 00:00:00 2
3 2021-08-09 00:00:00 2021-08-10 00:00:00 2021-08-06 00:00:00 3
-- getting all continuous timespans where units are booked (but we actually want the opposite)
select unit_id, grouping, min(entry_start), max(entry_end)
from (
select c.*, sum(case when prev_end < entry_start then 1 else 0 end) over (order by unit_id, entry_start) as grouping
from (
select d.*, max(entry_end) over (partition by unit_id order by entry_start rows between unbounded preceding and 1 preceding) as prev_end
from (
select unit_id, entry_start, entry_end
from unit_calendar_entry
union
select p.id, u.entry_start, u.entry_end
from unit p
join unit q
on q.id = p.parent_unit_id
join unit_calendar_entry u
on q.id = u.unit_id
union
select p.parent_unit_id as unit_id, u.entry_start, u.entry_end
from unit p
join unit_calendar_entry u
on p.id = u.unit_id
and p.parent_unit_id is not null
) d
) c
) b
group by unit_id, grouping;
unit_id grouping min(entry_start) max(entry_end)
1 0 2021-07-31 00:00:00 2021-08-07 00:00:00
1 1 2021-08-09 00:00:00 2021-08-10 00:00:00
2 1 2021-07-31 00:00:00 2021-08-07 00:00:00
3 1 2021-07-31 00:00:00 2021-08-03 00:00:00
3 2 2021-08-04 00:00:00 2021-08-06 00:00:00
3 3 2021-08-09 00:00:00 2021-08-10 00:00:00
-- getting all continuous timespans where units are available
with tab as (
select c.*, sum(case when prev_end < entry_start then 1 else 0 end) over (order by unit_id, entry_start) as grouping
from (
select d.*, max(entry_end) over (partition by unit_id order by entry_start rows between unbounded preceding and 1 preceding) as prev_end
from (
select unit_id, entry_start, entry_end
from unit_calendar_entry
union
select p.id, u.entry_start, u.entry_end
from unit p
join unit q
on q.id = p.parent_unit_id
join unit_calendar_entry u
on q.id = u.unit_id
union
select p.parent_unit_id as unit_id, u.entry_start, u.entry_end
from unit p
join unit_calendar_entry u
on p.id = u.unit_id
and p.parent_unit_id is not null
) d
) c
)
select distinct unit_id, NULLIF((min(ifnull(prev_end,'1000-01-01')) over (partition by unit_id, grouping)),'1000-01-01') as available_from, min(entry_start) over (partition by unit_id, grouping) as available_til
from tab
union
select distinct unit_id, max(entry_end) over (partition by unit_id) as available_from, null as available_til
from tab
order by unit_id, available_from
unit_id available_from available_til
1 null 2021-07-31 00:00:00
1 2021-08-07 00:00:00 2021-08-09 00:00:00
1 2021-08-10 00:00:00 null
2 null 2021-07-31 00:00:00
2 2021-08-07 00:00:00 null
3 null 2021-07-31 00:00:00
3 2021-08-03 00:00:00 2021-08-04 00:00:00
3 2021-08-06 00:00:00 2021-08-09 00:00:00
3 2021-08-10 00:00:00 null