Sql server 2005 从非连续范围查找日期

Sql server 2005 从非连续范围查找日期,sql-server-2005,tsql,Sql Server 2005,Tsql,我正在寻找一种从日期范围中查找日期的最佳方法,该日期范围可能是连续的,也可能不是连续的。我正在尝试避免使用光标,或者如果可能,避免使用繁重的函数 比如说,我有来来去去的酒店客人在登记入住和退房。我想知道某位客人在我们这里住了45晚的日期。我们使用的数据库记录数据如下: Create Table #GuestLog( ClientId int, StartDate DateTime, EndDate DateTime) 这里有一些数据 Insert Into #Gue

我正在寻找一种从日期范围中查找日期的最佳方法,该日期范围可能是连续的,也可能不是连续的。我正在尝试避免使用光标,或者如果可能,避免使用繁重的函数

比如说,我有来来去去的酒店客人在登记入住和退房。我想知道某位客人在我们这里住了45晚的日期。我们使用的数据库记录数据如下:

Create Table #GuestLog(
    ClientId int, 
    StartDate DateTime, 
    EndDate DateTime)
这里有一些数据

Insert Into #GuestLog Values(1, '01/01/2010', '01/10/2010')
Insert Into #GuestLog Values(1, '01/16/2010', '01/29/2010')
Insert Into #GuestLog Values(1, '02/13/2010', '02/26/2010')
Insert Into #GuestLog Values(1, '04/05/2010', '06/01/2010')
Insert Into #GuestLog Values(1, '07/01/2010', '07/21/2010')
到目前为止,我只能想到一些解决方案,其中包括带有临时表的函数和类似的疯狂的东西,我觉得我想得太多了

提前谢谢

编辑:对@Andriy M的解决方案进行轻微修改

DECLARE @ClientID int, @NightNo int;
SET @ClientID = 1;
SET @NightNo = 45;

SELECT *
FROM ( SELECT  gl.ClientId
        , Date = gl.StartDate + v.number - 1
        , rownum = ROW_NUMBER() OVER ( PARTITION BY gl.ClientId ORDER BY gl.StartDate, v.Number)
    FROM #GuestLog gl
    INNER JOIN master..spt_values v ON v.type = 'P'
    AND v.number BETWEEN 1  AND gl.EndDate - gl.StartDate + 1) as s //--added "+ 1"
WHERE ClientId = @ClientId
AND rownum = @NightNo

试试看,我讨厌对列使用内联查询,但想不出其他任何途径:

WITH logd 
     AS (SELECT a.*, 
                (SELECT SUM(Datediff(d, startdate, enddate)) 
                 FROM   #guestlog b 
                 WHERE  b.clientid = a.clientid 
                        AND b.startdate <= a.startdate) dayssofar 
         FROM   #guestlog a) 
SELECT a.*, 
       Dateadd(d, ( 45 - dayssofar ), enddate) 
FROM   (SELECT b.*, 
               Row_number() OVER(PARTITION BY clientid ORDER BY dayssofar)rn 
        FROM   logd b 
        WHERE  dayssofar > 44) a 
WHERE  rn = 1  

我不能在我坐的地方测试我的代码,但以下应该可以工作:

首先,如果可以的话,生成一个永久的

然后使用它将日期范围展平,如下所示:

SELECT DATEADD(d,n.number,'01/01/2000') AS StayedDate
FROM numbers n
INNER JOIN #GuestLog g ON DATEADD(d,n.number,'01/01/2000') BETWEEN g.StartDate AND g.EndDate)
ORDER BY n.number
然后添加一个带有行号的CTE以访问第45行


如果您经常使用这些查询类型,请创建一个与数字表类似的附加日期表,但带有日期,以消除难看的日期添加。

我在SQL Server 2008 R2中测试了一个与SQL Server 2005兼容90的数据库,因此我相信这将满足您的要求:

-- PLEASE NOTE: MAXRECURSION at the bottom needs to have a number that is higher than the
--  number of stored stays that any guest this will run on will have. Otherwise you'll need
--  to find a way to do this without recursion.

-- Parameterized because...why not? :)
DECLARE @CustomerID INT = 1
    , @NthStayDay INT = 45;

