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