генерувати дні з діапазону дат


135

Я хотів би запустити такий запит

select ... as days where `date` is between '2010-01-20' and '2010-01-24'

І повернути такі дані, як:

днів
----------
2010-01-20
2010-01-21
2010-01-22
2010-01-23
2010-01-24

10
До цього питання немає жодної іншої проблеми. Вищезазначене питання - проблема, освоєння курсів SQL.
Pentium10

Вам просто потрібен масив дат на основі вибраного діапазону дат?
Дерек Адаїр

1
Я думаю про використання, щоб знайти вам проблему ... Якщо ви отримаєте завдання заповнити якісь пропущені записи у вашій таблиці. І вам доведеться вести запит щодня, я думаю про щось на кшталтinsert into table select ... as days date between '' and ''
Pentium10,

13
Прикладом його використання може бути генерування статистики та включення рядка для дат, про які ви не маєте даних. Якщо ви робите якусь групову групу, це може бути набагато швидше насправді генерувати всю інформацію в SQL і додавати її в будь-який потрібний вам формат, замість того, щоб скидати свої дані як є на вашу мову, і почніть циклічно додавати свої порожнечі
Нанна

1
@Nanne саме тому я врятував це питання. Мені потрібно сказати, щоб НАЛІЧНО ПРИЄДНАТИсь до даних, які можуть не існувати певних дат.
Джош Діель

Відповіді:


318

Це рішення не використовує циклів, процедур або тимчасових таблиць . Підзапрос генерує дати за останні 10 000 днів, і його можна продовжити, щоб зайти вперед або вперед, як ви хочете.

select a.Date 
from (
    select curdate() - INTERVAL (a.a + (10 * b.a) + (100 * c.a) + (1000 * d.a) ) DAY as Date
    from (select 0 as a 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) as a
    cross join (select 0 as a 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) as b
    cross join (select 0 as a 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) as c
    cross join (select 0 as a 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) as d
) a
where a.Date between '2010-01-20' and '2010-01-24' 

Вихід:

Date
----------
2010-01-24
2010-01-23
2010-01-22
2010-01-21
2010-01-20

Примітки щодо продуктивності

Тестування його тут , виступ на диво добре: вище запит займає 0,0009 сек.

Якщо ми розширимо підзапит, щоб генерувати бл. 100 000 чисел (і, таким чином, близько 274 років дат), воно працює за 0,0458 сек.

До речі, це дуже портативна методика, яка працює з більшістю баз даних з незначними налаштуваннями.

Приклад SQL Fiddle, що повертається 1000 днів


6
Ви побачите кращу ефективність, якщо перейдете UNIONна UNION ALLце - витрачаєте час на перевірку дублікатів, щоб видалити такі, які не існують. Однак це складний ІМО - якщо ви збираєтеся створювати набір результатів за допомогою UNION, чому б не просто вказати дату і зробити це з нею?
OMG Ponies

7
чому б не просто вказати дату і зробити це з нею - адже вищевказаний метод дозволяє створювати довільно великі набори чисел (і дат), що не потребують створення таблиці, що було б болісно жорстким кодом у тому вигляді, який ви пропонуєте. Очевидно, що на 5 днів це надмірно; але навіть тоді, якщо ви приєднуєтесь до таблиці, де ви не знаєте дат заздалегідь, а лише потенційні значення min та max, це має сенс.
RedFilter

2
Боляче просто використовувати функцію DATETIME замість створеної вами заяви UNION? Це усуває будь-яку потребу в логіці, яку вам довелося додати . Отже, ви занадто ускладнили запит. Заява UNION, в будь-якому випадку, не є масштабованою - вказуючи дату чи номер, хто хоче оновити її, щоб розмістити 20 або 30 дати?
OMG Ponies

23
Дійсно приємно бачити відповідь на запитання, а не нескінченні коментарі, як цього не можна чи не слід робити. Більшість речей можна зробити, і "слід" має значення лише в контексті, який відрізняється для кожного. Ця відповідь мені допомогла, хоча я добре знаю, що в більшості ситуацій є кращі способи.
Джо

7
Ті з вас, хто не може змусити цей запит працювати: Будь ласка, ляпніть себе обличчям, а потім перечитайте коментар ОП щодо цього запиту, що генерує 1000 дат. Оскільки 2010 року було більше 1000 днів тому, вам потрібно буде відповідно скоригувати запит.
Ноель Барон

32

Ось ще одна версія з використанням переглядів:

CREATE VIEW digits AS
  SELECT 0 AS digit 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;

