T-SQL将动态行添加到结果集

时间:2016-06-02 08:52:42

标签: sql sql-server tsql

我有两张桌子:

  • 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

我现在的结果是:

enter image description here

我想要的结果是:

enter image description here

所以我实际上需要&#34;填充&#34;整个输入间隔使用db记录或动态记录

@Rhumborl 你的解决方案几乎为我解决了!剩下一个小细节:

我得到一些结果,合同在一个月的第二天开始,所以我想我需要一个&#34;空&#34;当天入境。例如。 2016年1月1日至2016年1月1日空。这是我的初始结果的一些子集以及我从您获得的解决方案的结果(我将该特定方案标记为黄色):

初始查询:

enter image description here

Rhumborl的查询:

enter image description here

您的查询是否有一点调整来解决这个问题?

2 个答案:

答案 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

让我们分解

第1步 - 占用房间

这几乎与您当前的查询相同。唯一的补充是我们将使用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

第2步 - 合同之间的空房间

现在我们有了每个房间的合同及其出现的顺序,我们可以在一个合同和下一个合同之间使用自我加入来计算出它是空的日期范围。因此,选择该行,然后在与之前的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

第3步 - 管理范围开始和结束时的差距

最后一步是如果房间在范围的开始处是空的 - 这会赢得;因为没有&#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

第4步 - 全部放在一起

我们现在拥有了所有需要的条目,因此我们将三个结果集合并在一起:

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