可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I need a MySQL table to hold ALL DATES between 2011-01-01 and 2011-12-31. I have created a table with one column names \"_date\", type DATE.
With what query can I populate the table with all the desired dates (instead of having to enter them by hand)?
回答1:
Try this:
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\');
Here\'s the SQL Fiddle to play with it: http://sqlfiddle.com/#!2/65d13/1
EDIT (to check if date already exists) as asked by Andrew Fox.
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;//
Here\'s the SQL Fiddle to play with it: http://sqlfiddle.com/#!2/66f86/1
回答2:
I did not want my SQL query to require external dependencies (needing to have a calendar table, procedure for populating a temporary table with dates, etc.) The original idea for this query came from http://jeffgarretson.wordpress.com/2012/05/04/generating-a-range-of-dates-in-mysql/ which I had slightly optimized for clarity and ease of use.
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
It is simple to optimize and scale this table for other uses. You can easily get rid of the tens and hundreds tables if you only need one week of data.
If you need a larger set of numbers, it is easy to add a thousands table. You only need to copy and paste the table with hundreds and add a zero to 9 numbers.
回答3:
I found this paste-and-go variant working:
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\');
回答4:
if you\'re in a situation like me where procedures are prohibited, and your sql user does not have permissions for insert, therefore insert not allowed, but you want to generate a list of dates in a specific period, say current year to do some aggregation, use this
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\'
回答5:
If you have a table with a large enough contiguous set of ids you could use -
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
note: but be aware that this might get you in trouble during leap-years (having 366 days)
回答6:
Thanks to IvanD.
I\'ve got a better solution which allowes you to create a specified calendar table.
For example, if I\'m trying to create a table of 2014-04, it looks like this:
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\')
回答7:
This can be achieved in PHP by using a simple for loop. There are a couple of ways to do it. One way would be to to place the original date in a variable and have the loop run through it for every day by adding +1 day on each loop, for instance, you\'ll start on 01/01/2011 and then the loop will add 0 the first time, 1 day the next, followed by 2 days so forth and so forth to the $i variable. You could then print out the days or add them to your database. In this case $i would represent the counter with 0 being the starting point, <=365 is how many loops you want to go through which is equal to or less than the number of days and $i++ adds +1 to the $i variable on each loop.
date(\'Y-m-d\' converts the date into yyyy-mm-dd. Using a capital Y gives you a full 4 digit year whereas using a lower case y will give you the last 2 digits of the year. You want to keep it in this order to add it in a date field in mySQL.
strtotime($originalDate parses the date into a Unix time stamp and the .\"+\".$i.\" day\") basically adds the value of $i in days to the date.
Finally there\'s the mysqli query. $db represents the database connection variable, this will need to be changed to whatever variable you have set for the connection. This is followed by the actual query. Just exchange the word table for your table name and the date before VALUES to you date row name and you\'re ready to go.
The following is an example:
<?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\')\");
}
Another way to achieve this using the for function would be to include the strtotime dates directly in the for actions as an oppose to the counter variables, which is an even shorter piece of code. Replace $i=0 (the starting counter point) with the starting day point, follow that with the less than or equal to the end day point (the number of loops) then finally with your plus +1 to the first statement placed into a variable ready for use.
Finally, convert the date into Y-m-d format ready to be placed into the database and run the query.
Again, as with the first example, this can be printed or placed directly into your database.
The following is an example:
<?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\')\");
}
I\'ve probably made it sound more confusing than it is, but hope it will at least give you an idea on how it works.
回答8:
I recently had a need to create a calendar_date
table as below:
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`));
I then populated it with all possible dates between January 1, 2001
and December 31, 2100
(both inclusive) using the query below:
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;
On my local MySQL database, the INSERT
query took just a few seconds. Hope this helps someone.
回答9:
Inspired by IvanD`s great number join i come to this:
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\')
回答10:
INSERT INTO my_dates (\\`_date\\`) SELECT DATE_ADD(\'2011-01-01\', INTERVAL @_tmp:=@_tmp+1 day) \\`_date\\`
FROM (SELECT @_tmp:=-1 d 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) a /\\*10^1\\*/
JOIN (SELECT 0 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) b /\\*10^2\\*/
JOIN (SELECT 0 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) c /\\*10^3\\*/
WHERE @_tmp+1 BETWEEN 0 AND DATEDIFF(\'2011-12-31\', \'2011-01-01\');