CREATE VIEW numbers AS
  SELECT
    ones.digit + tens.digit * 10 + hundreds.digit * 100 + thousands.digit * 1000 AS number
  FROM
    digits as ones,
    digits as tens,
    digits as hundreds,
    digits as thousands;

CREATE VIEW dates AS
  SELECT
    SUBDATE(CURRENT_DATE(), number) AS date
  FROM
    numbers;

І тоді ви можете просто зробити (подивіться, як це елегантно?):

SELECT
  date
FROM
  dates
WHERE
  date BETWEEN '2010-01-20' AND '2010-01-24'
ORDER BY
  date

Оновлення

Варто зазначити, що ви зможете генерувати минулі дати, починаючи з поточної дати . Якщо ви хочете генерувати будь-який діапазон дат (минуле, майбутнє та між ними), вам доведеться замість цього перегляду використовувати:

CREATE VIEW dates AS
  SELECT
    SUBDATE(CURRENT_DATE(), number) AS date
  FROM
    numbers
  UNION ALL
  SELECT
    ADDDATE(CURRENT_DATE(), number + 1) AS date
  FROM
    numbers;

1
Це працює не у всіх випадках. ВИБІРІТЬ ДАТА З дат, де дата МЕЖУ '2014-12-01' ТА '2014-12-28' ЗАКАЗУЙТЕ ДАТА
васант

3
Хороший дзвінок @ user927258. Це тому, що перший datesзгаданий перегляд обчислює дати, починаючи з поточної дати, тому ви не зможете отримати встановлені дати в майбутньому. Відповідь від @RedFilter страждає від тієї ж вади дизайну. Я все ж додав вирішення у своїй відповіді.
Стефан

Використання деяких поглядів безумовно спрощує запити та робить їх багаторазовими. Хоча вони по суті роблять те саме, всі ці UNIONпропозиції виглядають дивно в одному операторі SQL.
Стюарт

24

Прийнята відповідь не працювала для PostgreSQL (помилка синтаксису на "a" або біля неї).

Те, як ви це робите в PostgreSQL, використовуючи generate_seriesфункцію, тобто:

SELECT day::date
FROM generate_series('2010-01-20', '2010-01-24', INTERVAL '1 day') day;

    day
------------
 2010-01-20
 2010-01-21
 2010-01-22
 2010-01-23
 2010-01-24
(5 rows)

14

Використовуючи рекурсивний загальний вираз таблиці (CTE), ви можете створити список дат, а потім вибрати з нього. Очевидно, що ви зазвичай не хотіли б створювати три мільйони дат, тому це просто ілюструє можливості. Ви можете просто обмежити діапазон дат у межах CTE і пропустити пункт де з оператора select за допомогою CTE.

with [dates] as (
    select convert(datetime, '1753-01-01') as [date] --start
    union all
    select dateadd(day, 1, [date])
    from [dates]
    where [date] < '9999-12-31' --end
)
select [date]
from [dates]
where [date] between '2013-01-01' and '2013-12-31'
option (maxrecursion 0)

У Microsoft SQL Server 2005, генерування списку CTE усіх можливих дат зайняло 1:08. Генерація ста років зайняла менше секунди.


7

Запит MSSQL

select datetable.Date 
from (
    select DATEADD(day,-(a.a + (10 * b.a) + (100 * c.a)),getdate()) AS Date
    from (select 0 as a 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) as a

    cross join (select 0 as a 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) as b

    cross join (select 0 as a 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) as c
) datetable
where datetable.Date between '2014-01-20' and '2014-01-24' 
order by datetable.Date DESC

Вихідні дані

Date
-----
2014-01-23 12:35:25.250
2014-01-22 12:35:25.250
2014-01-21 12:35:25.250
2014-01-20 12:35:25.250

2
Якби я лише прокрутився трохи більше ... зітхну. У будь-якому випадку, дякую. Я додав CAST (<вираз> ЯК ДАТИ), щоб видалити час на мою версію. Також використовується, коли a.Date між GETDATE () - 365 І GETDATE () ... якщо ви запустите свій запит сьогодні, він не дасть рядків, якщо ви не помітите дати в WHERE = P
Ricardo C

4

Старе шкільне рішення для цього без циклу / курсору полягає у створенні NUMBERSтаблиці, у якій є один стовпець Integer зі значеннями, починаючи з 1.

