Подтвердить что ты не робот

SQL Server: как выбрать все дни в диапазоне дат, даже если в течение нескольких дней не существует данных

У меня есть приложение, которое должно отображать гистограмму для активности за последние 30 дней. Граф должен показывать все дни, даже если в течение дня нет активности.

например:

DATE       COUNT
==================
1/1/2011   5 
1/2/2011   3 
1/3/2011   0
1/4/2011   4
1/5/2011   0
etc....

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

4b9b3361

Ответ 1

Вы можете использовать рекурсивный CTE для создания списка из 30 дней, затем присоедините его к своим данным

--test
select cast('05 jan 2011' as datetime) as DT, 1 as val into #t
union all select CAST('05 jan 2011' as datetime), 1 
union all select CAST('29 jan 2011' as datetime), 1 

declare @start datetime = '01 jan 2011'
declare @end   datetime = dateadd(day, 29, @start)

;with amonth(day) as
(
    select @start as day
        union all
    select day + 1
        from amonth
        where day < @end
)
select amonth.day, count(val)
    from amonth 
    left join #t on #t.DT = amonth.day
group by amonth.day


>>

2011-01-04 00:00:00.000 0
2011-01-05 00:00:00.000 2
2011-01-06 00:00:00.000 0
2011-01-07 00:00:00.000 0
2011-01-08 00:00:00.000 0
2011-01-09 00:00:00.000 0
...

Ответ 2

Использование CTE:

WITH DateTable
AS
(
    SELECT CAST('20110101' AS Date) AS [DATE]
    UNION ALL
    SELECT DATEADD(dd, 1, [DATE])
    FROM DateTable
    WHERE DATEADD(dd, 1, [DATE]) < cast('20110201' as Date)
)
SELECT dt.[DATE], ISNULL(md.[COUNT], 0) as [COUNT]
FROM [DateTable] dt
LEFT JOIN [MyData] md
ON md.[DATE] = dt.[DATE]

Это предполагает, что все Date; если это DateTime, вам придется обрезать (с помощью DATEADD(dd, 0, DATEDIFF(dd, 0, [DATE]))).

Ответ 3

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

Используйте внешнее соединение между двумя таблицами, чтобы убедиться, что каждая дата в вашей таблице дат отражается на выходе.

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

Ответ 4

Без Transact-SQL: MS SQL 2005 - Получить список всех дней месяца:

В моем случае "20121201" является предопределенным значением.


 SELECT TOp (Select Day(DateAdd(day, -Day(DateAdd(month, 1,
 '20121201')), 
                          DateAdd(month, 1, '20121201')))) DayDate FROM ( SELECT DATEADD(DAY,ROW_NUMBER() OVER (ORDER BY (SELECT
 NULL))-1,'20121201') as DayDate FROM sys.objects s1 CROSS JOIN
 sys.objects s2 ) q

Ответ 5

Ответ на

@Alex K. полностью верен, но он не работает для версий, которые не поддерживают рекурсивные общие выражения таблиц (например, версия, с которой я работаю). В этом случае это выполнит следующее.

DECLARE @StartDate datetime = '2015-01-01'
DECLARE @EndDate datetime = SYSDATETIME()

;WITH days AS
(
  SELECT DATEADD(DAY, n, DATEADD(DAY, DATEDIFF(DAY, 0, @StartDate), 0)) as d
    FROM ( SELECT TOP (DATEDIFF(DAY, @StartDate, @EndDate) + 1)
            n = ROW_NUMBER() OVER (ORDER BY [object_id]) - 1
           FROM sys.all_objects ORDER BY [object_id] ) AS n
)
select days.d, count(t.val)
    FROM days LEFT OUTER JOIN yourTable as t
    ON t.dateColumn >= days.d AND t.dateColumn < DATEADD(DAY, 1, days.d)
GROUP BY days.d
ORDER BY days.d;

Ответ 6

создать таблицу чисел и использовать ее как:

declare @DataTable table (DateColumn datetime)
insert @DataTable values ('2011-01-09')
insert @DataTable values ('2011-01-10')
insert @DataTable values ('2011-01-10')
insert @DataTable values ('2011-01-11')
insert @DataTable values ('2011-01-11')
insert @DataTable values ('2011-01-11')

declare @StartDate  datetime
SET @StartDate='1/1/2011'

select
    @StartDate+Number,SUM(CASE WHEN DateColumn IS NULL THEN 0 ELSE 1 END)
    FROM Numbers
        LEFT OUTER JOIN @DataTable ON [email protected]+Number
    WHERE Number>=1 AND Number<=15
    GROUP BY @StartDate+Number

ВЫВОД:

----------------------- -----------
2011-01-02 00:00:00.000 0
2011-01-03 00:00:00.000 0
2011-01-04 00:00:00.000 0
2011-01-05 00:00:00.000 0
2011-01-06 00:00:00.000 0
2011-01-07 00:00:00.000 0
2011-01-08 00:00:00.000 0
2011-01-09 00:00:00.000 1
2011-01-10 00:00:00.000 2
2011-01-11 00:00:00.000 3
2011-01-12 00:00:00.000 0
2011-01-13 00:00:00.000 0
2011-01-14 00:00:00.000 0
2011-01-15 00:00:00.000 0
2011-01-16 00:00:00.000 0

