Получить список дат между двумя датами - PullRequest
85 голосов
/ 04 февраля 2009

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

например, учитывая 2009-01-01 и 2009-01-13, он вернет таблицу из одного столбца со значениями:

 2009-01-01 
 2009-01-02 
 2009-01-03
 2009-01-04 
 2009-01-05
 2009-01-06
 2009-01-07
 2009-01-08 
 2009-01-09
 2009-01-10
 2009-01-11
 2009-01-12
 2009-01-13

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

Ответы [ 19 ]

65 голосов
/ 04 февраля 2009

Я бы использовал эту хранимую процедуру для генерации необходимых интервалов во временную таблицу с именем time_intervals , затем JOIN и агрегировал вашу таблицу данных с таблицей temp time_intervals .

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

call make_intervals('2009-01-01 00:00:00','2009-01-10 00:00:00',1,'DAY')
.
select * from time_intervals  
.
interval_start      interval_end        
------------------- ------------------- 
2009-01-01 00:00:00 2009-01-01 23:59:59 
2009-01-02 00:00:00 2009-01-02 23:59:59 
2009-01-03 00:00:00 2009-01-03 23:59:59 
2009-01-04 00:00:00 2009-01-04 23:59:59 
2009-01-05 00:00:00 2009-01-05 23:59:59 
2009-01-06 00:00:00 2009-01-06 23:59:59 
2009-01-07 00:00:00 2009-01-07 23:59:59 
2009-01-08 00:00:00 2009-01-08 23:59:59 
2009-01-09 00:00:00 2009-01-09 23:59:59 
.
call make_intervals('2009-01-01 00:00:00','2009-01-01 02:00:00',10,'MINUTE')
. 
select * from time_intervals
.  
interval_start      interval_end        
------------------- ------------------- 
2009-01-01 00:00:00 2009-01-01 00:09:59 
2009-01-01 00:10:00 2009-01-01 00:19:59 
2009-01-01 00:20:00 2009-01-01 00:29:59 
2009-01-01 00:30:00 2009-01-01 00:39:59 
2009-01-01 00:40:00 2009-01-01 00:49:59 
2009-01-01 00:50:00 2009-01-01 00:59:59 
2009-01-01 01:00:00 2009-01-01 01:09:59 
2009-01-01 01:10:00 2009-01-01 01:19:59 
2009-01-01 01:20:00 2009-01-01 01:29:59 
2009-01-01 01:30:00 2009-01-01 01:39:59 
2009-01-01 01:40:00 2009-01-01 01:49:59 
2009-01-01 01:50:00 2009-01-01 01:59:59 
.
I specified an interval_start and interval_end so you can aggregate the 
data timestamps with a "between interval_start and interval_end" type of JOIN.
.
Code for the proc:
.
-- drop procedure make_intervals
.
CREATE PROCEDURE make_intervals(startdate timestamp, enddate timestamp, intval integer, unitval varchar(10))
BEGIN
-- *************************************************************************
-- Procedure: make_intervals()
--    Author: Ron Savage
--      Date: 02/03/2009
--
-- Description:
-- This procedure creates a temporary table named time_intervals with the
-- interval_start and interval_end fields specifed from the startdate and
-- enddate arguments, at intervals of intval (unitval) size.
-- *************************************************************************
   declare thisDate timestamp;
   declare nextDate timestamp;
   set thisDate = startdate;

   -- *************************************************************************
   -- Drop / create the temp table
   -- *************************************************************************
   drop temporary table if exists time_intervals;
   create temporary table if not exists time_intervals
      (
      interval_start timestamp,
      interval_end timestamp
      );

   -- *************************************************************************
   -- Loop through the startdate adding each intval interval until enddate
   -- *************************************************************************
   repeat
      select
         case unitval
            when 'MICROSECOND' then timestampadd(MICROSECOND, intval, thisDate)
            when 'SECOND'      then timestampadd(SECOND, intval, thisDate)
            when 'MINUTE'      then timestampadd(MINUTE, intval, thisDate)
            when 'HOUR'        then timestampadd(HOUR, intval, thisDate)
            when 'DAY'         then timestampadd(DAY, intval, thisDate)
            when 'WEEK'        then timestampadd(WEEK, intval, thisDate)
            when 'MONTH'       then timestampadd(MONTH, intval, thisDate)
            when 'QUARTER'     then timestampadd(QUARTER, intval, thisDate)
            when 'YEAR'        then timestampadd(YEAR, intval, thisDate)
         end into nextDate;

      insert into time_intervals select thisDate, timestampadd(MICROSECOND, -1, nextDate);
      set thisDate = nextDate;
   until thisDate >= enddate
   end repeat;

 END;

Подобный пример сценария данных в нижней части этого поста , где я построил аналогичную функцию для SQL Server.

28 голосов
/ 18 ноября 2011

Для MSSQL вы можете использовать это. Это ОЧЕНЬ быстро.

Вы можете обернуть это в табличную функцию или сохраненную процедуру и проанализировать начальную и конечную даты как переменные.

DECLARE @startDate DATETIME
DECLARE @endDate DATETIME