CREATE TABLE  `example`.`numbers` (
  `id` int(10) unsigned NOT NULL auto_increment,
  PRIMARY KEY  (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

Потрібно заповнити таблицю достатньою кількістю записів, щоб задовольнити ваші потреби:

INSERT INTO NUMBERS (id) VALUES (NULL);

Отримавши NUMBERSтаблицю, ви можете використовувати:

SELECT x.start_date + INTERVAL n.id-1 DAY
  FROM NUMBERS n
  JOIN (SELECT STR_TO_DATE('2010-01-20', '%Y-%m-%d') AS start_date 
          FROM DUAL) x
 WHERE x.start_date + INTERVAL n.id-1 DAY <= '2010-01-24'

Абсолютним низькотехнологічним рішенням було б:

SELECT STR_TO_DATE('2010-01-20', '%Y-%m-%d')
 FROM DUAL
UNION ALL
SELECT STR_TO_DATE('2010-01-21', '%Y-%m-%d')
 FROM DUAL
UNION ALL
SELECT STR_TO_DATE('2010-01-22', '%Y-%m-%d')
 FROM DUAL
UNION ALL
SELECT STR_TO_DATE('2010-01-23', '%Y-%m-%d')
 FROM DUAL
UNION ALL
SELECT STR_TO_DATE('2010-01-24', '%Y-%m-%d')
 FROM DUAL

Для чого ти б це використав?


Генерувати списки дат чи чисел для того, щоб НАТИСНІТЬ ПРИЄДНАТИсь до. Ви б зробили це для того, щоб побачити, де є прогалини в даних, тому що ви ВЛІТЛО приєднуєтесь до списку послідовних даних - нульові значення дадуть зрозуміти, де існують прогалини.


1
DUALТаблиця підтримується Oracle і MySQL для використання в якості дублера в таблиці в FROMп. Він не існує, вибравши значення з нього, повернеться незалежно від значення. Ідея полягала в тому, щоб створити режим очікування, оскільки для запиту SELECT потрібен FROMпункт із зазначенням хоча б однієї таблиці.
OMG Ponies

1
+1 - фактично створити таблицю постійних чисел замість того, щоб RDBMS створював її кожного разу, коли вам потрібен запит. Допоміжні столи не зла, люди!
Шматочки бекону

4

Для Access 2010 - потрібно кілька кроків; Я дотримувався тієї ж схеми, що була розміщена вище, але думав, що можу допомогти комусь у Access. Для мене це чудово працювало, мені не довелося вести насіннєвий стіл фініків.

Створіть таблицю під назвою DUAL (подібно до того, як працює таблиця Oracle DUAL)

  • Ідентифікатор (AutoNumber)
  • DummyColumn (текст)
  • Додайте значення одного рядка (1, "DummyRow")

Створіть запит під назвою "ZeroThru9Q"; ввести вручну такий синтаксис:

SELECT 0 AS a
FROM dual
UNION ALL
SELECT 1
FROM dual
UNION ALL
SELECT 2
FROM dual
UNION ALL
SELECT 3
FROM dual
UNION ALL
SELECT 4
FROM dual
UNION ALL
SELECT 5
FROM dual
UNION ALL
SELECT 6
FROM dual
UNION ALL
SELECT 7
FROM dual
UNION ALL
SELECT 8
FROM dual
UNION ALL
SELECT 9
FROM dual;

Створіть запит під назвою "TodayMinus1KQ" (для дат до сьогодні); ввести вручну такий синтаксис:

SELECT date() - (a.a + (10 * b.a) + (100 * c.a)) AS MyDate
FROM
  (SELECT *
   FROM ZeroThru9Q) AS a,

  (SELECT *
   FROM ZeroThru9Q) AS b,

  (SELECT *
   FROM ZeroThru9Q) AS c

Створіть запит під назвою "TodayPlus1KQ" (для дат після сьогодні); ввести вручну такий синтаксис:

SELECT date() + (a.a + (10 * b.a) + (100 * c.a)) AS MyDate
FROM
  (SELECT *
   FROM ZeroThru9Q) AS a,

  (SELECT *
   FROM ZeroThru9Q) AS b,

  (SELECT *
   FROM ZeroThru9Q) AS c;

Створіть запит на об'єднання під назвою "TodayPlusMinus1KQ" (для дат +/- 1000 днів):

SELECT MyDate
FROM TodayMinus1KQ
UNION
SELECT MyDate
FROM TodayPlus1KQ;

Тепер ви можете використовувати запит:

SELECT MyDate
FROM TodayPlusMinus1KQ
WHERE MyDate BETWEEN #05/01/2014# and #05/30/2014#

3

Порядок + тимчасова таблиця:

DELIMITER $$

CREATE DEFINER=`root`@`localhost` PROCEDURE `days`(IN dateStart DATE, IN dateEnd DATE)
BEGIN

    CREATE TEMPORARY TABLE IF NOT EXISTS date_range (day DATE);

    WHILE dateStart <= dateEnd DO
      INSERT INTO date_range VALUES (dateStart);
      SET dateStart = DATE_ADD(dateStart, INTERVAL 1 DAY);
    END WHILE;

    SELECT * FROM date_range;
    DROP TEMPORARY TABLE IF EXISTS date_range;

END

3

thx Pentium10 - ти змусив мене приєднатись до stackoverflow :) - це моє перенесення до msaccess - думаю, воно буде працювати в будь-якій версії:

SELECT date_value
FROM (SELECT a.espr1+(10*b.espr1)+(100*c.espr1) AS integer_value,
dateadd("d",integer_value,dateserial([start_year], [start_month], [start_day])) as date_value
FROM (select * from 
    (
    select top 1 "0" as espr1 from MSysObjects
    union all
    select top 1 "1" as espr2 from MSysObjects
    union all
    select top 1 "2" as espr3 from MSysObjects
    union all
    select top 1 "3" as espr4 from MSysObjects
    union all
    select top 1 "4" as espr5 from MSysObjects
    union all
    select top 1 "5" as espr6 from MSysObjects
    union all
    select top 1 "6" as espr7 from MSysObjects
    union all
    select top 1 "7" as espr8 from MSysObjects
    union all
    select top 1 "8" as espr9 from MSysObjects
    union all
    select top 1 "9" as espr9 from MSysObjects
    ) as a,
    (
    select top 1 "0" as espr1 from MSysObjects
    union all
    select top 1 "1" as espr2 from MSysObjects
    union all
    select top 1 "2" as espr3 from MSysObjects
    union all
    select top 1 "3" as espr4 from MSysObjects
    union all
    select top 1 "4" as espr5 from MSysObjects
    union all
    select top 1 "5" as espr6 from MSysObjects
    union all
    select top 1 "6" as espr7 from MSysObjects
    union all
    select top 1 "7" as espr8 from MSysObjects
    union all
    select top 1 "8" as espr9 from MSysObjects
    union all
    select top 1 "9" as espr9 from MSysObjects
    ) as b,
    (
    select top 1 "0" as espr1 from MSysObjects
    union all
    select top 1 "1" as espr2 from MSysObjects
    union all
    select top 1 "2" as espr3 from MSysObjects
    union all
    select top 1 "3" as espr4 from MSysObjects
    union all
    select top 1 "4" as espr5 from MSysObjects
    union all
    select top 1 "5" as espr6 from MSysObjects
    union all
    select top 1 "6" as espr7 from MSysObjects
    union all
    select top 1 "7" as espr8 from MSysObjects
    union all
    select top 1 "8" as espr9 from MSysObjects
    union all
    select top 1 "9" as espr9 from MSysObjects
    ) as c   
)  as d) 
WHERE date_value 
between dateserial([start_year], [start_month], [start_day]) 
and dateserial([end_year], [end_month], [end_day]);

з посиланням MSysObjects просто "тому, що для доступу потрібна таблиця, що враховує" щонайменше 1 запис, у пункті "a" - будь-яка таблиця, що має щонайменше 1 запис.


2

Як було сказано (або, принаймні, на це наголошується) у багатьох чудових відповідях, вже заданих, ця проблема легко вирішується, коли у вас є набір номерів, з якими можна працювати.

Примітка. Далі йде T-SQL, але це просто моя конкретна реалізація загальних концепцій, про які вже говорилося тут, і в Інтернеті в цілому. Перетворити код на обраний вами діалект слід непросто.

Як? Розглянемо цей запит:

SELECT DATEADD(d, N, '0001-01-22')
FROM Numbers -- A table containing the numbers 0 through N
WHERE N <= 5;

Вищезазначене дає діапазон дат 1/22/0001 - 27.01.0001 і надзвичайно тривіально. Є 2 ключових елементів інформації в наведеному вище запиті: дата початку з 0001-01-22і зміщення в 5. Якщо ми поєднаємо ці дві відомості, то, очевидно, ми закінчимо свою дату. Таким чином, з урахуванням двох дат генерація діапазону може бути розбита так:

  • Легко знайти різницю між двома заданими датами (зсув):

    -- Returns 125 SELECT ABS(DATEDIFF(d, '2014-08-22', '2014-12-25'))

    Використання ABS()тут гарантує, що порядок дати не має значення.

  • Зробити обмежений набір чисел також просто:

    -- Returns the numbers 0-2 SELECT N = ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) - 1 FROM(SELECT 'A' AS S UNION ALL SELECT 'A' UNION ALL SELECT 'A')

    Зауважте, нас насправді не цікавить, що ми FROMтут обираємо . Нам просто потрібен набір для роботи, щоб ми підрахували кількість рядків у ньому. Я особисто використовую TVF, деякі використовують CTE, інші використовують таблицю цифр замість цього, ви розумієте. Я виступаю за використання найефективнішого рішення, яке ви також розумієте.

