Как заполнить таблицу диапазоном дат?

Мне нужна таблица MySQL для хранения ВСЕХ ДАТ в период с 01.01.2011 по 31.12.2011. Я создал таблицу с одним именем столбца «_date» типа DATE.

С помощью какого запроса я могу заполнить таблицу всеми желаемыми датами (вместо того, чтобы вводить их вручную)?


person Pr0no    schedule 12.04.2012    source источник
comment
а вы используете только mysql? ничего похожего на php и т. д.?   -  person hjpotter92    schedule 13.04.2012
comment
И чтобы ответить на ваш вопрос, у меня есть доступ к PHP, но (я) действительно не знаю, как быстро вычислить все даты в пределах диапазона в PHP, и (ii) я думаю, MySQL мог бы сделать это намного эффективнее?   -  person Pr0no    schedule 13.04.2012
comment
@ hjpotter92: Если вы одинаково хорошо знаете и то, и другое, в PHP действительно не легче. И гораздо эффективнее делать это в MySQL.   -  person mwotton    schedule 22.10.2015


Ответы (9)


Попробуй это:

DROP PROCEDURE IF EXISTS filldates;
DELIMITER |
CREATE PROCEDURE filldates(dateStart DATE, dateEnd DATE)
BEGIN
  WHILE dateStart <= dateEnd DO
    INSERT INTO tablename (_date) VALUES (dateStart);
    SET dateStart = date_add(dateStart, INTERVAL 1 DAY);
  END WHILE;
END;
|
DELIMITER ;
CALL filldates('2011-01-01','2011-12-31');

Вот скрипт SQL, с которым можно поиграть: http://sqlfiddle.com/#!2/65d13/1

ИЗМЕНИТЬ (чтобы проверить, существует ли уже дата) по запросу Эндрю Фокс.

CREATE PROCEDURE filldates(dateStart DATE, dateEnd DATE)

BEGIN

DECLARE adate date;

    WHILE dateStart <= dateEnd DO

        SET adate = (SELECT mydate FROM MyDates WHERE mydate = dateStart);

        IF adate IS NULL THEN BEGIN

            INSERT INTO MyDates (mydate) VALUES (dateStart);

        END; END IF;

        SET dateStart = date_add(dateStart, INTERVAL 1 DAY);

    END WHILE;

END;//

Вот скрипт SQL, с которым можно поиграть: http://sqlfiddle.com/#!2/66f86/1

person Leniel Maccaferri    schedule 12.04.2012
comment
По крайней мере, не работал на моем сервере. alter routine command denied to user 'name'@'localhost' for routine 'name.filldates': DROP PROCEDURE IF EXISTS filldates - person hjpotter92; 13.04.2012
comment
На этот раз другая ошибка: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'DELIMITER | CREATE PROCEDURE filldates(dateStart DATE, dateEnd DATE) BEGIN WHI' at line 1: DELIMITER | CREATE PROCEDURE filldates(dateStart DATE, dateEnd DATE) BEGIN WHILE dateStart <= dateEnd - person hjpotter92; 13.04.2012
comment
У вас есть собственный сервер mysql? Если это так, попробуйте войти в систему как пользователь root или даже предоставить необходимые разрешения для своей текущей учетной записи. Пример: GRANT ALL ON mydb.* TO 'someuser'@'somehost'; Подробнее об этом здесь: dev. mysql.com/doc/refman/5.1/en/ - person Leniel Maccaferri; 13.04.2012
comment
как ни странно. похоже, работает на серверной машине. но не тогда, когда я использую удаленный доступ. - person hjpotter92; 13.04.2012
comment
Да ... это полностью вопрос разрешения. - person Leniel Maccaferri; 13.04.2012
comment
Еще не тестировал mysql (сделаю завтра), но, предполагая, что он работает нормально, принял ваш ответ. Спасибо! - person Pr0no; 13.04.2012
comment
+1 от меня, ваш код сработал лучше моих ожиданий. Спасибо!! Небольшой вопрос, как мне запустить проверку, чтобы увидеть, существует ли уже дата? Ваше здоровье! - person Andrew Fox; 15.11.2012
comment
@AndrewFox: Обновил ответ, чтобы вы узнали .. :) - person Leniel Maccaferri; 17.11.2012
comment
Хотел бы я купить тебе пива. Большое спасибо!! - person Andrew Fox; 17.11.2012
comment
Тем, у кого возникают синтаксические ошибки. Если вы подключитесь к своей базе данных через MySQL Workbench и попытаетесь запустить сценарий процедуры создания, вы получите синтаксические ошибки. Чтобы запустить этот сценарий, щелкните правой кнопкой мыши меню «Хранимые процедуры» и выберите «Создать хранимую процедуру», затем вставьте сценарий создания процедуры, удалите // в конце сценария и нажмите «Применить». У меня это сработало. - person Murilo Garcia; 05.08.2015
comment
Вана даст тебе двойной балл! - person Umair Hamid; 20.08.2018