SET @startDate = '2011-01-01'
SET @endDate = '2011-01-31';

WITH dates(Date) AS 
(
    SELECT @startdate as Date
    UNION ALL
    SELECT DATEADD(d,1,[Date])
    FROM dates 
    WHERE DATE < @enddate
)

SELECT Date
FROM dates
OPTION (MAXRECURSION 0)
GO
13 голосов
/ 04 февраля 2009

У нас была похожая проблема с отчетами BIRT в том, что мы хотели сообщать в те дни, когда данных не было. Поскольку для этих дат не было записей, самым простым решением для нас было создать простую таблицу, в которой хранятся все даты, и использовать ее для получения диапазонов или объединить, чтобы получить нулевые значения для этой даты.

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

create table all_dates (
    dt date primary key
);

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

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

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

Но, если честно, вы можете заполнить таблицу на 1000 лет без каких-либо серьезных проблем с хранением данных - 365 000 16-байтовых (например) дат плюс индекс, дублирующий дату плюс 20% накладных расходов на безопасность, я бы сказал, грубо говоря, около 14M [365 000 * 16 * 2 * 1,2 = 14 016 000 байт]), крошечная таблица в схеме вещей.

12 голосов
/ 04 февраля 2009

Вы можете использовать MySQL пользовательские переменные , например:

SET @num = -1;
SELECT DATE_ADD( '2009-01-01', interval @num := @num+1 day) AS date_sequence, 
your_table.* FROM your_table
WHERE your_table.other_column IS NOT NULL
HAVING DATE_ADD('2009-01-01', interval @num day) <= '2009-01-13'

@ num равно -1, потому что вы добавляете его в первый раз, когда используете его. Кроме того, вы не можете использовать «HAVING date_sequence», потому что это приводит к увеличению пользовательской переменной в два раза для каждой строки.

8 голосов
/ 04 февраля 2009

Заимствуя идею из этого ответа, вы можете создать таблицу с 0 по 9 и использовать ее для создания списка дат.

CREATE TABLE num (i int);
INSERT INTO num (i) VALUES (0), (1), (2), (3), (4), (5), (6), (7), (8), (9);

select adddate('2009-01-01', numlist.id) as `date` from
(SELECT n1.i + n10.i*10 + n100.i*100 AS id
   FROM num n1 cross join num as n10 cross join num as n100) as numlist
where adddate('2009-01-01', numlist.id) <= '2009-01-13';

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

3 голосов
/ 23 августа 2011

для доступа (или любого языка SQL)

  1. Создайте одну таблицу с двумя полями, мы назовем эту таблицу tempRunDates:
    - поля fromDate и toDate
    - Затем вставьте только 1 запись с начальной и конечной датами.

  2. Создать другую таблицу: Time_Day_Ref
    - Импортировать список дат (сделать список в Excel легко) в эту таблицу.
    - Имя поля в моем случае Greg_Dt, для григорианской даты
    - Я сделал свой список с 1 января 2009 по 1 января 2020 года.

  3. Запустить запрос:

    SELECT Time_Day_Ref.GREG_DT
    FROM tempRunDates, Time_Day_Ref
    WHERE Time_Day_Ref.greg_dt>=tempRunDates.fromDate And greg_dt<=tempRunDates.toDate;
    

Легко!

2 голосов
/ 25 мая 2011

Ну, как найти даты между двумя указанными датами в SQL-сервере, объясняется на http://ektaraval.blogspot.com/2010/09/writing-recursive-query-to-find-out-all.html

2 голосов
/ 25 января 2012
CREATE FUNCTION [dbo].[_DATES]
(
    @startDate DATETIME,
    @endDate DATETIME
)
RETURNS 
@DATES TABLE(
    DATE1 DATETIME
)
AS
BEGIN
    WHILE @startDate <= @endDate
    BEGIN 
        INSERT INTO @DATES (DATE1)
            SELECT @startDate   
    SELECT @startDate = DATEADD(d,1,@startDate) 
    END
RETURN
END
2 голосов
/ 04 февраля 2009

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

SELECT *
FROM (
    SELECT DATEADD(d, number - 1, '2009-01-01') AS dt
    FROM Numbers
    WHERE number BETWEEN 1 AND DATEDIFF(d, '2009-01-01', '2009-01-13') + 1
) AS DateRange
LEFT JOIN YourStuff
    ON DateRange.dt = YourStuff.DateColumn

Я видел варианты с табличными функциями и т. Д.

Вы также можете вести постоянный список дат. У нас есть это в нашем хранилище данных, а также список времени суток.

1 голос
/ 09 июня 2011

Мы использовали это в нашей системе HRMS, вы найдете ее полезной

SELECT CAST(DAYNAME(daydate) as CHAR) as dayname,daydate
    FROM
    (select CAST((date_add('20110101', interval H.i*100 + T.i*10 + U.i day) )as DATE) as daydate
      from erp_integers as H
    cross
      join erp_integers as T
    cross
      join erp_integers as U
     where date_add('20110101', interval H.i*100 + T.i*10 + U.i day ) <= '20110228'
    order
        by daydate ASC
        )Days
...