Поєднання цих двох методів вирішить нашу проблему:

DECLARE @date1 DATE = '9001-11-21';
DECLARE @date2 DATE = '9001-11-23';

SELECT D = DATEADD(d, N, @date1)
FROM (
    SELECT N = ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) - 1
    FROM (SELECT 'A' AS S UNION ALL SELECT 'A' UNION ALL SELECT 'A') S
) Numbers
WHERE N <= ABS(DATEDIFF(d, @date1, @date2));

Наведений вище приклад - жахливий код, але демонструє, як все поєднується.

Більше веселощів

Мені потрібно багато чого робити, тому я вклав логіку у два ТВФ. Перший генерує діапазон чисел, а другий використовує цю функціональність для створення діапазону дат. Математика полягає в тому, щоб порядок введення не мав значення, і тому, що я хотів використовувати повний діапазон номерів, доступних в GenerateRangeSmallInt.

Наступна функція займає ~ 16 мс часу процесора, щоб повернути максимальний діапазон 65536 дат.

CREATE FUNCTION dbo.GenerateRangeDate (   
    @date1 DATE,   
    @date2 DATE   
)   
RETURNS TABLE
WITH SCHEMABINDING   
AS   
RETURN (
    SELECT D = DATEADD(d, N + 32768, CASE WHEN @date1 <= @date2 THEN @date1 ELSE @date2 END)
    FROM dbo.GenerateRangeSmallInt(-32768, ABS(DATEDIFF(d, @date1, @date2)) - 32768)
);

