Например, есть таблица с датами:

2015-01-01
2015-01-02
2015-01-03
2015-01-06
2015-01-07
2015-01-11

Мне нужно написать запрос ms sql, который вернет количество последовательных дат, начиная с каждой даты в таблице. Так что результат будет примерно таким:

2015-01-01   1
2015-01-02   2
2015-01-03   3
2015-01-06   1
2015-01-07   2
2015-01-11   1

Мне кажется, что надо использовать функции LAG и LEAD, но сейчас я даже не могу представить себе способ мышления.

10
Mutex 31 Янв 2015 в 11:35

3 ответа

Лучший ответ
CREATE TABLE #T ( MyDate DATE) ;
INSERT #T VALUES ('2015-01-01'),('2015-01-02'),('2015-01-03'),('2015-01-06'),('2015-01-07'),('2015-01-11')

SELECT 
    RW=ROW_NUMBER() OVER( PARTITION BY GRP  ORDER BY MyDate) ,MyDate
FROM
(
SELECT 
    MyDate, DATEDIFF(Day, '1900-01-01' , MyDate)- ROW_NUMBER() OVER( ORDER BY MyDate ) AS GRP
FROM #T 
) A

DROP TABLE #T;
13
Lukas Eder 31 Янв 2015 в 09:36

Я предполагаю эту таблицу:

SELECT * 
INTO #Dates
FROM (VALUES
  (CAST('2015-01-01' AS DATE)),
  (CAST('2015-01-02' AS DATE)),
  (CAST('2015-01-03' AS DATE)),
  (CAST('2015-01-06' AS DATE)),
  (CAST('2015-01-07' AS DATE)),
  (CAST('2015-01-11' AS DATE))) dates(d);

Вот рекурсивное решение с пояснениями:

WITH
  dates AS (
    SELECT
      d, 
      -- This checks if the current row is the start of a new group by using LAG()
      -- to see if the previous date is adjacent
      CASE datediff(day, d, LAG(d, 1) OVER(ORDER BY d)) 
        WHEN -1 THEN 0 
        ELSE 1 END new_group,
      -- This will be used for recursion
      row_number() OVER(ORDER BY d) rn
    FROM #Dates
  ),
  -- Here, the recursion happens
  groups AS (
    -- We initiate recursion with rows that start new groups, and calculate "GRP"
    -- numbers
    SELECT d, new_group, rn, row_number() OVER(ORDER BY d) grp
    FROM dates
    WHERE new_group = 1
    UNION ALL
    -- We then recurse by the previously calculated "RN" until we hit the next group
    SELECT dates.d, dates.new_group, dates.rn, groups.grp
    FROM dates JOIN groups ON dates.rn = groups.rn + 1
    WHERE dates.new_group != 1
  )
-- Finally, we enumerate rows within each group
SELECT d, row_number() OVER (PARTITION BY grp ORDER BY d)
FROM groups
ORDER BY d

SQLFiddle

0
Lukas Eder 31 Янв 2015 в 09:31

Вы можете использовать это CTE:

;WITH CTE AS (
   SELECT [Date],        
          ROW_NUMBER() OVER(ORDER BY [Date]) AS rn,
          CASE WHEN DATEDIFF(Day, PrevDate, [Date]) IS NULL THEN 0
               WHEN DATEDIFF(Day, PrevDate, [Date]) > 1 THEN 0
               ELSE 1
          END AS flag
   FROM (
      SELECT [Date], LAG([Date]) OVER (ORDER BY [Date]) AS PrevDate
      FROM #Dates ) d
)

Чтобы получить следующий результат:

Date        rn  flag
===================
2015-01-01  1   0
2015-01-02  2   1
2015-01-03  3   1
2015-01-06  4   0
2015-01-07  5   1
2015-01-11  6   0

Все, что вам теперь нужно сделать, это вычислить промежуточный итог от flag до первого появления предшествующего нулевого значения:

;WITH CTE AS (
   ... cte statements here ...
)
SELECT [Date], b.cnt + 1
FROM CTE AS c
OUTER APPLY (
   SELECT TOP 1 COALESCE(rn, 1) AS rn 
   FROM CTE
   WHERE flag = 0 AND rn < c.rn
   ORDER BY rn DESC
) a 
CROSS APPLY (
   SELECT COUNT(*) AS cnt
   FROM CTE 
   WHERE c.flag <> 0 AND rn < c.rn AND rn >= a.rn
) b

OUTER APPLY вычисляет значение rn первого флага с нулевым значением, который стоит перед текущей строкой. CROSS APPLY вычисляет количество записей, предшествующих текущей записи до первого появления предыдущего флага с нулевым значением.

1
Giorgos Betsos 31 Янв 2015 в 09:25