1

我拥有一些我经常出租的东西。它由几个部分组成。它可以部分租用,也可以整体租用。如果一个部分被出租,你将无法将它作为一个整体出租。

一个例子可以是我租了一辆车。这辆车有轮胎,也是出租的。您可以选择租用轮胎(«整体»)的汽车,或者只租一个轮胎。但是,如果租用一个或多个轮胎,您将无法租用汽车(“整车”)。

我把它想象成一个层次结构。

         Whole
    _______|_______
   |               |
 Part 1           Part 2

我使用了一个谷歌日历来处理“整个”的事情,并为每个包含的部分使用单独的日历。这行得通,但是很烦人,我希望能够将链接发送给感兴趣的人-他们可以在其中查看可用的内容。

所以我做了一个简单的数据库(mariadb 10.4),有两个表:

# tbl: part
| id | parent_id | name   |

该列parent_id只是引用同一个表中的另一行,这是一个数据示例。

| 1  | NULL      | Car    |
| 2  | 1         | Tire 1 |
| 3  | 1         | Tire 2 |

然后下表存储每个部分的日期booked(+示例数据)。

# tbl: booking
| id | part_id | booked_from | booked_until |
--------------------------------------------
| 1  | 1       | 2021-07-31  | 2021-08-03   |
| 2  | 2       | 2021-08-03  | 2021-08-07   |
| 3  | 3       | 2021-08-04  | 2021-08-06   |
| 4  | 3       | 2021-08-09  | 2021-08-10   |

由此我们知道汽车本身是从2021-07-31-预订的2021-08-03,但它只能从 - 预订,2021-08-06因为在此期间租用了两个轮胎(但是它们可以同时租用,因为它们不是严格相关的)。但是直到2021-08-09再次预订轮胎为止。

我正在寻找的是一个查询以获取可用日期列表。从pars-table 我能够找出哪些部分是相关的,这不是我最大的问题 - 我想,因为在查询可用性时可以使用这样的东西:

  • 车:part_id IN(1,2,3)
  • 轮胎1:part_id IN(1,2)
  • 轮胎 3:part_id IN(1,3)

我的问题是(简单地说?)我如何编排一个查询,该查询仅返回可用日期的日期,尤其是对于日期可以重叠的汽车。

例如汽车的结果

SELECT 
  `booked_until` AS `available-from`, 
  `booked_from` as `available-until`
FROM 
  booking
/** some JOIN magic? **
WHERE part_id IN(1,2,3)

例如,轮胎 1将是相同的,但与part_id IN(1,2)astire 2 (id: 3)不直接相关tire 1

两者都应分别返回:

# car
| available-from | available-until |
------------------------------------
| NULL           | 2021-07-31      |
| 2021-08-06     | 2021-08-09      |
| 2021-08-10     | NULL            |
# tire 1
| available-from | available-until |
------------------------------------
| NULL           | 2021-07-31      |
| 2021-08-07     | NULL            |

-valuesNULL只是表示之前或之前没有任何预订。例如,这个轮胎在地球存在的最后一天from now之前2021-07-31和之后都是可用的。2021-08-07

希望这是有道理的 - 并且有人能够提供帮助。

先感谢您。

4

3 回答 3

1

好的,这是我的尝试。

因此,如果我理解正确,除了表中明确给出的信息外,这些单位还隐含不可用。所以我首先明确地检索了这个:

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_calendar_entry u
   on  p.parent_unit_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只是获取表中已经存在的条目
  • 第二个添加了汽车的条目,因为如果它的任何一个轮胎被预订,它就可以被认为是预订的
  • 第三个添加了轮胎条目,因为如果汽车被预订,它们可以被视为已预订

结果:

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

基于此,为了对相邻/重叠的时间跨度进行分组,需要解决间隙和孤岛问题。您可以使用两个查询来标记属于一起的条目,就像在这个 SO answer 中一样。如果我们调用上面的查询subtab,相应的语句是

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
  • 内部查询获取每一行的前一个结尾。
  • 外部分配一个分组 id(在 each 内unit_id)标识属于连续块(又名island)的所有条目。

结果:

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

从此(我们称之为),您可以通过对andtab进行分组来获得不可用的时间跨度(参见下面的这个 SO 答案或 db<>fiddle),或者按如下方式计算空闲时间:unit_idgrouping

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
  • 第一个查询作为每个/available_from的最小值。为了从中获取值,我使用了类似于此 SO 答案的解决方法。prev_endunit_idgroupingNULLMIN()
  • 第二个查询为每个查询添加一行,unit_id最大值entry_end为 start 和NULLend

结果:

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)

将所有内容放在一个查询中:

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_calendar_entry u
                         on p.parent_unit_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

另请参阅此 db<>fiddle

于 2021-06-26T01:52:48.323 回答
0

好的,所以我不得不进一步扩展解决方案。添加了另一个表,该表为每个项目的每个条目保留特定数据。例如,它需要返回多长时间,以及准备« ting» 恢复到其原始状态所需的时间。这只是一个近似值,并不那么重要,但它应该限制任何人在特定时间之前收集它。:)

所以表格现在看起来像这样:

# tbl: property_unit (former: part)
| id | parent_id | identifier   |

# tbl: property_unit_calendar (NEW)
| id | property_unit_id | return_by | preparation_time |

# tbl: property_unit_calendar_entry (former: booking)
| id | calendar_id | entry_start | entry_end |

这不会对当前查询产生太大影响,因为列中的时间return_bypreparation_timeproperty_unit_calendar中的时间应用于property_unit_calendar_entry预订时的日期时间字段。

采用了代码并添加了这种关系。它似乎工作正常 - (再次,非常感谢你)。

更新后的db<>fiddle

现在我正在努力找出我应该如何减少结果以匹配unit我正在检查可用性的实际情况。我应该为每个选择添加tab吗?像这样

FROM tab
WHERE property_unit = 8
UNION
SELECT DISTINCT
  ...
FROM tab
WHERE property_unit = 8

我正在努力解决的另一件事是如何将结果减少到某个帧,例如在日期之间、从某个日期开始或直到某个日期。

主要问题不是如何在日期之间/从/直到日期获得结果,但如果预订在时间范围之前提前,它将NULL在第一行和最后一行给我任何一种方式 - 这应该表明它可用于“不可预测的过去/未来”(这可能不正确)。

所以.... 最好为 NULL 添加一个额外的查询来检查帧之前或之后是否有预订?

希望我解释得足够好。这么多依赖要考虑!

谢谢!

于 2021-06-26T20:29:42.627 回答
0

非常感谢你- 这对你非常有帮助。

由于这个查询对我来说非常复杂,无法完全理解,我想问一下

  1. 如何仅选择结果unit_id = 1?我尝试在外部查询上使用 where 语句,但没有任何效果。在指定时返回所有日历相交的单元 1 的结果就足够了WHERE unit_id IN(1,2,3)(这将是 db<>fiddle 中为单元 1 返回的行)。
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)
  1. 如何限制日期范围的结果,比如说在 date 之间2021-08-01 - 2021-08-30

再次,谢谢你!

编辑:如果我将“螺栓”(安装轮胎)添加到等式中,给层次结构另一个层次。这个解决方案还能用吗?

                                    Whole
                               _______|_______
                              |               |
                            Part 1           Part 2
                        ______|____          ...
                        |          |
                     Bolt 1 .... Bolt N
于 2021-06-26T14:55:00.673 回答