GO

CREATE FUNCTION dbo.GenerateRangeSmallInt (
    @num1 SMALLINT = -32768
  , @num2 SMALLINT = 32767
)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN (
    WITH Numbers(N) AS (
        SELECT N FROM(VALUES
            (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 16
          , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 32
          , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 48
          , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 64
          , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 80
          , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 96
          , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 112
          , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 128
          , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 144
          , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 160
          , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 176
          , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 192
          , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 208
          , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 224
          , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 240
          , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 256
        ) V (N)
    )
    SELECT TOP(ABS(CAST(@num1 AS INT) - CAST(@num2 AS INT)) + 1)
           N = ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) + CASE WHEN @num1 <= @num2 THEN @num1 ELSE @num2 END - 1
    FROM Numbers A
       , Numbers B
);

2

спробуйте це.

SELECT TO_DATE('20160210','yyyymmdd') - 1 + LEVEL AS start_day 
from DUAL
connect by level <= (TO_DATE('20160228','yyyymmdd') + 1) - TO_DATE('20160210','yyyymmdd') ;

2

Ви хочете отримати діапазон дат.

У своєму прикладі ви хочете отримати дати між "2010-01-20" та "2010-01-24"

можливе рішення:

 select date_add('2010-01-20', interval row day) from
 ( 
    SELECT @row := @row + 1 as row FROM 
    (select 0 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 6 union all select 7 union all select 8 union all select 9) t,
    (select 0 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 6 union all select 7 union all select 8 union all select 9) t2, 
    (select 0 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 6 union all select 7 union all select 8 union all select 9) t3, 
    (select 0 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 6 union all select 7 union all select 8 union all select 9) t4, 
    (SELECT @row:=-1) r
 ) sequence
 where date_add('2010-01-20', interval row day) <= '2010-01-24'

Пояснення

MySQL має функцію date_add так

select date_add('2010-01-20', interval 1 day)

дасть тобі

2010-01-21

Функція dateiff дасть вам знати, часто вам доведеться повторювати це

select datediff('2010-01-24', '2010-01-20')

який повертається

 4

Отримання списку дат у діапазоні дат зводиться до створення послідовності цілих чисел, див. Створення цілої послідовності в MySQL

Тут найочікуваніша відповідь взяла аналогічний підхід, як https://stackoverflow.com/a/2652051/1497139 :

SELECT @row := @row + 1 as row FROM 
(select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) t,
(select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) t2, 
(select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) t3, 
(select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) t4, 
(SELECT @row:=0) r
limit 4

що призведе до

row
1.0
2.0
3.0
4.0

Рядки тепер можна використовувати для створення списку дат із заданої дати початку. Для включення дати початку ми починаємо з рядка -1;

