Итерация по датам в SQL - PullRequest
       16

Итерация по датам в SQL

6 голосов
/ 31 августа 2010

У меня есть таблица данных, которая выглядит примерно так:

Name    StartTime              FinishTime              Work
Bob     2010-08-03 08:00:00    2010-08-03 12:00:00     4
Bob     2010-08-03 13:00:00    2010-08-03 16:00:00     3
Pete    2010-08-04 08:00:00    2010-08-04 12:00:00     4
Mark    2010-08-04 10:00:00    2010-08-04 12:00:00     2

Ни один из этих диапазонов дат не должен охватывать полночь.
Я хочу написать SQL, который даст мне следующий вывод, учитывая дату начала ввода 2010-08-02 и дату окончания 2010-08-05

Date          Name   TotalWork
2010-08-03    Bob    7
2010-08-03    Pete   3
2010-08-04    Pete   4
2010-08-04    Mark   2 

Я мог бы жить и, в действительности, в конечном счете, нуждаться в том, чтобы дни, в которых нет работы, также были представлены в наборе результатов, возможно, в виде строки, подобной этой:

2010-08-05     NULL   0

Я не совсем уверен, как перебирать даты в SQL так же, как и в других языках.

Чтобы придать этому некоторый контекст, выходные данные этого в конечном итоге будут подключены к элементу управления Stacked Chart .Net.

Может ли кто-нибудь дать мне подсказку, ссылку на учебник или другую помощь? В противном случае, я думаю, что буду возиться с этим в течение нескольких дней!

Спасибо!

Jonathan

Ответы [ 2 ]

7 голосов
/ 31 августа 2010

Попробуйте это:

Select DateAdd(day, 0, DateDiff(day, 0, StartDate)) Date,
    Name, Sum (Work) TotalWork
From TableData
Group By Name, DateAdd(day, 0, DateDiff(day, 0, StartDate)) 

Чтобы получить пропущенные дни сложнее.

   Declare @SD DateTime, @ED DateTime  -- StartDate and EndDate variables
   Select @SD = DateAdd(day, 0, DateDiff(day, 0, Min(StartDate))),
          @ED = DateAdd(day, 0, DateDiff(day, 0, Max(StartDate)))
   From TableData
   Declare @Ds Table (aDate SmallDateTime)
   While @SD <= @ED Begin 
       Insert @Ds(aDate ) Values @SD
       Set @SD = @SD + 1
   End 
-- ----------------------------------------------------
 Select DateAdd(day, 0, DateDiff(day, 0, td.StartDate)) Date,
    td.Name, Sum (td.Work) TotalWork
 From @Ds ds Left Join TableData td
    On DateAdd(day, 0, DateDiff(day, 0, tD.StartDate)) = ds.aDate 
 Group By Name, DateAdd(day, 0, DateDiff(day, 0, tD.StartDate)) 

РЕДАКТИРОВАТЬ, я возвращаюсь к этому с решением, которое использует общее выражение таблицы (CTE),Это НЕ требует использования таблицы дат.

    Declare @SD DateTime, @ED DateTime
    Declare @count integer = datediff(day, @SD, @ED)
    With Ints(i) As
      (Select 0 Union All
    Select i + 1 From Ints
    Where i < @count )  
     Select DateAdd(day, 0, DateDiff(day, 0, td.StartDate)) Date,
         td.Name, Sum (td.Work) TotalWork
     From Ints i 
        Left Join TableData d
           On DateDiff(day, @SD, d.StartDate) = i.i
     Group By d.Name, DateAdd(day, 0, DateDiff(day, 0, d.StartDate)) 
5 голосов
/ 31 августа 2010

То, как вы перебираете строки в SQL, заключается в том, что вы этого не делаете. SQL - это язык на основе множеств, который требует совершенно другого мышления, чем другие процедурные языки. Если вы собираетесь работать с SQL, вам действительно необходимо изменить этот подход, думая, что он будет успешным.

Вот как бы я справился с этим:

SELECT
    CONVERT(VARCHAR(10), StartTime, 121) AS [date],
    name,
    SUM(work)
FROM
    My_Table
WHERE
    StartTime >= @start_date AND
    StartTime < DATEADD(dy, 1, @finish_date)
GROUP BY
    CONVERT(VARCHAR(10), StartTime, 121),
    name

Кроме того, ваш дизайн таблицы выглядит так, как будто он нарушает нормальные стандарты проектирования баз данных. Ваш столбец "работа" на самом деле просто расчет между StartTime и FinishTime. Это делает дублирование одних и тех же данных, что может вызвать всевозможные проблемы. Например, что вы делаете, когда ваши StartTime и FinishTime разнесены на 4 часа, а «Работа» означает 5 часов?

Чтобы включить даты без связанной работы, вам нужно либо обработать их во внешнем интерфейсе, либо вам понадобится таблица «Календарь». В нем будут все даты, и вы сделаете ЛЕВОЕ СОЕДИНЕНИЕ к этому со своей таблицей. Например:

SELECT
    CONVERT(VARCHAR(10), C.StartTime, 121) AS [date],
    MT.name,
    SUM(MT.work)
FROM
    Calendar C
LEFT JOIN My_Table MT ON
    MT.StartDate BETWEEN C.StartTime and C.FinishTime
WHERE
    C.StartTime >= @start_date AND
    C.StartTime < DATEADD(dy, 1, @finish_date)
GROUP BY
    CONVERT(VARCHAR(10), C.StartTime, 121),
    MT.name

Таблица календаря также позволяет добавлять к датам дополнительную информацию, такую ​​как флаг для праздничных дней, «сверхурочные» дни (возможно, работа считается как полтора раза по воскресеньям) и т. Д.

ПРИМЕЧАНИЕ : Решение Чарльза Бретаны, вероятно, немного чище, поскольку он сохраняет типы данных в виде даты-времени, а не превращает их в строки. Я собираюсь оставить это здесь для некоторых других комментариев.

...