-- This does nothing but get the rows out of GuestLog that we care about. From my experience
--  it's a good idea to do a simple data grab from a physical table or indexed view using
--  a seek, then play with that smaller subset of data in other CTE's. Though I'm sure that
--  those with more performance knowledge could give better answers. RowNumber is added for
--  recursion in the next CTE.
WITH OrderedStays(RowNumber, StartDate, EndDate) AS
(
    SELECT
        ROW_NUMBER() OVER(ORDER BY StartDate) AS RowNumber
        , StartDate
        , EndDate
    FROM @GuestLog GuestLog
    WHERE GuestLog.ClientId = @CustomerID
)
-- This is a recusive CTE, but I don't imagine it will preform to badly because there is no IO
--  at this point, simply processing the previous CTE. You'll have to be the judge of that.
--  The purpose of this CTE is to be able to limit down to the start date that we care about.
, StayRanges(RowNumber, StartDate, EndDate, FirstDayCount, LastDayCount) AS
(
    -- This is our anchor row. It is the first date range at which the guest stayed with you.
    --  The DATEDIFF returns 9 with dates of 20100101 - 20100110, but since I don't think the 
    --  0th day stayed makes sense, I'm making it return 10 in that case since we're starting
    --  at 1.
    SELECT
        RowNumber
        , StartDate
        , EndDate
        , 1 AS FirstDayCount
        , DATEDIFF(DAY, StartDate, EndDate) + 1 AS LastDayCount
    FROM OrderedStays
    WHERE RowNumber = 1

    UNION ALL

    -- This is the recursion. This joins the first CTE on what we have where the first CTE's
    --  RowNumber is 1 more than whatever is in our StayRanges CTE. The column logic is the
    --  same as above, but now we need to add in the LastDayCount from our previous iteration.
    SELECT
        OrderedStays.RowNumber
        , OrderedStays.StartDate
        , OrderedStays.EndDate
        , StayRanges.LastDayCount + 1 AS FirstDayCount
        , DATEDIFF(DAY, OrderedStays.StartDate, OrderedStays.EndDate) + StayRanges.LastDayCount + 1 AS LastDayCount
    FROM OrderedStays
    INNER JOIN StayRanges
        ON (StayRanges.RowNumber + 1) = OrderedStays.RowNumber
)
-- Now that we have our ranges, we can select the range that has the day we want in it with a
--  simple between. Once that's done, take out the FirstDayCount from the day we care about so
--  that you're left with the difference from the StartDate and the date we want, and add that
--  to the StartDate. Done!
SELECT
    DATEADD(DAY, @NthStayDay - FirstDayCount, StartDate) AS DateOfNthStayDate
FROM StayRanges
WHERE @NthStayDay BETWEEN FirstDayCount AND LastDayCount
OPTION(MAXRECURSION 5000)

按照Jeremy Pridemore的好例子,我也对我的解决方案进行了参数化,为什么不呢

注意:因为你说的是“第45晚”,我不理解这意味着应该记下前一晚的日期。如果我错了,那么只需删除计算日期的-1部分

DECLARE @ClientID int, @NightNo int;
SET @ClientID = 1;
SET @NightNo = 45;

SELECT *
FROM (
  SELECT
    gl.ClientId,
    Date = gl.StartDate + v.number - 1,
    rownum = ROW_NUMBER() OVER (
      PARTITION BY gl.ClientId
      ORDER BY gl.StartDate, v.Number
    )
  FROM #GuestLog gl
    INNER JOIN master..spt_values v ON v.type = 'P'
      AND v.number BETWEEN 1 AND gl.EndDate - gl.StartDate
) s
WHERE ClientId = @ClientId
  AND rownum = @NightNo

我用MySQL解决了一个类似但不同的问题。诀窍是为每个日期创建一个单独的表,从记录开始前的一个日期到记录结束后的几年。然后,您可以将两者结合起来,创建一个给定的值,例如“1”,其中日期在停留期间。然后,您只需从具有给定值的联接数据集中查找第45行。当然,可能有一个SQL Server特定的答案更有效;所以看来EndDate是客户最后一晚入住的日期。我认为这是真正的结帐日期,例如,如果我今天中午结帐,那么我肯定不会留下过夜,因此今天不应计入脚本。但我明白,你的情况似乎不是这样。@Andrey M:是的,结束日期是最后一晚。我的应用程序并不是真正的酒店住宿,我可能应该使用不同的场景。再次感谢。好吧,我内心的完美主义者建议对剧本再做一次修改。在您评论的行中,将介于0和gl.EndDate-gl.StartDate之间的内容更改为介于0和gl.EndDate-gl.StartDate之间,即从0开始,而不是从1开始,删除+1并删除我前面提到的-1,日期=。。。线稍微少一些计算,最后稍微优雅一些。他答应不再在这个问题上打扰你那找不到第45天晚上的约会,太神奇了。我得去查一下什么是master..spt_值,然后盯着它看一看,看看它是怎么工作的。我喜欢你通过将每次住宿与spt_价值观联系起来来扩展住宿的方式。更简单,不需要递归。回答得很好,+1。我也花了一些时间才弄明白你在干什么。你的代码工作正常,是最紧凑的,完全适合我的存储过程……我接受你的答案。我确实需要稍微修改一行,使其按照我希望看到的编辑方式工作。谢谢@杰里米·普里德摩尔:谢谢。我必须说,我只是最近才了解到师父..spt_价值观的存在,就在这里,所以,我感谢上帝为我提供了这个交流经验的好地方。@AGoodDisplayName:谢谢!