select date_add('2010-01-20', interval row day) from
 ( 
    SELECT @row := @row + 1 as row FROM 
    (select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) t,
    (select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) t2, 
    (select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) t3, 
    (select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) t4, 
    (SELECT @row:=-1) r
 ) sequence
 where date_add('2010-01-20', interval row day) <= '2010-01-24'

1

якщо вам колись знадобиться більше пари днів, вам потрібен стіл.

Створіть діапазон дат у mysql

тоді,

select from days.day, count(mytable.field) as fields from days left join mytable on day=date where date between x and y;

3
чому ви це розмістили, оскільки вищевказана відповідь не потребує таблиці та забезпечує рішення?
Pentium10

1

Створення дат між двома полями дати

Якщо вам відомо про запит SQL CTE, то це рішення допоможе вам вирішити своє питання

Ось приклад

У нас є дати в одній таблиці

Назва таблиці: "тестовий термін"

STARTDATE   ENDDATE
10/24/2012  10/24/2012
10/27/2012  10/29/2012
10/30/2012  10/30/2012

Потрібен результат:

STARTDATE
10/24/2012
10/27/2012
10/28/2012
10/29/2012
10/30/2012

Рішення:

WITH CTE AS
  (SELECT DISTINCT convert(varchar(10),StartTime, 101) AS StartTime,
                   datediff(dd,StartTime, endTime) AS diff
   FROM dbo.testdate
   UNION ALL SELECT StartTime,
                    diff - 1 AS diff
   FROM CTE
   WHERE diff<> 0)
SELECT DISTINCT DateAdd(dd,diff, StartTime) AS StartTime
FROM CTE

Пояснення: Пояснення рекурсивного запиту CTE

  • Перша частина запиту:

    SELECT DISTINCT convert(varchar(10), StartTime, 101) AS StartTime, datediff(dd, StartTime, endTime) AS diff FROM dbo.testdate

    Пояснення: перший стовпець - "дата початку", другий стовпець - різниця дати початку та кінця в днях, і він вважатиметься стовпцем "відмінності".

  • Друга частина запиту:

    UNION ALL SELECT StartTime, diff-1 AS diff FROM CTE WHERE diff<>0

    Пояснення: Союз усіх успадкує результат вищезазначеного запиту, поки результат не стане нульовим, тому результат "StartTime" успадковується від створеного запиту CTE, а від diff - зменшення - 1, так що він виглядає як 3, 2 і 1 до 0

Наприклад

STARTDATE   DIFF
10/24/2012  0
10/27/2012  0
10/27/2012  1
10/27/2012  2
10/30/2012  0

Специфікація результату

STARTDATE       Specification
10/24/2012  --> From Record 1
10/27/2012  --> From Record 2
10/27/2012  --> From Record 2
10/27/2012  --> From Record 2
10/30/2012  --> From Record 3
  • 3-а частина запиту

    SELECT DISTINCT DateAdd(dd,diff, StartTime) AS StartTime FROM CTE

    Це додасть день "diff" у "startdate", тому результат повинен бути наступним

Результат

STARTDATE
10/24/2012
10/27/2012
10/28/2012
10/29/2012
10/30/2012

1

Коротша, ніж прийнята відповідь, та сама ідея:

(SELECT TRIM('2016-01-05' + INTERVAL a + b DAY) date
FROM
(SELECT 0 a UNION SELECT 1 a UNION SELECT 2 UNION SELECT 3
UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7
UNION SELECT 8 UNION SELECT 9 ) d,
(SELECT 0 b UNION SELECT 10 UNION SELECT 20
UNION SELECT 30 UNION SELECT 40) m
WHERE '2016-01-05' + INTERVAL a + b DAY  <=  '2016-01-21')

1

Для тих, хто хоче це як збережене уявлення (MySQL не підтримує вкладені оператори вибору у представленнях):

create view zero_to_nine as
    select 0 as n 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;

create view date_range as
    select curdate() - INTERVAL (a.n + (10 * b.n) + (100 * c.n)) DAY as date
    from zero_to_nine as a
    cross join zero_to_nine as b
    cross join zero_to_nine as c;

Потім ви можете зробити

select * from date_range

отримати

date
---
2017-06-06
2017-06-05
2017-06-04
2017-06-03
2017-06-02
...

1

Елегантне рішення з використанням нових рекурсивних (загальних табличних виразів) функцій в 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". Це також пристойно швидко. Повернення фінішних дат на 1000 років (~ 365 000 днів) займає близько 400 мс на моїй машині.


1

