【问题标题】:Discard existing dates that are included in the result, SQL Server丢弃结果中包含的现有日期,SQL Server
【发布时间】:2017-09-14 20:35:51
【问题描述】:

在我的数据库中,我有一个Reservation 表,它有三列Initial DayLast DayHouse Id

我要统计总天数,省略那些重复的,例如:

+-------------+------------+------------+
|             | Results    |            |
+-------------+------------+------------+
|   House Id  | InitialDay |  LastDay   |
+-------------+------------+------------+
|  1          | 2017-09-18 | 2017-09-20 |
|  1          | 2017-09-18 | 2017-09-22 |
| 19          | 2017-09-18 | 2017-09-22 |
| 20          | 2017-09-18 | 2017-09-22 |
+-------------+------------+------------+

如果您注意到编号为 1 的 House Id 有两行,每行都有日期,但第一行在第二行的日期间隔内。总天数应为 5,因为不应将第一个天数计算在内,因为这些天数已存在于第二个中。

发生这种情况的原因是每个房子都有两个房间,不同的人可以在同一日期住在那个房子里。

我的问题是:我怎样才能忽略这些情况,只计算房子被占用的真实天数?

【问题讨论】:

  • 我认为您也可以拥有像 1, 2017-08-20, 2017-08-22 这样的记录,您希望将这些天数添加到一号房屋的总数中吗?
  • 那么输出会是什么? House 1 5 天还是...?还有其他列吗?

标签: sql sql-server date


【解决方案1】:

在您使用 SQL Server 2012 或更高版本时,您可以使用LAG() 获取上一个最终日期并调整初始日期:

with ReservationAdjusted as (
select *,
  lag(LastDay) over(partition by HouseID order by InitialDay, LastDay) as PreviousLast
from Reservation
)
select HouseId,
  sum(case when PreviousLast>LastDay then 0 -- fully contained in the previous reservation
    when PreviousLast>=InitialDay then datediff(day,PreviousLast,LastDay) -- overlap
    else datediff(day,InitialDay,LastDay)+1 -- no overlap
    end) as Days
from ReservationAdjusted
group by HouseId

案例是:

  • 预订完全包含在上一次预订中:我们只需要比较结束日期​​,因为上一行是通过InitialDay, LastDay排序获得的,所以上一次开始日期总是小于或等于当前开始日期。李>
  • 当前预订与上一个重叠:在这种情况下我们调整开始并且不加1(初始日期已经计算在内),这种情况包括当上一个结束等于当前开始时(是一个天重叠)。
  • 没有重叠:我们只是计算差值并加 1 来计算初始日期。

请注意,我们不需要额外的条件来保留 HouseID,因为默认情况下,LAG() 函数在没有前一行时返回 NULL,并且与 null 的比较始终为 false。

示例输入和输出:

| HouseId | InitialDay |    LastDay |
|---------|------------|------------|
|       1 | 2017-09-18 | 2017-09-20 |
|       1 | 2017-09-18 | 2017-09-22 |
|       1 | 2017-09-21 | 2017-09-22 |
|      19 | 2017-09-18 | 2017-09-27 |
|      19 | 2017-09-24 | 2017-09-26 |
|      19 | 2017-09-29 | 2017-09-30 |
|      20 | 2017-09-19 | 2017-09-22 |
|      20 | 2017-09-22 | 2017-09-26 |
|      20 | 2017-09-24 | 2017-09-27 |

| HouseId | Days |
|---------|------|
|       1 |    5 |
|      19 |   12 |
|      20 |    9 |

【讨论】:

  • 这正是我的意思,而且它易于阅读,优秀的代码非常感谢。
【解决方案2】:
select house_id,min(initialDay),max(LastDay)
group by houseId

如果我理解正确的话!

试试看,告诉我它对你有什么效果。

泰德。

【讨论】:

  • 如果一个特定的 houseID 有多个条目并且它们之间有一个未占用的时间,那将会多计。例如。如果房子 1 从 9 月 1 日到 9 月 3 日逗留,另一个从 9 月 5 日到 9 月 10 日。
【解决方案3】:

在思考您的问题时,我想到了日历表的想法。您将使用此代码创建一个,其中包含您想要的日历日期范围。代码来自http://blog.jontav.com/post/9380766884/calendar-tables-are-incredibly-useful-in-sql

declare @start_dt as date = '1/1/2010';     
declare @end_dt as date = '1/1/2020';       

declare @dates as table (
 date_id date primary key,
 date_year smallint,
 date_month tinyint,
 date_day tinyint,
 weekday_id tinyint,
 weekday_nm varchar(10),
 month_nm varchar(10),
 day_of_year smallint,
 quarter_id tinyint,
 first_day_of_month date,
 last_day_of_month date,
 start_dts datetime,
 end_dts datetime
)

while @start_dt < @end_dt
begin
    insert into @dates(
        date_id, date_year, date_month, date_day, 
        weekday_id, weekday_nm, month_nm, day_of_year, quarter_id, 
        first_day_of_month, last_day_of_month, 
        start_dts, end_dts
    )   
    values(
        @start_dt, year(@start_dt), month(@start_dt), day(@start_dt), 
        datepart(weekday, @start_dt), datename(weekday, @start_dt), datename(month, @start_dt), datepart(dayofyear, @start_dt), datepart(quarter, @start_dt),
        dateadd(day,-(day(@start_dt)-1),@start_dt), dateadd(day,-(day(dateadd(month,1,@start_dt))),dateadd(month,1,@start_dt)), 
        cast(@start_dt as datetime), dateadd(second,-1,cast(dateadd(day, 1, @start_dt) as datetime))
    )
    set @start_dt = dateadd(day, 1, @start_dt)
end

select * 
into Calendar
from @dates

一旦你有了一个日历表,你的查询就很简单了:

select distinct t.House_id, c.date_id
from Reservation as r
inner join Calendar as c
on 
    c.date_id >= r.InitialDay 
    and c.date_id <= r.LastDay

每个房间被占用的每个独特的日子都会为您提供一行。如果您需要每个房间被占用的天数总和,则为:

select a.House_id, count(a.House_id) as Days_occupied
from
    (select distinct t.House_id, c.date_id
    from so_test as t
    inner join Calendar as c
    on 
        c.date_id >= t.InitialDay 
        and c.date_id <= t.LastDay) as a
group by a.House_id

【讨论】:

    【解决方案4】:

    创建一个包含所有可能日期的表,然后将其连接到 Reservations 表,这样您就有了 InitialDay 和 LastDay 之间所有日期的列表。像这样:

        DECLARE @i date
    DECLARE @last date
    CREATE TABLE #temp (Date date)
    SELECT @i = MIN(Date) FROM Reservations
    SELECT @last = MAX(Date) FROM Reservations
    WHILE @i <= @last
    BEGIN
        INSERT INTO #temp VALUES(@i)
        SET @i = DATEADD(day, 1, @i)
    END
    SELECT HouseID, COUNT(*) FROM
    (
        SELECT DISTINCT HouseID, Date FROM Reservation
        LEFT JOIN #temp 
            ON Reservation.InitialDay <= #temp.Date 
            AND Reservation.LastDay >= #temp.Date
    ) AS a
    GROUP BY HouseID
    DROP TABLE #temp
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-05-27
      • 2018-09-20
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多