Найти дату из несмежных диапазонов - PullRequest
3 голосов
/ 01 февраля 2011

Я ищу, чтобы найти лучший способ найти дату из диапазонов дат, которые могут быть или не быть смежными (я пытаюсь избежать курсора или тяжелой функции, если это возможно).

Допустим, у меня есть гости отеля, которые приходят и уходят (заезд, выезд). Я хочу найти дату, когда какой-то гость провел у нас 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

Ответы [ 4 ]

1 голос
/ 01 февраля 2011

Следуя хорошему примеру Джереми Придемора, я тоже параметризовал свое решение (почему бы и нет?).

Одно примечание: поскольку вы сказали «45-я ночь », я понимаю, что это означает, что дата , предшествующая ночь, должна быть взята. Если я ошибаюсь, просто удалите часть - 1, где вычисляется Date.

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 голосов
/ 01 февраля 2011

Я не могу проверить свой код, где я сижу, но должно работать следующее:

Сначала создайте таблицу подсчета (сделайте ее постоянной, если можете).

Затем используйте его, чтобы сгладить диапазон дат следующим образом:

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 с помощью ROW_NUMBER () для доступа к 45-й строке.

Если у вас часто встречаются эти типы запросов, создайте дополнительную таблицу дат (как в таблице чисел, но с датами), чтобы избавиться от уродливых DATEADD.

0 голосов
/ 01 февраля 2011

Я протестировал свое решение в SQL Server 2008 R2 на базе данных с совместимостью 90 (SQL Server 2005), поэтому я считаю, что это сделает то, что вы хотите:

-- 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)
0 голосов
/ 01 февраля 2011

Попробуйте (я ненавижу использовать встроенные запросы для столбцов, но не могу придумать какой-либо другой маршрут.):

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  
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...