Це гарна ідея, коли генеруються ці дати. Однак мені не комфортно робити це з досить великим діапазоном, тому я закінчив таке рішення:

  1. Створено таблицю "ДатиНомери", в якій будуть міститись числа, використані для обчислення дат:
CREATE TABLE DatesNumbers (
    i MEDIUMINT NOT NULL,
    PRIMARY KEY (i)
)
COMMENT='Used by Dates view'
;
  1. Наповнили таблицю, використовуючи вищезазначені методики з числами від -59999 до 40000. Цей діапазон дасть мені дати від 59999 днів (~ 164 роки) до 40000 днів (109 років) вперед:
INSERT INTO DatesNumbers
SELECT 
    a.i + (10 * b.i) + (100 * c.i) + (1000 * d.i) + (10000 * e.i) - 59999 AS i
FROM 
  (SELECT 0 AS i 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) AS a,
  (SELECT 0 AS i 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) AS b,
  (SELECT 0 AS i 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) AS c,
  (SELECT 0 AS i 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) AS d,
  (SELECT 0 AS i 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) AS e
;
  1. Створено подання "Дати":
SELECT
      i,
      CURRENT_DATE() + INTERVAL i DAY AS Date
FROM
    DatesNumbers

Це воно.

  • (+) Легко читати запити
  • (+) Ні, на льоту не покоління
  • (+) Вказує дати в минулому і в майбутньому, і для цього немає СОЮЗУ, як у цій публікації .
  • (+) Дати "Тільки в минулому" або "лише в майбутньому" можна було б відфільтрувати за допомогою WHERE i < 0або WHERE i > 0(PK)
  • (-) використовується "тимчасова" таблиця та подання

0

Добре .. Спробуйте це: http://www.devshed.com/c/a/MySQL/Delving-Deeper-into-MySQL-50/
http://dev.mysql.com/doc/refman/5.0/uk/ loop-statement.html
http://www.roseindia.net/sql/mysql-example/mysql-loop.shtml

Використовуйте це, скажімо, для створення темп-таблиці, а потім зробіть вибір * на таблиці темп. Або виводити результати по одному.
Те, що ви говорите, що хочете зробити, не може бути виконане з оператором SELECT , але це можливо зробити з речами, характерними для MySQL.
Потім знову, можливо, вам потрібні курсори: http://dev.mysql.com/doc/refman/5.0/uk/cursors.html


0

Для Oracle моє рішення:

select trunc(sysdate-dayincrement, 'DD') 
  from dual, (select level as dayincrement 
                from dual connect by level <= 30)

Дату системи можна змінити на конкретну дату, а номер рівня можна змінити, щоб дати більше дат.


0

якщо ви хочете перелік дат між двома датами:

create table #dates ([date] smalldatetime)
while @since < @to
begin
     insert into #dates(dateadd(day,1,@since))
     set @since = dateadd(day,1,@since)
end
select [date] from #dates

* скрипка тут: http://sqlfiddle.com/#!6/9eecb/3469


0
set language  'SPANISH'
DECLARE @table table(fechaDesde datetime , fechaHasta datetime ) 
INSERT @table VALUES('20151231' , '20161231');
WITH x AS 
    (
        SELECT   DATEADD( m , 1 ,fechaDesde ) as fecha  FROM @table
        UNION ALL
        SELECT  DATEADD( m , 1 ,fecha )
        FROM @table t INNER JOIN x ON  DATEADD( m , 1 ,x.fecha ) <= t.fechaHasta
    )
SELECT LEFT( CONVERT( VARCHAR, fecha , 112 ) , 6 ) as Periodo_Id 
,DATEPART ( dd, DATEADD(dd,-(DAY(fecha)-1),fecha)) Num_Dia_Inicio
,DATEADD(dd,-(DAY(fecha)-1),fecha) Fecha_Inicio
,DATEPART ( mm , fecha ) Mes_Id
,DATEPART ( yy , fecha ) Anio
,DATEPART ( dd, DATEADD(dd,-(DAY(DATEADD(mm,1,fecha))),DATEADD(mm,1,fecha))) Num_Dia_Fin
,DATEADD(dd,-(DAY(DATEADD(mm,1,fecha))),DATEADD(mm,1,fecha)) ultimoDia
,datename(MONTH, fecha) mes
,'Q' + convert(varchar(10),  DATEPART(QUARTER, fecha)) Trimestre_Name
FROM x 
OPTION(MAXRECURSION 0)

