复杂的SQL查询建议:保留分配逻辑

时间:2011-06-27 09:33:36

标签: sql-server algorithm tsql sql-server-2008 logic

我正在尝试在SQL Server 2008上进行复杂的查询(至少对我来说很复杂),到目前为止,我可以走到这一步。这是代码;

DECLARE @Hotels AS TABLE(
  HotelID   INT, 
  HotelName NVARCHAR(100)
);

DECLARE @HotelAllotments AS TABLE(
  HotelID   INT, 
  StartDate DATETIME,
  EndDate   DATETIME,
  Allotment INT
);

DECLARE @Reservations AS TABLE(
  ReservationID INT,
  HotelID       INT, 
  CheckIn       DATETIME, 
  CheckOut      DATETIME, 
  IsCanceled    BIT
);

INSERT @Hotels VALUES(1,'Foo Hotel');
INSERT @Hotels VALUES(2,'Poo Hotel');

INSERT @HotelAllotments VALUES(1,'2011-01-01', '2011-02-01', 10);
INSERT @HotelAllotments VALUES(1,'2011-02-02', '2011-02-18', 7);
INSERT @HotelAllotments VALUES(1,'2011-02-19', '2011-05-18', 19);
INSERT @HotelAllotments VALUES(1,'2011-05-19', '2011-10-18', 30);
INSERT @HotelAllotments VALUES(2,'2011-05-19', '2011-10-18', 30);

INSERT @Reservations VALUES(100, 1, '2011-05-10','2011-05-24',0);
INSERT @Reservations VALUES(101, 1, '2011-05-18','2011-05-28',0);
INSERT @Reservations VALUES(102, 1, '2011-03-07','2011-03-19',0);
INSERT @Reservations VALUES(103, 1, '2011-08-29','2011-09-07',0);
INSERT @Reservations VALUES(104, 1, '2011-09-01','2011-09-07',1);
INSERT @Reservations VALUES(105, 1, '2011-09-01','2011-09-07',1);

with e as( 
  SELECT ReservationID as resid1, CheckIn as chin1, 1 as lvl
  FROM @Reservations res1
  WHERE res1.HotelID = 1
  UNION ALL
  SELECT ReservationID as resid2, DATEADD(DAY,1,stall.chin1) as chin2, 1
  FROM @Reservations res2
    INNER JOIN e stall ON stall.chin1 < res2.CheckOut
  WHERE stall.resid1 = res2.ReservationID
)
SELECT tb.chin1, SUM(lvl)
FROM e tb
GROUP BY tb.chin1
ORDER BY tb.chin1 DESC

@HotelAllotments 部分,您可以看到 start end 日期。分配是每天进行的。我的意思是如果行如下所示;

INSERT @HotelAllotments VALUES(1,'2011-01-01', '2011-01-03', 10);

这意味着这个;

  • id为1的酒店在2011-01-01
  • 上有10个分配
  • id为1的酒店在2011-01-02
  • 上有10个分配
  • id为1的酒店在2011-01-03
  • 上有10个分配

然后,如果我们收到2011-01-01和2011-01-03之间的预订,如下所示;

INSERT @Reservations VALUES(106, 1, '2011-01-01','2011-01-03',0);

情况如下;

  • 在2011-01-01
  • 预订后,ID为1的酒店有9个分配
  • 在2011-01-02
  • 预订后,ID为1的酒店有9个分配
  • 在2011-01-03预订后,身份为1的酒店有 10 分配

上面,我创建了一些临时表并插入了一些假值,我尝试了一个查询。它让我到了某个地方(我不知道怎么称呼它。所以,如果你有一个 有机会运行查询,你会看到它到目前为止的地方)但不是我需要的地方。我需要的是那个;

我需要列出酒店签订协议的所有日期及收到预订后的左侧分配。这是一个例子;

HotelID  Date        Allotment
-------  ----------  ---------
1        2011-01-01  9
1        2011-01-02  9
1        2011-01-03  10
1        2011-01-04  10
1        2011-01-05  10

那我怎么能实现这个目标呢?

修改

有些人应该想知道为什么在预订的前两天取消分配,而不是最后一天。这是因为客人最后一天不会在酒店住一整天。他/她应该把房间倒空到凌晨12:00。因此,在最后一天不会有任何分配用途。

2 个答案:

答案 0 :(得分:4)

;WITH expanded AS (
  SELECT
    a.HotelID,
    Date = DATEADD(DAY, v.number, a.StartDate),
    a.Allotment
  FROM @HotelAllotments a
    INNER JOIN master..spt_values v ON v.type = 'P'
      AND v.number BETWEEN 0 AND DATEDIFF(DAY, a.StartDate, a.EndDate)
),
filtered AS (
  SELECT
    e.HotelID,
    e.Date,
    Allotment = e.Allotment - COUNT(r.ReservationID)
  FROM expanded e
    LEFT JOIN @Reservations r ON e.HotelID = r.HotelID
      AND e.Date >= r.CheckIn AND e.Date < r.CheckOut
      AND r.IsCanceled = 0
  GROUP BY e.HotelID, e.Date, e.Allotment
)
SELECT *
FROM filtered;

此解决方案使用系统表master..spt_values作为tally table来获取日期列表而不是日期范围。接下来,扩展的分配列表与@Rivesvations表连接在一起。对于列表中的每个日期,相应的分配将减少其范围与给定日期匹配的预订数量。

答案 1 :(得分:2)

我在编写where where子句时有点草率。我不知道你是否想要解决这些空白日子。这是我在设置where子句后想出的。我有datejumps的原因是为了弥补sql中100个重复调用的限制。所以我从系统表中加入了10行,更好地利用了100次重复,这样我可以获得1000行而不是100行。

WITH cte(HOTELID, STARTDATE, ENDDATE, Allotment)
as
(
SELECT H.HOTELID, A.STARTDATE + RN STARTDATE, (SELECT MAX(ENDDATE) FROM @HotelAllotments) ENDDATE,  (select Allotment from @HotelAllotments where A.STARTDATE + RN between StartDate and enddate and H.HOTELID = HOTELID) Allotment
FROM (
SELECT MIN(STARTDATE) STARTDATE from @HotelAllotments c    
) A,
(SELECT TOP 10 rn = ROW_NUMBER() OVER (ORDER BY (SELECT 1))-1 FROM INFORMATION_SCHEMA.COLUMNS) B,
@Hotels H 
UNION ALL
SELECT ch.HOTELID, ch.STARTDATE + 10, ENDDATE, (select Allotment from @HotelAllotments where CH.STARTDATE + 10 between StartDate and enddate and CH.HOTELID = HOTELID)
FROM cte ch    
WHERE CH.STARTDATE<  ENDDATE
AND CH.HOTELID = HOTELID
)
SELECT HotelID,  StartDate Date , Allotment - (select count(*) from @Reservations where cte.STARTDATE between CheckIn and CheckOut and cte.HOTELID = HOTELID) Allotment
FROM CTE where allotment is not null
ORDER BY STARTDATE, HOTELID