Получить список дат между двумя датами
Использование стандартных функций 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
Изменить: Кажется, я не был ясен. Я хочу GENERATE этот список. У меня есть значения, хранящиеся в базе данных (по дате), но хочу, чтобы они были объединены в левом внешнем соединении в список дат, как указано выше (я ожидаю, что null с правой стороны некоторого этого соединения в течение нескольких дней будет обрабатываться).
Ответы
Ответ 1
Я бы использовал эту хранимую процедуру для создания необходимых вам интервалов в таблице temp с именем 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.
Ответ 2
Для 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
Ответ 3
У нас была аналогичная проблема с отчетами BIRT, в которых мы хотели сообщить о тех днях, у которых не было данных. Поскольку для этих дат не было записей, самым простым решением для нас было создание простой таблицы, которая хранила все даты и использовала бы это для получения диапазонов или объединения для получения нулевых значений для этой даты.
У нас есть работа, которая выполняется каждый месяц, чтобы гарантировать, что таблица будет заполнена на 5 лет в будущем. Таблица создается таким образом:
create table all_dates (
dt date primary key
);
Без сомнения, есть волшебные сложные способы сделать это с помощью разных СУБД, но мы всегда выбираем простейшее решение. Требования к хранению для таблицы минимальны и делают запросы более простыми и переносимыми. Такое решение почти всегда лучше с точки зрения производительности, поскольку для данных не требуется вычислений в каждой строке.
Другой вариант (и мы использовали это раньше) - это обеспечить запись в таблице для каждой даты. Мы периодически просматривали таблицу и добавляли нулевые записи для дат и/или времени, которых не было. Это может быть не вариант в вашем случае, это зависит от сохраненных данных.
Если вы действительно считаете, что держать таблицу all_dates
заселенной, хранимой процедурой является способ перехода, который вернет набор данных, содержащий эти даты. Это почти наверняка будет медленнее, так как вам нужно рассчитать диапазон каждый раз, когда он вызывается, а не просто вытягивать предварительно рассчитанные данные из таблицы.
Но, честно говоря, вы можете заполнить таблицу в течение 1000 лет без серьезных проблем с хранением данных - 365 000 16-байтных (например) дат плюс индекс, дублирующий дату плюс 20% накладных расходов для безопасности, я бы примерно на 14М [365 000 * 16 * 2 * 1,2 = 14,016,000 байт]), минимальная таблица в схеме вещей.
Ответ 4
Вы можете использовать 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", потому что это увеличивает пользовательскую переменную в два раза для каждой строки.
Ответ 5
Заимствуя идею из этого, вы можете настроить таблицу с 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 дат. Если вам нужно увеличить размер, вы можете добавить другое кросс-соединение во внутренний запрос.
Ответ 6
Для доступа (или любого языка SQL)
-
Создайте одну таблицу с двумя полями, мы будем называть эту таблицу tempRunDates
:
--Fields 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;
Легко!
Ответ 7
Обычно можно использовать таблицу вспомогательных номеров, которую вы обычно используете для этой цели с некоторыми вариациями:
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
Я видел варианты с табличными функциями и т.д.
Вы также можете сохранить постоянный список дат. У нас есть это в нашем хранилище данных, а также список времени суток.
Ответ 8
Хорошо, как найти даты между двумя заданными датами в SQL-сервере, объясняется на http://ektaraval.blogspot.com/2010/09/writing-recursive-query-to-find-out-all.html
Ответ 9
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
Ответ 10
Мы использовали это в нашей системе 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
Ответ 11
Это решение работает с 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 x, mytable y, mytable z
Итак, диапазон будет 10*10*10 =1000
и т.д.
Ответ 12
Создайте хранимую процедуру, которая принимает два параметра: a_begin и a_end.
Создайте временную таблицу внутри нее с именем t, объявите переменную d, назначьте a_begin для d и запустите цикл WHILE
INSERT
ing d в t и вызовите функцию ADDDATE
, чтобы увеличить значение d. Наконец SELECT * FROM t
.
Ответ 13
Я бы использовал нечто похожее на это:
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
Variable содержит все даты, увеличивающиеся по дням между этими двумя датами, готовые присоединиться к вашему содержанию.
Ответ 14
Я долгое время боролся с этим. Поскольку это первый хит в 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 для таблицы, но она вернет только одну строку.
Ответ 15
select * from table_name where col_Date between '2011/02/25' AND DATEADD(s,-1,DATEADD(d,1,'2011/02/27'))
Здесь сначала добавьте день в текущую endDate, это будет 2011-02-28 00:00:00, после чего вы вычтите одну секунду, чтобы сделать конечную дату. 2011-02-27 23:59:59. Делая это, вы можете получить все даты между заданными интервалами.
Выход:
2011/02/25
2011/02/26
2011/02/27
Ответ 16
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 $$
эта процедура будет вставлять все даты с начала года до настоящего времени, просто подставлять дни "старта" и "конца", и вы готовы к работе!
Ответ 17
Мне нужен список со всеми месяцами между двумя датами статистики. 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 @[email protected]; #- 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
Ответ 18
Я использую 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)
надеюсь, это поможет многим.
Ответ 19
Элегантное решение с использованием новой рекурсивной (Common Table Expressions) функциональности в MariaDB> = 10.3 и MySQL> = 8.0.
WITH RECURSIVE t as (
select '2019-01-01' as dt
UNION
SELECT DATE_ADD(t.dt, INTERVAL 1 DAY) FROM t WHERE DATE_ADD(t.dt, INTERVAL 1 DAY) <= '2019-04-30'
)
select * FROM t;
Выше приведена таблица дат между "2019-01-01" и "2019-04-30".