酒店使用 0103031206100002 等代码提供有关其客房的信息。代码说明:前 2 个符号 - 成人数量 (01),接下来 2 个符号 - 儿童数量 (03)。剩下的 12 个符号我们可以分成 3 个部分,每个部分有 4 个符号:0312、0610、0002。这意味着房间可容纳一名 3 至 12 岁的儿童,再加上一名 6-10 岁的儿童以及 0 -2岁一岁。 我们存储在 Room 表中的信息还包含其他字段(代码、人数、价格等),但我将仅显示所需的字段:
身份证 | 代码 | 价格 |
---|---|---|
1 | 0103031206100002 | 125 |
2 | 0103000600060006 | 120 |
3 | 010200020003 | 100 |
4 | 01010002 | 38 |
此表中约有 10k 行。
第一个问题:我们可以执行查询从 Code 字段中获取所需的子级数量吗?我没有找到这样的方法,所以决定创建另一个表,ChildAccommodation:
身份证 | 代码 | ChMinAge | ChMaxAge | 范围数 |
---|---|---|---|---|
1 | 0103031206100002 | 3 | 12 | 1 |
2 | 0103031206100002 | 6 | 10 | 2 |
3 | 0103031206100002 | 0 | 2 | 3 |
4 | 0103000600060006 | 0 | 6 | 1 |
5 | 0103000600060006 | 0 | 6 | 2 |
6 | 0103000600060006 | 0 | 6 | 3 |
7 | 01010002 | 0 | 2 | 1 |
8 | 01010003 | 0 | 3 | 1 |
9 | 01010004 | 0 | 4 | 1 |
10 | 010200020003 | 0 | 2 | 1 |
11 | 010200020003 | 0 | 3 | 2 |
... |
在这里,我们根据房间表中的代码存储有关范围的更多详细信息。
为了寻找房间,我们会收到儿童的人数和年龄。例如:3个年龄分别为2岁、5岁和7岁的孩子。需要注意的是,可以有 1 至 5 名儿童。因此,我们需要显示 Room 表中的一行,其中包含 Price:可以容纳所有孩子的房间。
我们以代码0103031206100002为例看一下需求: 如果我们正在寻找 2 岁的儿童 <=2 years, then the result should be empty, since there is only one range from 0 to 2 years. For a 7 year old child, both ranges 3-12 and 6-10 are suitable. But the catch here is that if we return the range 3-12 for a 7-years-old, then the 5-years-old will be left out: neither 0-2 nor 6-10 will not suit him anymore. We should get:
在这种情况下,所有的孩子都会被容纳。
在这里您可以找到包含数据的表格示例。
第二个主要问题:什么脚本/请求可以帮助了解我们是否有合适的空间将所有孩子安置在不同的范围内?
我尝试了很多选项,如递归 CTE、关系除法等,但仍然找不到解决方案。
例如,这是众多脚本之一。我们寻找2岁、6岁和6岁的孩子:
declare @childs table (Id int primary key, Age int);
INSERT INTO @childs (Id, Age)
VALUES
(1, 2),
(2, 6),
(3, 6);
select *
from @childs as c
cross apply (select count(*) from @childs) as cc(cnt)
inner join ChildAccommodation as ca
on c.Age between ca.ChMinAge and ca.ChMaxAge
AND SUBSTRING(ca.Code,3, 2) = cc.cnt
结果,我得到下表:
身份证 | 年龄 | cnt | 身份证 | 代码 | ChMinAge | ChMaxAge | 范围数 |
---|---|---|---|---|---|---|---|
2 | 6 | 3 | 1 | 0103031206100002 | 3 | 12 | 1 |
3 | 6 | 3 | 1 | 0103031206100002 | 3 | 12 | 1 |
2 | 6 | 3 | 2 | 0103031206100002 | 6 | 10 | 2 |
3 | 6 | 3 | 2 | 0103031206100002 | 6 | 10 | 2 |
1 | 2 | 3 | 3 | 0103031206100002 | 0 | 2 | 3 |
并且不能正好取3个范围,因为3-12和6-10范围都适合每个6岁的孩子。由于重复,按代码分组和年龄/ID/范围计数将给出错误的结果。
这样的事情怎么样?该代码并未针对美观或简洁进行优化,但我认为它有效。
这仅解决您的第一个问题。也就是说,这会解压编码字符串并为您提供基本的关系数据以进行处理。另一个问题可能应该是它自己的问题,但是如果有人想获取此代码并使用它来完成另一个问题,无论如何,就去吧。
通过递归 CTE 运行基线数据,该递归 CTE 会删除代码的 4 个字符段,直到不再有为止。
然后,将生成的 4 字符代码拆分为年龄范围的左右两个字符
-- Populate example data
drop table if exists #Room
create table #Room
(
RoomId int not null,
Code varchar(50) not null,
Price float not null
)
insert into #Room
values
(1,'0103031206100002', 125),
(2,'0103000600060006', 120),
(3,'010200020003', 100),
(4,'01010002', 38)
-- Split out the known parts, and have the Remainder field be everything after those first two pieces are chomped off
drop table if exists #Base
select
RoomId,
Code,
Price,
NumAdults = substring(Code, 1, 2),
NumChildren = substring(Code, 3, 2),
Remainder = substring(Code, 5, len(Code))
into #Base
from #Room
-- Recursively chomp out 4 character slices of Remainder util there are none left
;with cte as
(
select
RoomId,
Code,
Price,
NumAdults,
NumChildren,
SubCode = convert(varchar(50), iif(len(Remainder) >= 4, left(Remainder, 4), null)),
Remainder = convert(varchar(50), stuff(Remainder, 1, 4, '')),
RangeNum = 1
from #Base
union all
select
b.RoomId,
b.Code,
b.Price,
b.NumAdults,
b.NumChildren,
SubCode = convert(varchar(50), iif(len(c.Remainder) >= 4, left(c.Remainder, 4), null)),
Remainder = convert(varchar(50), stuff(c.Remainder, 1, 4, '')),
RangeNum = c.RangeNum + 1
from #Base b
inner join cte c
on b.RoomId = c.RoomId
where len(c.Remainder) >= 4
)
-- Results and beautification
select
RoomId,
Code,
Price,
NumAdults = try_convert(int, NumAdults),
NumChildren = try_convert(int, NumChildren),
SubCode,
ChildMinAge = try_convert(int, left(SubCode, 2)),
ChildMaxAge = try_convert(int, right(SubCode, 2)),
RangeNum
from cte
order by RoomId, RangeNum