0
DELIMITER $$
CREATE PROCEDURE GenerateRangeDates(IN dateStart DATE, IN dateEnd DATE)
BEGIN

    CREATE TEMPORARY TABLE IF NOT EXISTS dates (day DATE);

    loopDate: LOOP
        INSERT INTO dates(day) VALUES (dateStart); 
        SET dateStart = DATE_ADD(dateStart, INTERVAL 1 DAY);

        IF dateStart <= dateEnd 
            THEN ITERATE loopDate;
            ELSE LEAVE loopDate;
        END IF;
    END LOOP loopDate;

    SELECT day FROM dates;
    DROP TEMPORARY TABLE IF EXISTS dates;

END 
$$

-- Call procedure
call GenerateRangeDates( 
        now() - INTERVAL 40 DAY,
        now()
    );

0

Версія верхнього рішення RedFilters SQLite

select d.Date
from (
    select 
    date(julianday('2010-01-20') + (a.a + (10 * b.a) + (100 * c.a))) as Date
    from (select 0 as a 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) as a
    cross join (select 0 as a 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) as b
    cross join (select 0 as a 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) as c
) d
where 
d.Date between '2010-01-20' and '2010-01-24' 
order by d.Date

0

покращено з робочим днем та приєднанням до спеціального святкового столу microsoft MSSQL 2012 для таблиці дати Powerpivot https://gist.github.com/josy1024/cb1487d66d9e0ccbd420bc4a23b6e90e

with [dates] as (
    select convert(datetime, '2016-01-01') as [date] --start
    union all
    select dateadd(day, 1, [date])
    from [dates]
    where [date] < '2018-01-01' --end
)
select [date]
, DATEPART (dw,[date]) as Wochentag
, (select holidayname from holidaytable 
where holidaytable.hdate = [date]) 
as Feiertag
from [dates]
where [date] between '2016-01-01' and '2016-31-12'
option (maxrecursion 0)

0
WITH
  Digits AS (SELECT 0 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),
  Dates AS (SELECT adddate('1970-01-01',t4.d*10000 + t3.d*1000 + t2.d*100 + t1.d*10 +t0.d) AS date FROM Digits AS t0, Digits AS t1, Digits AS t2, Digits AS t3, Digits AS t4)
SELECT * FROM Dates WHERE date BETWEEN '2017-01-01' AND '2017-12-31'

0

Можна створити процедуру також для створення календарної таблиці з часовою карткою, відмінною від дня. Якщо ви хочете таблицю для кожної чверті

напр

2019-01-22 08:45:00
2019-01-22 09:00:00
2019-01-22 09:15:00
2019-01-22 09:30:00
2019-01-22 09:45:00
2019-01-22 10:00:00

ви можете використовувати

CREATE DEFINER=`root`@`localhost` PROCEDURE `generate_calendar_table`()
BEGIN

select unix_timestamp('2014-01-01 00:00:00') into @startts;
select unix_timestamp('2025-01-01 00:00:00') into @endts;

if ( @startts < @endts ) then

    DROP TEMPORARY TABLE IF EXISTS calendar_table_tmp;

    CREATE TEMPORARY TABLE calendar_table_tmp (ts int, dt datetime); 

    WHILE ( @startts < @endts)
        DO 
        SET @startts = @startts + 900;
        INSERT calendar_table_tmp VALUES (@startts, from_unixtime(@startts));
    END WHILE;

END if;

END

а потім маніпулювати наскрізь

select ts, dt from calendar_table_tmp;

які дають вам також ц

'1548143100', '2019-01-22 08:45:00'
'1548144000', '2019-01-22 09:00:00'
'1548144900', '2019-01-22 09:15:00'
'1548145800', '2019-01-22 09:30:00'
'1548146700', '2019-01-22 09:45:00'
'1548147600', '2019-01-22 10:00:00'

звідси ви можете почати додавати іншу інформацію, таку як

select ts, dt, weekday(dt) as wd from calendar_table_tmp;

або створити реальну таблицю зі створенням оператора таблиці


0

Більш загальна відповідь, яка працює в AWS MySQL.

select datetable.Date
from (
    select date_format(adddate(now(),-(a.a + (10 * b.a) + (100 * c.a))),'%Y-%m-%d') AS Date
    from (select 0 as a 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) as a

    cross join (select 0 as a 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) as b

    cross join (select 0 as a 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) as c
) datetable
where datetable.Date between now() - INTERVAL 14 Day and Now()
order by datetable.Date DESC

-1

Ще одне рішення для mysql 8.0.1 та mariadb 10.2.2 з використанням рекурсивних загальних табличних виразів:

with recursive dates as (
    select '2010-01-20' as date
    union all
    select date + interval 1 day from dates where date < '2010-01-24'
)
select * from dates;
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.