(15 row(s) affected)

Ответ 7

Может быть, что-то вроде этого: Создайте DaysTable, за 30 дней. И DataTable, содержащий столбец "день" и столбец "count". А затем левые присоединяются к ним.

WITH    DaysTable (name) AS (
        SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 -- .. And so on to 30
    ),
    DataTable (name, value) AS (        
        SELECT  DATEPART(DAY, [Date]), [Count]
        FROM    YourExampleTable
        WHERE   [Date] < DATEADD (day , -30 , getdate())
    )
SELECT  DaysTable.name, DataTable.value
FROM    DaysTable LEFT JOIN
        DataTable ON DaysTable.name = DataTable.name
ORDER BY DaysTable.name

Ответ 8

Для пациентов с рекуррентной аллергологией

select SubQ.TheDate
from 
(
    select DATEADD(day, a.a + (10 * b.a) + (100 * c.a), DATEADD(day, DATEDIFF(day, 0, GETDATE()), 0) - 30) AS TheDate
    from 
    (
        (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as a
        cross join (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as b
        cross join (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as c
    ) 
    WHERE a.a + (10 * b.a) + (100 * c.a) < 30
) AS SubQ
ORDER BY TheDate

Ответ 9

Попробуйте.

DECLARE @currentDate DATETIME = CONVERT(DATE, GetDate())
DECLARE @startDate   DATETIME = DATEADD(DAY, -DAY(@currentDate)+1, @currentDate)

;WITH fnDateNow(DayOfDate) AS
(
    SELECT @startDate AS DayOfDate
        UNION ALL
    SELECT DayOfDate + 1 FROM fnDateNow WHERE DayOfDate < @currentDate
) SELECT fnDateNow.DayOfDate FROM fnDateNow

Ответ 10

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

Итак, в таблице поиска "дней" я не мог хранить дату с временем "00: 00: 00.000" и получить любые совпадения. Поэтому я сохранил как строку, и я попытался присоединиться к преобразованному значению напрямую.

Это не возвращало нулевые строки, и решение заключалось в том, чтобы выполнить подзапрос, возвращающий дату, уже преобразованную в строку.

Пример кода следующим образом:

declare @startDate datetime = convert(datetime,'09/02/2016')
declare @curDate datetime = @startDate
declare @endDate datetime = convert(datetime,'09/09/2016')
declare @dtFormat int = 102;
DECLARE @null_Date varchar(24) = '1970-01-01 00:00:00.000'

/* Initialize #days table */
select CONVERT(VARCHAR(24),@curDate, @dtFormat) as [Period] into #days

/* Populate dates into #days table */
while (@curDate < @endDate )
begin
    set @curDate = dateadd(d, 1, @curDate)
    insert into #days values (CONVERT(VARCHAR(24),@curDate, @dtFormat))
end

/* Outer aggregation query to group by order numbers */
select [Period], count(c)-case when sum(c)=0 then 1 else 0 end as [Orders],
sum(c) as [Lines] from
(
    /* Inner aggregation query to sum by order lines */ 
    select
        [Period], sol.t_orno, count(*)-1 as c   
        from (
            /* Inner query against source table with date converted */
            select convert(varchar(24),t_dldt, @dtFormat) as [shipdt], t_orno
                from salesorderlines where t_dldt > @startDate
        ) sol
        right join #days on shipdt = #days.[Period]     
        group by [Period], sol.t_orno
) as t
group by Period
order by Period desc

drop table #days

Примеры результатов:

Period      Orders  Lines
2016.09.09  388     422
2016.09.08  169     229
2016.09.07  1       1
2016.09.06  0       0
2016.09.05  0       0
2016.09.04  165     241
2016.09.03  0       0
2016.09.02  0       0

Ответ 11

DECLARE @StartDate DATE = '20110101', @NumberOfYears INT = 1;

DECLARE @CutoffDate DATE = DATEADD(YEAR, @NumberOfYears, @StartDate);


CREATE TABLE Calender
(
  [date]       DATE
);


INSERT Calender([date]) 
SELECT d
FROM
(
  SELECT d = DATEADD(DAY, rn - 1, @StartDate)
  FROM 
  (
    SELECT TOP (DATEDIFF(DAY, '2011-01-01', '2011-12-31')) 
      rn = ROW_NUMBER() OVER (ORDER BY s1.[object_id])
    FROM sys.all_objects AS s1
    CROSS JOIN sys.all_objects AS s2
    ORDER BY s1.[object_id]
  ) AS x
) AS y;


create table test(a date)

insert into test values('1/1/2011')
insert into test values('1/1/2011')
insert into test values('1/1/2011')
insert into test values('1/1/2011')
insert into test values('1/1/2011')

insert into test values('1/2/2011')
insert into test values('1/2/2011')
insert into test values('1/2/2011')

insert into test values('1/4/2011')
insert into test values('1/4/2011')
insert into test values('1/4/2011')
insert into test values('1/4/2011')

select c.date as DATE,count(t.a) as COUNT from calender c left join test t on c.date = t.a group by c.date