Получить список дат между двумя датами
используя стандартные функции 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
Edit: похоже, я не был ясен. Я хочу создать этот список. У меня есть значения, хранящиеся в базе данных (по datetime), но они должны быть объединены на левом внешнем соединении со списком дат, как указано выше (я ожидаю null с правой стороны некоторые из них присоединяются в течение нескольких дней и будут обрабатывать это).
18 ответов:
Я бы использовал эту хранимую процедуру для генерации интервалов, которые вам нужны, в временную таблицу с именем time_intervals, затем объедините и агрегируйте таблицу данных с 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.
для MSSQL вы можете использовать это. Это очень быстро.
вы можете обернуть это в табличную функцию или сохранить proc и проанализировать даты начала и окончания в качестве переменных.
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
вы можете использовать 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, потому что вы добавляете его при первом использовании. Кроме того, вы не можете использовать "наличие date_sequence", потому что это делает пользовательскую переменную увеличенной дважды для каждой строки.
у нас была аналогичная проблема с отчетами BIRT в том, что мы хотели сообщить о тех днях, которые не имели данных. Поскольку для этих дат не было записей, самым простым решением для нас было создать простую таблицу, в которой хранятся все даты, и использовать ее для получения диапазонов или объединения, чтобы получить нулевые значения для этой даты.
у нас есть работа, которая выполняется каждый месяц, чтобы гарантировать, что таблица заполняется на 5 лет в будущее. Таблица создается таким образом:
create table all_dates ( dt date primary key );
без сомнения, есть волшебные хитрые способы сделать это с разными СУБД, но мы всегда выбираем самое простое решение. Требования к хранилищу для таблицы минимальны, и это делает запросы намного проще и портативнее. Такое решение почти всегда лучше с точки зрения производительности, так как оно не требует вычисления по строкам данных.
другой вариант (и мы использовали это раньше), чтобы обеспечить запись в таблице для каждой даты. Мы периодически подметали стол и добавлены нулевые записи для дат и / или времен, которые не существовали. Это может быть не вариант в вашем случае, это зависит от сохраненных данных.
Если вы действительно думаю, что это хлопот, чтобы держать
all_dates
таблица заполнена, хранимая процедура-это путь, который вернет набор данных, содержащий эти даты. Это почти наверняка будет медленнее, так как вам нужно вычислять диапазон каждый раз, когда он вызывается, а не просто вытаскивать предварительно вычисленные данные из a стол.но, честно говоря, вы можете заполнить таблицу в течение 1000 лет без каких - либо серьезных проблем с хранением данных-365,000 16-байтовых (например) дат плюс индекс, дублирующий дату плюс 20% накладных расходов для безопасности, я бы примерно оценил около 14M [365,000 * 16 * 2 * 1.2 = 14,016,000 bytes]), крохотная таблица в схеме вещей.
заимствование идеи из этой ответ, вы можете настроить таблицу с 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 дат. Если вам нужно увеличить размер, вы можете добавить еще одно перекрестное соединение во внутренний запрос.
для доступа (или любого языка SQL)
создать одну таблицу, которая имеет 2 поля, мы будем называть эту таблицу
tempRunDates
:
--МесторожденияfromDate
иtoDate
-- Затем вставьте только 1 запись, которая имеет дату начала и дату окончания.создать еще одну таблицу:
Time_Day_Ref
-- Импорт списка дат (сделать список в excel легко) в эту таблицу.
-- Имя поля в моем случае -Greg_Dt
, на дату по Григорианскому календарю
-- Я сделал мой список с 1 января 2009 года по 1 января 2020 года.выполнить запрос:
SELECT Time_Day_Ref.GREG_DT FROM tempRunDates, Time_Day_Ref WHERE Time_Day_Ref.greg_dt>=tempRunDates.fromDate And greg_dt<=tempRunDates.toDate;
легко!
обычно можно было бы использовать вспомогательную таблицу чисел, которую вы обычно держите вокруг только для этой цели с некоторыми вариациями на этот счет:
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
Я видел вариации с табличными функциями и т. д.
вы также можете сохранить постоянный список дат. У нас есть это в нашем хранилище данных, а также список времени суток.
Ну как найти даты между двумя заданными датами в SQL server объясните дальше http://ektaraval.blogspot.com/2010/09/writing-recursive-query-to-find-out-all.html
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
мы использовали это в нашей системе 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
это решение работает с MySQL 5.0
Создайте таблицу -mytable
.
Схема не имеет значения. Что имеет значение, так это количество строк в нем.
Таким образом, вы можете сохранить только один столбец типа INT с 10 строками, значения - от 1 до 10.SQL:
set @tempDate=date('2011-07-01') - interval 1 day; select date(@tempDate := (date(@tempDate) + interval 1 day)) as theDate from mytable x,mytable y group by theDate having theDate <= '2011-07-31';
ограничение: Максимальное количество дат, возвращенных вышеуказанным запросом, будет
(rows in mytable)*(rows in mytable) = 10*10 = 100.
вы можете увеличить этот диапазон, изменив часть формы в sql:
от таблица mytable х,моя_таблица г, моя_таблица з
Итак, диапазон будет10*10*10 =1000
и так далее.
создайте хранимую процедуру, которая принимает два параметра a_begin и a_end. Создайте в нем временную таблицу с именем t, объявите переменную d, назначьте a_begin d и запустите a
WHILE
циклINSERT
ing d в t и вызовADDDATE
функция для увеличения значения d. наконецSELECT * FROM t
.
Я бы использовал что-то похожее на это:
DECLARE @DATEFROM AS DATETIME DECLARE @DATETO AS DATETIME DECLARE @HOLDER TABLE(DATE DATETIME) SET @DATEFROM = '2010-08-10' SET @DATETO = '2010-09-11' INSERT INTO @HOLDER (DATE) VALUES (@DATEFROM) WHILE @DATEFROM < @DATETO BEGIN SELECT @DATEFROM = DATEADD(D, 1, @DATEFROM) INSERT INTO @HOLDER (DATE) VALUES (@DATEFROM) END SELECT DATE FROM @HOLDER
тут
@HOLDER
переменная таблица содержит все даты, увеличенные на день между этими двумя датами, готовые присоединиться к вашему сердцу.
я боролся с этим долгое время. Поскольку это первый удар по Google, когда я искал решение, позвольте мне опубликовать, где я получил до сих пор.
SET @d := '2011-09-01'; SELECT @d AS d, cast( @d := DATE_ADD( @d , INTERVAL 1 DAY ) AS DATE ) AS new_d FROM [yourTable] WHERE @d <= '2012-05-01';
заменить
[yourTable]
с таблицей из вашей базы данных. Хитрость заключается в том, что количество строк в выбранной таблице должно быть >= количество дат, которые вы хотите вернуть. Я попытался использовать заполнитель таблицы DUAL, но он вернет только одну строку.
DELIMITER $$ CREATE PROCEDURE popula_calendario_controle() BEGIN DECLARE a INT Default 0; DECLARE first_day_of_year DATE; set first_day_of_year = CONCAT(DATE_FORMAT(curdate(),'%Y'),'-01-01'); one_by_one: LOOP IF dayofweek(adddate(first_day_of_year,a)) <> 1 THEN INSERT INTO calendario.controle VALUES(null,150,adddate(first_day_of_year,a),adddate(first_day_of_year,a),1); END IF; SET a=a+1; IF a=365 THEN LEAVE one_by_one; END IF; END LOOP one_by_one; END $$
эта процедура вставит все даты с начала года до сих пор, просто замените дни "начала" и "конца", и вы готовы к работе!
Мне нужен был список со всеми месяцами между 2 датами для статистики. 2 даты-это дата начала и окончания подписки. таким образом, в списке отображаются все месяцы и количество подписок в месяц.
MYSQL
CREATE PROCEDURE `get_amount_subscription_per_month`() BEGIN -- Select the ultimate start and enddate from subscribers select @startdate := min(DATE_FORMAT(a.startdate, "%Y-%m-01")), @enddate := max(DATE_FORMAT(a.enddate, "%Y-%m-01")) + interval 1 MONTH from subscription a; -- Tmp table with all months (dates), you can always format them with DATE_FORMAT) DROP TABLE IF EXISTS tmp_months; create temporary table tmp_months ( year_month date, PRIMARY KEY (year_month) ); set @tempDate=@startdate; #- interval 1 MONTH; -- Insert every month in tmp table WHILE @tempDate <= @enddate DO insert into tmp_months (year_month) values (@tempDate); set @tempDate = (date(@tempDate) + interval 1 MONTH); END WHILE; -- All months select year_month from tmp_months; -- If you want the amount of subscription per month else leave it out select mnd.year_month, sum(subscription.amount) as subscription_amount from tmp_months mnd LEFT JOIN subscription ON mnd.year_month >= DATE_FORMAT(subscription.startdate, "%Y-%m-01") and mnd.year_month <= DATE_FORMAT(subscription.enddate, "%Y-%m-01") GROUP BY mnd.year_month; END
Я использую
Server version: 5.7.11-log MySQL Community Server (GPL)
теперь мы решим это простым способом.
Я создал таблицу с именем "datetable"
mysql> describe datetable; +---------+---------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +---------+---------+------+-----+---------+-------+ | colid | int(11) | NO | PRI | NULL | | | coldate | date | YES | | NULL | | +---------+---------+------+-----+---------+-------+ 2 rows in set (0.00 sec)
теперь мы увидим вставленные записи внутри.
mysql> select * from datetable; +-------+------------+ | colid | coldate | +-------+------------+ | 101 | 2015-01-01 | | 102 | 2015-05-01 | | 103 | 2016-01-01 | +-------+------------+ 3 rows in set (0.00 sec)
и здесь наш запрос, чтобы получить записи в течение двух дат, а не эти даты.
mysql> select * from datetable where coldate > '2015-01-01' and coldate < '2016-01-01'; +-------+------------+ | colid | coldate | +-------+------------+ | 102 | 2015-05-01 | +-------+------------+ 1 row in set (0.00 sec)
надеюсь, что это поможет многим из них.
select * from table_name where col_Date between '2011/02/25' AND DATEADD(s,-1,DATEADD(d,1,'2011/02/27'))
здесь сначала добавьте день к текущей дате окончания, это будет 2011-02-28 00:00:00, затем вычитаете одну секунду, чтобы сделать дату окончания 2011-02-27 23:59: 59. Таким образом, вы можете получить все даты между заданными интервалами.
выход:
2011/02/25
2011/02/26
2011/02/27