Перебора дат в SQL

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

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 так же, как и с другими языками.

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

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

спасибо!

Джонатан

2 ответов


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

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

EDIT, я возвращаюсь к этому с решением, которое использует общее табличное выражение (CTE). Это не требует использования таблицы dates.

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

способ, которым вы перебираете строки в 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. Это делает его дублированием одних и тех же данных, что может вызвать всевозможные проблемы. Например, что вы делаете, когда ваше время начала и окончания 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

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

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