Я не хотел, чтобы мой SQL-запрос требовал внешних зависимостей (наличия календарной таблицы, процедуры для заполнения временной таблицы датами и т. Д.). Первоначальная идея для этого запроса пришла от http://jeffgarretson.wordpress.com/2012/05/04/generating-a-range-of-dates-in-mysql/, который я немного оптимизировал для ясности и простоты использования.

SELECT (CURDATE() - INTERVAL c.number DAY) AS date
FROM (SELECT singles + tens + hundreds number FROM 
( SELECT 0 singles
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
) singles JOIN 
(SELECT 0 tens
UNION ALL SELECT  10 UNION ALL SELECT  20 UNION ALL SELECT  30
UNION ALL SELECT  40 UNION ALL SELECT  50 UNION ALL SELECT  60
UNION ALL SELECT  70 UNION ALL SELECT  80 UNION ALL SELECT  90
) tens  JOIN 
(SELECT 0 hundreds
UNION ALL SELECT  100 UNION ALL SELECT  200 UNION ALL SELECT  300
UNION ALL SELECT  400 UNION ALL SELECT  500 UNION ALL SELECT  600
UNION ALL SELECT  700 UNION ALL SELECT  800 UNION ALL SELECT  900
) hundreds
ORDER BY number DESC) c  
WHERE c.number BETWEEN 0 and 364

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

Если вам нужен больший набор чисел, легко добавить таблицу тысяч. Вам нужно только скопировать и вставить таблицу с сотнями и добавить от нуля до 9 чисел.

person IvanD    schedule 08.07.2014
comment
Это очень интересная концепция, немного медленная при обработке большого количества дат, но действительно неплохая. Возможно, быстрее, чем альтернативы. - person Felype; 24.11.2015
comment
Очень умный. Если кто-то спрашивает себя: c таблица, сгенерированная внутренним, выберите сумму между 0-9, 10-90, 100-900, которая представляет собой большой интервал с числами от 0 до 999 (всего 1000 чисел). Внешний выбор - это выбор списка чисел от 0 до 364 (т.е. 365 дней) и выбор всех дат от одного года до сегодняшнего дня (CURDATE). - person Diego Andrés Díaz Espinoza; 03.04.2017

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

