Используя стандартные функции 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
Изменить: похоже, я не понял. Я хочу СОЗДАТЬ этот список. У меня есть значения, хранящиеся в базе данных (по дате и времени), но я хочу, чтобы они были агрегированы в левом внешнем соединении со списком дат, как указано выше (я ожидаю null с правой стороны некоторых из этого соединения в течение нескольких дней и буду обрабатывать это ).
mysql
sql
date
gaps-and-islands
Гильгад
источник
источник
Ответы:
Я бы использовал эту хранимую процедуру для генерации необходимых интервалов в таблице temp с именем time_interval , затем 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.
источник
Для 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
источник
У нас была аналогичная проблема с отчетами BIRT, поскольку мы хотели отчитываться в те дни, когда не было данных. Поскольку для этих дат не было записей, самым простым решением для нас было создать простую таблицу, в которой хранятся все даты, и использовать ее для получения диапазонов или объединения для получения нулевых значений для этой даты.
У нас есть задание, которое выполняется каждый месяц, чтобы обеспечить заполнение таблицы на 5 лет вперед. Таблица создается таким образом:
create table all_dates ( dt date primary key );
Несомненно, есть волшебные хитрые способы сделать это с помощью разных СУБД », но мы всегда выбираем самое простое решение. Требования к хранилищу для таблицы минимальны, что делает запросы намного проще и переносимыми. Такое решение почти всегда лучше с точки зрения производительности, поскольку не требует вычислений для каждой строки данных.
Другой вариант (и мы использовали это раньше) - обеспечить наличие записи в таблице для каждой даты. Мы периодически просматривали таблицу и добавляли нулевые записи для дат и / или времени, которых не было. Это может быть не вариант в вашем случае, это зависит от сохраненных данных.
Если вы действительно думаете, что сохранить
all_dates
таблицу заполненной хранимую процедуру, которая вернет набор данных, содержащий эти даты. Это почти наверняка будет медленнее, поскольку вам придется вычислять диапазон каждый раз, когда он вызывается, а не просто извлекать предварительно рассчитанные данные из таблицы.Но, честно говоря, вы могли бы заполнить таблицу в течение 1000 лет без каких-либо серьезных проблем с хранением данных - 365000 16-байтовых (например) дат плюс индекс, дублирующий дату плюс 20% накладных расходов для безопасности, я бы приблизительно оценил в около 14M [365 000 * 16 * 2 * 1,2 = 14 016 000 байт]), крохотная таблица в схеме вещей.
источник
Вы можете использовать пользовательские переменные 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», потому что при этом пользовательская переменная увеличивается вдвое для каждой строки.
источник
Заимствуя идею из этого ответа, вы можете создать таблицу с цифрами от 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 дат. Если вам нужно сделать больше, вы можете добавить еще одно перекрестное соединение во внутренний запрос.
источник
date
; НЕ приводит к этой части.Для доступа (или любого языка 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-сервере, объясняется на 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
источник
Элегантное решение, использующее новую рекурсивную функциональность (общие табличные выражения) в 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'.
источник
Мы использовали это в нашей системе 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
Create a table -
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:
from mytable x, mytable y, mytable z
Итак, диапазон будет
10*10*10 =1000
и так далее.источник
Создайте хранимую процедуру, которая принимает два параметра a_begin и a_end. Создайте внутри нее временную таблицу с именем t, объявите переменную d, назначьте a_begin переменной d и запустите
WHILE
циклINSERT
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, но он вернул только одну строку.источник
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
источник
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 $$
эта процедура вставит все даты с начала года до настоящего момента, просто замените дни «начало» и «конец», и все готово!
источник
IF a=365 THEN
влияют високосные годы?150
в строке 9? Этот код является "стандартным ответом" без особых пояснений.Мне нужен был список всех месяцев между двумя датами для статистики. Две даты - это дата начала и окончания подписки. Итак, в списке показаны все месяцы и количество подписок в месяц.
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
источник
Вы можете использовать это
SELECT CAST(cal.date_list AS DATE) day_year FROM ( SELECT SUBDATE('2019-01-01', INTERVAL 1 YEAR) + INTERVAL xc DAY AS date_list FROM ( SELECT @xi:=@xi+1 as xc from (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) xc1, (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) xc2, (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) xc3, (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) xc4, (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) xc5, (SELECT @xi:=-1) xc0 ) xxc1 ) cal WHERE cal.date_list BETWEEN '2019-01-01' AND '2019-12-31' ORDER BY cal.date_list DESC;
источник
Я использую
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)
надеюсь, что это поможет многим.
источник