2011-01-31 16 views
3

日付範囲から日付が連続しているかどうかを見つける最良の方法を探しています(可能な場合はカーソルや重い関数を避けようとしています)。非連続範囲から日付を検索

私はホテルのゲストが出入りする(チェックイン、チェックアウト)と言います。私は特定のゲストが滞在した日付を見つけたいです45th夜私たちと。私たちが使用するデータベースは、なるようにデータが記録されます。ここでは

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') 

これまでのところ私は一時テーブルとそのようなクレイジーなものを持つ関数を伴う解決策を考えることができ、私は感じています私はそれを考えるのと同じように。

ありがとうございます。

EDIT: @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 
+0

私は、MySQLと同様の(しかし異なる)問題に取り組んできました。そのトリックは、レコードが始まる前の日付から終了した後の数年までの各日付の行を持つ別のテーブルを持つことでした。あなたはその2つを結合し、滞在中に日付が落ちる所与の値(例えば「1」)を作成することができます。次に、結合されたデータセットから、指定された値を持つ45行目を探します。もちろん、SQL Server固有の回答が良いでしょう; – Purpletoucan

+0

「EndDate」は、クライアントが最後の夜に滞在した日付です。本当のチェックアウト日だと思った。今日は正午にチェックアウトすると、夜間は確かに滞在しないので、今日はスクリプトで数えてはいけません。しかし、それはそうではないように思われる、私は理解する。 –

+0

@Andriy M:そうです。最後の日は最後の夜です。私のアプリは本当にホテル滞在ではありません、おそらく別のシナリオを使用する必要があります。再度、感謝します。 – AGoodDisplayName

答えて

1

、I私の解決策もパラメータ化されました(どうして本当ですか?)。

ワンノート:あなたが「第45回」言ったので、私はそれが夜の前が取られるべき日付を意味undestand。私がそこに間違っている場合は、Dateが計算される- 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 
+0

それは素晴らしいです。私はmaster..spt_valuesが何であったかを調べなければならなかったし、それがどのように動作するかを理解するためにこれをちょっと凝視しなければならなかった。私はあなたがspt_valuesにリンクすることで、それぞれの滞在をどのように広げていくのが大好きです。はるかに簡単で、再帰を必要としません。素晴らしい答え、+1。 –

+0

あなたがやっていたことを理解するのにはしばらく時間がかかりました。あなたのコードが動作し、最もコンパクトで、完璧に私のsprocに収まる...私はあなたの答えを受け入れています。私はそれが私が望むように(私の編集を参照してください)動作するように少し修正する必要がありました。ありがとう! – AGoodDisplayName

+0

@Jeremy Pridemore:ありがとう。私は、私は最近、マスターのことについて学んだことがあります.spt_valuesの存在は、まさにここにありました。そして、この素晴らしい経験交換の場で神に感謝します。 –

0

(私は列のインラインクエリを使用して嫌いますが、他の経路を考えるcouldb't。)これを試してみてください:

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 
+0

これは、45日目の夜の日付が見つかりません。 – TToni

+0

@TToni:それに気付かなかった。クエリを更新しました... – Chandu

0

私は私が座っている私のコードをテストcant'tが、次のように動作するはずです:

まず、tally tableを生成します(できるだけ永久にしてください)。次に、45行にアクセスするROW_NUMBERとのCTE()を追加

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 

次に、このような日付範囲を平坦化するために使用します。

これらのクエリタイプが頻繁にある場合は、醜いDATEADDを取り除くために、追加の日付テーブルを作成します(数字テーブルと同じですが日付は付け加えてください)。

0

私は90の互換性(SQL Server 2005の)でデータベースにSQL Server 2008 R2の私の解決策をテストしたので、私はこれがにあなたがそれをやりたいと考えている:ジェレミーPridemoreの良い例に続き

-- 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)