select * from 
(select adddate('1970-01-01',t4*10000 + t3*1000 + t2*100 + t1*10 + t0) gen_date from
 (select 0 t0 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t0,
 (select 0 t1 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t1,
 (select 0 t2 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t2,
 (select 0 t3 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t3,
 (select 0 t4 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t4) v
where gen_date between '2017-01-01' and '2017-12-31'
person computingfreak    schedule 01.09.2017
comment
Это наиболее четкое и краткое решение, но его можно было бы сделать еще более кратким: используйте HAVING вместо WHERE, и вы можете избавиться от внешней оболочки SELECT. - person ttarchala; 21.11.2018
comment
Это чертовски круто - person vladiastudillo; 28.02.2019
comment
Мне это нравится, я работаю со студией данных Google, и это единственный способ получить даты одним выбором и без изменения базы данных, который я нашел за три дня поиска. - person janpeterka; 13.06.2019
comment
У меня есть БД, для которой у меня есть доступ только к представлениям, а не к самим таблицам, и это ИДЕАЛЬНО. - person Lynn Hoffman; 04.02.2021

Я обнаружил, что этот простой вариант работает:

DROP PROCEDURE IF EXISTS FillCalendar;
DROP TABLE IF EXISTS calendar;
CREATE TABLE IF NOT EXISTS calendar(calendar_date DATE NOT NULL PRIMARY KEY);

DELIMITER $$
    CREATE PROCEDURE FillCalendar(start_date DATE, end_date DATE)
    BEGIN
    DECLARE crt_date DATE;
    SET crt_date = start_date;
    WHILE crt_date <= end_date DO
        INSERT IGNORE INTO calendar VALUES(crt_date);
        SET crt_date = ADDDATE(crt_date, INTERVAL 1 DAY);
    END WHILE;
    END$$
DELIMITER ;

CALL FillCalendar('2013-01-01', '2013-01-03');
CALL FillCalendar('2013-01-01', '2013-01-07');
person Zon    schedule 25.10.2013
comment
если кому-то нужен пи: CALL FillCalendar ('2013-01-01', '2099-01-04'); выберите количество (*) из календаря; 31415 - person robert king; 03.03.2016

Недавно мне понадобилось создать calendar_date таблицу, как показано ниже:

CREATE TABLE `calendar_date` (
    `date`    DATE NOT NULL      -- A calendar date.
    , `day`   SMALLINT NOT NULL  -- The day of the year for the date, 1-366.
    , `month` TINYINT NOT NULL   -- The month number, 1-12.
    , `year`  SMALLINT NOT NULL  -- The year.
    , PRIMARY KEY (`id`));

Затем я заполнил его всеми возможными датами от January 1, 2001 до December 31, 2100 (оба включительно), используя запрос ниже:

INSERT INTO `calendar_date` (`date`
    , `day`
    , `month`
    , `year`)
SELECT
    DATE
    , INCREMENT + 1
    , MONTH(DATE)
    , YEAR(DATE)
FROM
    -- Generate all possible dates for every year from 2001 to 2100.
    (SELECT
        DATE_ADD(CONCAT(YEAR, '-01-01'), INTERVAL INCREMENT DAY) DATE
        , INCREMENT
    FROM
        (SELECT
            (UNITS + TENS + HUNDREDS) INCREMENT
        FROM
            (SELECT 0 UNITS UNION
            SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION
            SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION
            SELECT 7 UNION SELECT 8 UNION SELECT 9) UNITS
        CROSS JOIN
            (SELECT 0 TENS UNION
            SELECT 10 UNION SELECT 20 UNION SELECT 30 UNION
            SELECT 40 UNION SELECT 50 UNION SELECT 60 UNION
            SELECT 70 UNION SELECT 80 UNION SELECT 90) TENS
        CROSS JOIN
            (SELECT 0 HUNDREDS UNION
            SELECT 100 UNION SELECT 200 UNION SELECT 300 UNION
            SELECT 400 UNION SELECT 500 UNION SELECT 600 UNION
            SELECT 700 UNION SELECT 800 UNION SELECT 900) HUNDREDS
        ) INCREMENT
        -- For every year from 2001 to 2100, find the number of days in the year.
        , (SELECT
            YEAR
            , DAYOFYEAR(CONCAT(YEAR, '-12-31')) - DAYOFYEAR(CONCAT(YEAR, '-01-01')) + 1 DAYS
        FROM
            -- Generate years from 2001 to 2100.
            (SELECT
                (2000 + UNITS + TENS) YEAR
            FROM
                (SELECT 0 UNITS UNION
                SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION
                SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION
                SELECT 7 UNION SELECT 8 UNION SELECT 9) UNITS
            CROSS JOIN
                (SELECT 0 TENS UNION
                SELECT 10 UNION SELECT 20 UNION SELECT 30 UNION
                SELECT 40 UNION SELECT 50 UNION SELECT 60 UNION
                SELECT 70 UNION SELECT 80 UNION SELECT 90) TENS
            ) YEAR
        WHERE
            YEAR BETWEEN 2001 AND 2100
        ) YEAR
      WHERE
          INCREMENT BETWEEN 0 AND DAYS - 1
      ORDER BY
          YEAR
          , INCREMENT) DATE;

В моей локальной базе данных MySQL запрос INSERT занял всего несколько секунд. Надеюсь, это кому-то поможет.

person manish    schedule 31.01.2018

Если у вас есть таблица с достаточно большим непрерывным набором идентификаторов, вы можете использовать -

INSERT INTO tablename (_date)
SELECT '2011-01-01' + INTERVAL (id - 1) DAY
FROM some_table_with_lots_of_ids
WHERE id BETWEEN 1 AND 365

примечание: но имейте в виду, что это может вызвать у вас проблемы в високосные годы (366 дней)

person nnichols    schedule 12.04.2012

Спасибо IvanD. У меня есть лучшее решение, которое позволяет вам создать указанную календарную таблицу. Например, если я пытаюсь создать таблицу за 2014-04 год, это будет выглядеть так:

SELECT (CURDATE() - INTERVAL c.number DAY) AS DATE
FROM 
(
    SELECT singles + tens + hundreds number FROM 
    ( 
        SELECT 0 singles
        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
    ) singles JOIN 
    (
        SELECT 0 tens
        UNION ALL SELECT  10 UNION ALL SELECT  20 UNION ALL SELECT  30
        UNION ALL SELECT  40 UNION ALL SELECT  50 UNION ALL SELECT  60
        UNION ALL SELECT  70 UNION ALL SELECT  80 UNION ALL SELECT  90
    ) tens  JOIN 
    (
        SELECT 0 hundreds
        UNION ALL SELECT  100 UNION ALL SELECT  200 UNION ALL SELECT  300
        UNION ALL SELECT  400 UNION ALL SELECT  500 UNION ALL SELECT  600
        UNION ALL SELECT  700 UNION ALL SELECT  800 UNION ALL SELECT  900
    ) hundreds
    ORDER BY number DESC
) c  
WHERE c.number BETWEEN 
DAYOFYEAR(NOW()) - DAYOFYEAR('2014-04-01')-  DAY(LAST_DAY('2014-04-01')) +1
AND 
DAYOFYEAR(NOW()) - DAYOFYEAR('2014-04-01')
person Zhili Yin    schedule 19.11.2014
comment
Это не вернет table of 2014-04, но таблицу с уже прошедшими днями в выбранном месяце и в текущем году! - person TooroSan; 12.04.2016

Это может быть достигнуто в PHP с помощью простого цикла for. Есть несколько способов сделать это. Один из способов - поместить исходную дату в переменную и запустить цикл каждый день, добавив +1 день к каждому циклу, например, вы начнете 01.01.2011, а затем цикл будет добавьте 0 в первый раз, 1 день в следующий, затем 2 дня и т. д. к переменной $ i. Затем вы можете распечатать дни или добавить их в свою базу данных. В этом случае $ i будет представлять счетчик, где 0 является начальной точкой, ‹= 365 - это количество циклов, которые вы хотите пройти, которые равны или меньше количества дней, а $ i ++ добавляет +1 к переменной $ i. на каждой петле.

date ('Ymd' преобразует дату в гггг-мм-дд. Использование заглавной буквы Y дает вам полный 4-значный год, тогда как использование строчной буквы y дает вам последние 2 цифры года. Вы хотите сохранить его в этом чтобы добавить его в поле даты в mySQL.

strtotime ($ originalDate анализирует дату в метку времени Unix, а. "+". $ i. "day") в основном добавляет к дате значение $ i в днях.

Наконец, есть запрос mysqli. $ db представляет переменную соединения с базой данных, ее нужно будет изменить на любую переменную, которую вы установили для соединения. Далее следует собственно запрос. Просто замените слово table на имя вашей таблицы и дату перед VALUES на имя строки даты, и вы готовы к работе.

Ниже приводится пример:

<?php
for($i=0;$i<=365;$i++){ 
$originalDate = "01/01/2011";
$date = date('Y-m-d',strtotime($originalDate . "+".$i." day"));
mysqli_query($db, "INSERT INTO table (date)VALUES('$date')");
}

Другой способ добиться этого с помощью функции for - это включить даты strtotime непосредственно в действия for в отличие от переменных счетчика, что является еще более коротким фрагментом кода. Замените $ i = 0 (начальная точка счетчика) на начальную точку дня, затем на точку, меньшую или равную конечной дневной точке (количество циклов), затем, наконец, на ваш плюс +1 к первому утверждению, помещенному в переменная готова к использованию.

Наконец, преобразуйте дату в формат Y-m-d, готовый для размещения в базе данных, и запустите запрос.

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

Ниже приводится пример:

<?php
for ($startdate = strtotime("2011-01-01"); $startdate <= strtotime("2011-12-31"); $startdate = strtotime("+1 day", $startdate)) {
$date= date("Y-m-d", $startdate);
mysqli_query($db, "INSERT INTO tracking (date)VALUES('$date')");
}

Я, вероятно, сделал это более запутанным, чем есть на самом деле, но надеюсь, что это, по крайней мере, даст вам представление о том, как это работает.

person independent.guru    schedule 08.08.2016
comment
Хотя этот фрагмент кода может решить вопрос, включение объяснения действительно помогает улучшить качество вашего сообщения. Помните, что вы отвечаете на вопрос для читателей в будущем, и эти люди могут не знать причины вашего предложения кода. Также постарайтесь не загромождать свой код пояснительными комментариями, так как это снижает удобочитаемость как кода, так и пояснений! - person Goodbye StackExchange; 08.08.2016
comment
Извините, это был мой первый пост. Я отредактировал ответ, включив в него описание. Я также ответил другому человеку, который хотел добавить только с понедельника по пятницу в течение года в другом посте. Думаю, вы можете найти это в моем профиле ... - person independent.guru; 08.08.2016

Вдохновленный большим количеством пользователей IvanD, я пришел к следующему:

SELECT DATE_ADD('2015-10-21', INTERVAL c.number DAY)    AS DATE
FROM 
(
    SELECT singles + tens + hundreds+thousands number FROM 
    ( 
        SELECT 0 singles
        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
    ) singles JOIN 
    (
        SELECT 0 tens
        UNION ALL SELECT  10 UNION ALL SELECT  20 UNION ALL SELECT  30
        UNION ALL SELECT  40 UNION ALL SELECT  50 UNION ALL SELECT  60
        UNION ALL SELECT  70 UNION ALL SELECT  80 UNION ALL SELECT  90
    ) tens  JOIN 
    (
        SELECT 0 hundreds
        UNION ALL SELECT  100 UNION ALL SELECT  200 UNION ALL SELECT  300
        UNION ALL SELECT  400 UNION ALL SELECT  500 UNION ALL SELECT  600
        UNION ALL SELECT  700 UNION ALL SELECT  800 UNION ALL SELECT  900
    ) hundreds
     JOIN 
    (
        SELECT 0 thousands
        UNION ALL SELECT  1000 UNION ALL SELECT  2000 UNION ALL SELECT  3000
        UNION ALL SELECT  4000 UNION ALL SELECT  5000 UNION ALL SELECT  6000
        UNION ALL SELECT  7000 UNION ALL SELECT  8000 UNION ALL SELECT  9000
    ) thousands
    ORDER BY number DESC
) c  
WHERE c.number BETWEEN 
0 
AND
DATEDIFF('2016-10-08', '2015-10-21')
person ilgoom    schedule 19.07.2016
comment
@Leniel Macaferi, не могли бы вы помочь stackoverflow.com/questions / 45490612 / - person davidb; 03.08.2017