我有两张桌子:
Room
Contract
(租赁合同)Contract
n-1 Room
我收到用户输入startDate
& endDate
。在该间隔的基础上,我查询与Contracts
相关的Rooms
。相关方式:
Room
Room
开始按给定时间间隔租借Room
我的查询是:
SELECT Room.id,
RentContract.activeon,
RentContract.expireson
FROM RentContract
INNER JOIN Room ON RentContract.roomid = Room.id
WHERE (RentContract.new_activeon >= @startDate
OR RentContract.new_activeon IS NULL)
AND (RentContract.new_expireson <= @endDate
OR RentContract.new_expireson IS NULL)
现在,要求是我另外显示这些房间的非租用间隔。由于我在我的数据库中没有这个,我想我需要插入某种动态行,我将在同一个列表中显示。另外,我将在结果(状态)中显示一个额外的列,它将显示&#34; Occupied&#34;对于实际合同和&#34;空的&#34;对于&#34;动态&#34;行。
因此,作为示例,用户输入为:startDate = 01.05.2016,endDate = 01.07.2016
我现在的结果是:
我想要的结果是:
所以我实际上需要&#34;填充&#34;整个输入间隔使用db记录或动态记录
@Rhumborl 你的解决方案几乎为我解决了!剩下一个小细节:
我得到一些结果,合同在一个月的第二天开始,所以我想我需要一个&#34;空&#34;当天入境。例如。 2016年1月1日至2016年1月1日空。这是我的初始结果的一些子集以及我从您获得的解决方案的结果(我将该特定方案标记为黄色):
初始查询:
Rhumborl的查询:
您的查询是否有一点调整来解决这个问题?
答案 0 :(得分:4)
你需要几个CTE来解决这个问题。基本的想法是获得您已经拥有的所有占用时间,然后使用该结果集中的日期来查找每个房间的间隙。
首先是完整的查询:
declare @startDate smalldatetime = '20160501',
@endDate smalldatetime = '20160701'
; with occupieds as (
SELECT Room.id,
RentContract.activeon,
RentContract.expireson,
'Occupied' as [State],
-- get ordering of contract for each rooms
row_number() over (partition by roomid order by activeon) SortOrder
FROM RentContract
INNER JOIN Room ON RentContract.roomid = Room.id
WHERE (RentContract.activeon >= @startDate
OR RentContract.activeon IS NULL)
AND (RentContract.expireson <= @endDate
OR RentContract.expireson IS NULL)
),
empties as (
select o1.id, o1.expireson + 1 as activeon, o2.activeon - 1 as expireson, 'Empty' as [State] from occupieds o1
inner join occupieds o2 on o1.id = o2.id and o1.SortOrder = o2.SortOrder - 1
),
extremes as (
select id, @startDate as activeon, min(activeon) - 1 as expireson, 'Empty' as [State] from occupieds group by id
having min(activeon) > @startDate
union all
select id, max(expireson) + 1 as activeon, @endDate as expireson, 'Empty' as [State] from occupieds group by id
having max(expireson) < @enddate
)
select id, activeon, expireson, [State] from occupieds
union all
select id, activeon, expireson, [State] from empties
union all
select id, activeon, expireson, [State] from extremes
order by id, activeon
让我们分解
这几乎与您当前的查询相同。唯一的补充是我们将使用row_number()
来订购每个房间的合同。我们将在下一步中使用它。
SELECT Room.id,
RentContract.activeon,
RentContract.expireson,
'Occupied' as [State],
-- get ordering of contract for each rooms
row_number() over (partition by roomid order by activeon) SortOrder
FROM RentContract
INNER JOIN Room ON RentContract.roomid = Room.id
WHERE (RentContract.activeon >= @startDate
OR RentContract.activeon IS NULL)
AND (RentContract.expireson <= @endDate
OR RentContract.expireson IS NULL)
这给出了以下
id | activeon | expireson | State | SortOrder
1 | 2016-05-01 00:00:00 | 2016-05-31 00:00:00 | Occupied | 1
1 | 2016-06-15 00:00:00 | 2016-06-25 00:00:00 | Occupied | 2
2 | 2016-05-01 00:00:00 | 2016-07-01 00:00:00 | Occupied | 1
现在我们有了每个房间的合同及其出现的顺序,我们可以在一个合同和下一个合同之间使用自我加入来计算出它是空的日期范围。因此,选择该行,然后在与之前的roomid
相同的SortOrder
上加入自身。在上表中,第1行将加入第2行。这为我们提供了一个开始日期(第1行的expireson)和结束日期(第2行的活动日期)。我们只需加/减一天,这样它们就不会重叠:
select o1.id, o1.expireson + 1 as activeon, o2.activeon - 1 as expireson, 'Empty' as [State] from occupieds o1
inner join occupieds o2 on o1.id = o2.id and o1.SortOrder = o2.SortOrder - 1
最后一步是如果房间在范围的开始处是空的 - 这会赢得;因为没有&#34;之前的&#34;排第一份合同。
为此,我们只需找到最早的占用日期,并将其作为空期的到期日期。我们还检查这是在startDate之后,所以我们不会在同一天开始和结束一个实际占用的rom的条目。
同样适用于结束日期 - 找到最大到期日期并使用endDate作为结束:
select id, @startDate as activeon, min(activeon) - 1 as expireson, 'Empty' as [State] from occupieds group by id
having min(activeon) > @startDate
union all
select id, max(expireson) + 1 as activeon, @endDate as expireson, 'Empty' as [State] from occupieds group by id
having max(expireson) < @enddate
我们现在拥有了所有需要的条目,因此我们将三个结果集合并在一起:
select id, activeon, expireson, [State] from occupieds
union all
select id, activeon, expireson, [State] from empties
union all
select id, activeon, expireson, [State] from extremes
order by id, activeon
对于空心和极端的CTE,您可以在最终的联合中将它们作为子查询,但为了清晰起见我将它们分开
; with occupieds as (
SELECT Room.id,
RentContract.activeon,
RentContract.expireson,
'Occupied' as [State],
row_number() over (partition by roomid order by activeon) SortOrder
FROM RentContract
INNER JOIN Room ON RentContract.roomid = Room.id
WHERE (RentContract.activeon >= @startDate
OR RentContract.activeon IS NULL)
AND (RentContract.expireson <= @endDate
OR RentContract.expireson IS NULL)
)
select id, activeon, expireson, [State] from occupieds
union all
select o1.id, o1.expireson + 1 as activeon, o2.activeon - 1 as expireson, 'Empty' as [State] from occupieds o1
inner join occupieds o2 on o1.id = o2.id and o1.SortOrder = o2.SortOrder - 1
union all
select id, @startDate as activeon, min(activeon) - 1 as expireson, 'Empty' as [State] from occupieds group by id
having min(activeon) > @startDate
union all
select id, max(expireson) + 1 as activeon, @endDate as expireson, 'Empty' as [State] from occupieds group by id
having max(expireson) < @enddate
order by id, activeon
答案 1 :(得分:3)
您可以在cte
的帮助下完成此操作。由于您还没有提供更多数据 - 可以使用发布的内容:
DECLARE @startdate date = '2016-05-01',
@enddate date = '2016-07-01'
;WITH cte AS (
SELECT r.id,
rc.activeon,
rc.expireson,
'Occupited' as [state]
FROM RentContract rc
INNER JOIN Room r
ON rc.roomid = r.id
WHERE (rc.new_activeon >= @startDate OR rc.new_activeon IS NULL)
AND (rc.new_expireson <= @endDate OR rc.new_expireson IS NULL)
)
SELECT *
FROM cte
UNION ALL
SELECT id,
activeon,
CASE WHEN expireson < activeon THEN @enddate ELSE expireson END as expireson,
'Empty' as [state]
FROM (
SELECT id, CAST(DATEADD(day,1,expireson) as date) as activeon, ROW_NUMBER() OVER (ORDER BY expireson ASC) as rn
FROM cte
) as s
INNER JOIN (
SELECT CAST(DATEADD(day,-1,activeon)as date) as expireson, ROW_NUMBER() OVER (ORDER BY expireson ASC) as rn
FROM cte
) as e
ON e.rn=s.rn+1
ORDER BY id, activeon
输出:
id activeon expireson state
1 2016-05-01 2016-05-31 Occupited
1 2016-06-01 2016-06-14 Empty
1 2016-06-15 2016-06-25 Occupited
1 2016-06-26 2016-07-01 Empty
2 2016-05-01 2016-07-01 Occupited