确定资源在 n 个日期期间的可用性

Determine availability for resources on n date periods

给出下表:

资源

| id | name |
| 1  | John |
| 2  | Anna |

预订

| id | start               | end                | resource_id |
| 1  | 2020-05-29 08:00:00 |2020-05-29 12:00:00 | 1           |

我想查找 n 日期期间的所有可用资源。我可以使用子查询构建动态查询,如下所示:

SELECT name, (
    SELECT COUNT(id) FROM bookings WHERE
        resources.id = bookings.resource_id
        AND '2020-05-29 08:00:00' < `end`
        AND '2020-05-29 09:00:00' > `start` 
) AS 'dp1',
(SELECT COUNT(id) FROM bookings WHERE
        resources.id = bookings.resource_id
        AND '2020-05-29 09:00:00' < `end`
        AND '2020-05-29 10:00:00' > `start` 
) AS 'dp2'
FROM resources

这给了我以下结果:

| name | dp1 | dp2 |
| John | 1   | 1   |
| Anna | 0   | 0   |

这不能很好地扩展。如果我想确定整个星期的可用性,从 08 到 17 将是 70 个子查询与当前解决方案。

如何更优雅、更高效地确定更大 n 给定日期期间的资源可用性?

我建议将句点放在行中而不是列中。这为您提供了一个纯 SQL 解决方案的机会——否则您将需要动态 SQL.

如果你是运行ning MySQL 8.0,你可以使用递归查询来枚举句点,cross join它与resources table获得所有组合,然后将 table 与 left join 相结合。

这是一个查询,可以在给定的一周内为您提供所需的结果。您可以根据需要调整 cte 中的边界。

with period as (
    select '2020-05-22 08:00:00' ts1 
    union all
    select case when hour(ts1) = 16 
        then date_format(ts, '%Y-%m-%d 09:00:00') + interval 1 day
        else ts + interval 1 hour
    end
    from cte    
    where ts < '2020-05-29 17:00:00' 
)
select 
    p.ts period,
    r.name,
    count(b.id) is_reserved
from periods p
cross join resources r 
left join bookings b 
    on  b.start <= p.ts + interval 1 hour 
    and b.end   >= p.ts 
    and b.resource_id = r.id
group by p.ts, r.name

如果你要经常去运行这个查询,你应该考虑把cte的结果具体化在一个table中(也就是日历table的概念),即然后您可以在查询中使用。

一种可能的方法是创建一个临时的 table,您可以在其中放置您感兴趣的时间段 periods_table:

| period_id | period_start        | period_end         | 
|        1  | 2020-05-29 08:00:00 |2020-05-29 09:00:00 | 
|        2  | 2020-05-29 09:00:00 |2020-05-29 10:00:00 | 
|        3  | 2020-05-29 10:00:00 |2020-05-29 11:00:00 | 
|        4  | 2020-05-29 11:00:00 |2020-05-29 12:00:00 | 
    ... etc

然后您可以将时间段与您的预订和资源值进行匹配:

SELECT name, period_start, period_end, count(period_id) FROM (
SELECT bookings.start, bookings.end JOIN resources ON 
   resources.id = bookings.resource_id  
) AS `subquery` JOIN periods_table WHERE
        period_end > `start`
        AND 'period_start' < `end` 
)