gerar dias a partir do período


135

Eu gostaria de executar uma consulta como

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

E retorne dados como:

dias
----------
20/01/2010
21/01/2010
22/01/2010
23/01/2010
24/01/2010

10
Não há outro problema associado a esta pergunta. A questão acima é o problema, dominando os cursos SQL.
usar o seguinte comando

Você só precisa de uma matriz de datas com base em um período selecionado?
Derek Adair

1
Estou pensando em um uso, para encontrar um problema para você ... Se você receber uma tarefa para preencher alguns registros ausentes na sua tabela. E você tem que executar uma consulta para cada dia eu estou pensando em algo comoinsert into table select ... as days date between '' and ''
Pentium10

13
Um exemplo para seu uso seria gerar estatísticas e incluir uma linha para datas nas quais você não tem dados. Se você está fazendo algum tipo de agrupamento, pode ser muito mais rápido gerar todas as informações no SQL e adicioná-las ao formato que você precisar, em vez de colocar seus dados como estão no seu idioma e começar a repetir e adicionar seus dados. esvazia.
Nanne 24/07/2013

1
@ Anne, é precisamente por isso que salvei esta pergunta. Preciso do que foi dito acima para LEFT JOIN em dados que podem não existir em determinadas datas.
Josh Diehl

Respostas:


318

Esta solução não usa loops, procedimentos ou tabelas temporárias . A subconsulta gera datas para os últimos 10.000 dias e pode ser estendida para voltar ou avançar o quanto você desejar.

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' 

Resultado:

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

Notas sobre desempenho

Testando aqui , o desempenho é surpreendentemente bom: a consulta acima leva 0,0009 s.

Se estendermos a subconsulta para gerar aprox. 100.000 números (e, portanto, cerca de 274 anos em datas), é executado em 0,0458 s.

Aliás, essa é uma técnica muito portátil que funciona com a maioria dos bancos de dados com pequenos ajustes.

Exemplo do SQL Fiddle retornando 1.000 dias


6
Você verá um melhor desempenho se mudar UNIONpara UNION ALL- está perdendo tempo procurando duplicatas para remover que não existem. No entanto, a IMO é muito complicada - se você deseja criar um conjunto de resultados usando UNIONs, por que não especificar a data e terminar com ela?
OMG Ponies

7
por que não apenas especificar a data e concluir com ela - porque o método acima permite criar arbitrariamente grandes conjuntos de números (e datas) que não exigem criação de tabela, seria doloroso codificar da maneira sugerida. Obviamente, por 5 datas, é um exagero; mas, mesmo assim, se você estiver ingressando em uma tabela em que não conhece as datas com antecedência, mas apenas os possíveis valores mínimo e máximo, isso faz sentido.
RedFilter

2
É "doloroso" usar apenas a função DATETIME no lugar da instrução UNION que você já criou? Ele alivia a necessidade da lógica que você teve que adicionar . Portanto - você complicou demais a consulta. A declaração UNION, de qualquer forma, não é escalável - especificando uma data ou número, quem deseja atualizá-la para acomodar, digamos, 20 ou 30 datas?
OMG Ponies

23
É realmente bom ver uma resposta para a pergunta, sem comentários intermináveis ​​sobre como ela não pode ou não deve ser feita. A maioria das coisas pode ser feita, e "deveria" é apenas significativo no contexto, o que difere para todos. Essa resposta me ajudou, embora eu esteja ciente de que existem maneiras melhores na maioria das situações.
Joe

7
Aqueles que não conseguem fazer com que esta consulta funcione: Dê um tapa na cara e, em seguida, releia o comentário do OP sobre esta consulta, gerando 1000 datas. Desde 2010, há mais de 1000 dias, você precisará ajustar a consulta adequadamente.
Noel Baron

32

Aqui está outra variação usando visualizações:

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;

E então você pode simplesmente fazer (veja como é elegante?):

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

Atualizar

Vale ressaltar que você só poderá gerar datas passadas a partir da data atual . Se você deseja gerar qualquer tipo de intervalo de datas (passado, futuro e intermediário), será necessário usar esta exibição:

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
Isso não funciona em todos os casos. SELECIONE data DAS datas ONDE data ENTRE '2014-12-01' e '2014-12-28' PEDIR POR data
vasanth

3
Boa chamada @ user927258. Isso ocorre porque a primeira exibição datesmencionada acima calcula as datas a partir da data atual, e é por isso que você não poderá recuperar as datas definidas no futuro. A resposta do @RedFilter sofre da mesma falha de design. Eu adicionei uma solução alternativa na minha resposta.
Stéphane

O uso de algumas visualizações simplifica definitivamente as consultas e as torna reutilizáveis. Embora eles estejam essencialmente fazendo a mesma coisa, todas essas UNIONcláusulas parecem estranhas em uma única instrução SQL.
Stewart

24

A resposta aceita não funcionou para o PostgreSQL (erro de sintaxe em ou próximo de "a").

A maneira como você faz isso no PostgreSQL é usando a generate_seriesfunção, ou seja:

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

Usando uma Common Table Expression (CTE) recursiva, você pode gerar uma lista de datas e selecionar a partir dela. Obviamente, você normalmente não gostaria de criar três milhões de datas, então isso apenas ilustra as possibilidades. Você pode simplesmente limitar o período dentro do CTE e omitir a cláusula where da instrução select usando o 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)

No Microsoft SQL Server 2005, a geração da lista CTE de todas as datas possíveis levou 1:08. Gerar cem anos levou menos de um segundo.


7

Consulta 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

Resultado

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
Se eu tivesse rolado um pouco mais ... suspiro. De qualquer forma, obrigado. Adicionei um CAST (<expressão> COMO DATA) para remover o tempo na minha versão. Também usado onde a.Date entre GETDATE () - 365 AND GETDATE () ... se você executar sua consulta hoje, ela não fornecerá linhas se você não perceber as datas no WHERE = P
Ricardo C

4

A solução antiga para fazer isso sem um loop / cursor é criar uma NUMBERStabela, que possui uma única coluna Inteira com valores começando em 1.

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

Você precisa preencher a tabela com registros suficientes para atender às suas necessidades:

INSERT INTO NUMBERS (id) VALUES (NULL);

Depois de ter a NUMBERStabela, você pode usar:

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'

A solução de baixa tecnologia absoluta seria:

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

Para que você usaria isto?


Para gerar listas de datas ou números para LEFT JOIN. Você faria isso para ver onde existem lacunas nos dados, porque você está deixando uma junção em uma lista de dados sequenciais - valores nulos tornarão óbvio onde existem lacunas.


1
A DUALtabela é suportada pelo Oracle e MySQL para usar como tabela de reserva na FROMcláusula. Não existe, a seleção de valores retornará qualquer que seja o valor. A idéia era ter o substituto, porque uma consulta SELECT requer uma FROMcláusula especificando pelo menos uma tabela.
OMG Ponies

1
+1 para criar uma tabela de números permanentes em vez de fazer com que o RDBMS a construa sempre que precisar da consulta. As mesas auxiliares não são más, pessoal!
Bacon Bits

4

Para o Access 2010 - várias etapas necessárias; Segui o mesmo padrão postado acima, mas achei que poderia ajudar alguém no Access. Funcionou muito bem para mim, não tive que manter uma tabela semeada de datas.

Crie uma tabela chamada DUAL (semelhante à forma como a tabela Oracle DUAL funciona)

  • ID (AutoNumeração)
  • DummyColumn (Texto)
  • Adicione valores de uma linha (1, "DummyRow")

Crie uma consulta chamada "ZeroThru9Q"; insira manualmente a seguinte sintaxe:

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;

Crie uma consulta chamada "TodayMinus1KQ" (para datas anteriores a hoje); insira manualmente a seguinte sintaxe:

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

Crie uma consulta denominada "TodayPlus1KQ" (para datas posteriores a hoje); insira manualmente a seguinte sintaxe:

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;

Crie uma consulta de união chamada "TodayPlusMinus1KQ" (para datas +/- 1000 dias):

SELECT MyDate
FROM TodayMinus1KQ
UNION
SELECT MyDate
FROM TodayPlus1KQ;

Agora você pode usar a consulta:

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

3

Procedimento + tabela temporária:

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 - você me fez participar do stackoverflow :) - esta é minha portabilidade para o msaccess - acho que funcionará em qualquer versão:

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 referenciados apenas porque o acesso precisa de uma tabela com pelo menos 1 registro, em uma cláusula from - qualquer tabela com pelo menos 1 registro seria necessária.


2

Como afirmado (ou pelo menos mencionado) em muitas das maravilhosas respostas já dadas, esse problema é facilmente resolvido quando você tem um conjunto de números para trabalhar.

Nota: O seguinte é o T-SQL, mas é simplesmente minha implementação específica de conceitos gerais já mencionados aqui e na Internet em geral. Deve ser relativamente simples converter o código para o seu dialeto de sua escolha.

Quão? Considere esta consulta:

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

O texto acima produz o intervalo de datas 22/1/0001 a 27/1/0001 e é extremamente trivial. Há duas informações importantes na consulta acima: a data de início0001-01-22 e o deslocamento de 5. Se combinarmos essas duas informações, obviamente teremos nossa data final. Assim, dadas duas datas, a geração de um intervalo pode ser dividida da seguinte forma:

  • Encontre a diferença entre duas datas (o deslocamento), fácil:

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

    O uso ABS()aqui garante que a ordem da data seja irrelevante.

  • Gere um conjunto limitado de números, também fácil:

    -- 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')

    Observe que não nos importamos com o que estamos selecionando FROMaqui. Só precisamos de um conjunto para trabalhar, para que contemos o número de linhas nele. Eu pessoalmente uso um TVF, alguns usam CTE, outros usam uma tabela de números, você entendeu. Defendo o uso da solução de melhor desempenho que você também entende.

A combinação desses dois métodos resolverá nosso problema:

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));

O exemplo acima é um código horrível, mas demonstra como tudo acontece.

Mais divertido

Eu preciso fazer muito esse tipo de coisa, então encapsulei a lógica em dois TVFs. O primeiro gera um intervalo de números e o segundo usa essa funcionalidade para gerar um intervalo de datas. A matemática é garantir que a ordem de entrada não importa e porque eu queria usar toda a gama de números disponíveis no GenerateRangeSmallInt.

A função a seguir leva ~ 16ms do tempo da CPU para retornar o intervalo máximo de 65536 datas.

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

tente isso.

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

Você deseja obter o período.

No seu exemplo, você deseja obter as datas entre '2010-01-20' e '2010-01-24'

solução possível:

 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'

Explicação

O MySQL possui uma função date_add , então

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

Darei à você

2010-01-21

A função datatediff informaria com frequência que você teria que repetir isso

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

que retorna

 4

Obter uma lista de datas em um intervalo de datas se resume a criar uma sequência de números inteiros, veja gerar uma sequência inteira no MySQL

A resposta mais votada aqui adotou uma abordagem semelhante à https://stackoverflow.com/a/2652051/1497139 como base:

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

o que resultará em

row
1.0
2.0
3.0
4.0

As linhas agora podem ser usadas para criar uma lista de datas a partir da data de início especificada. Para incluir a data de início, começamos com a linha -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

se você precisar de mais de alguns dias, precisará de uma mesa.

Crie um período no mysql

então,

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

3
por que você postou isso, já que a resposta acima não precisa de uma tabela e fornece a solução?
usar o seguinte comando

1

Gere datas entre dois campos de data

Se você está ciente da consulta SQL CTE, esta solução o ajudará a resolver sua pergunta

Aqui está um exemplo

Temos datas em uma tabela

Nome da tabela: "testdate"

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

Exigir resultado:

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

Solução:

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

Explicação: Explicação da consulta recursiva do CTE

  • Primeira parte da consulta:

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

    Explicação: a primeira coluna é "data de início", a segunda coluna é a diferença da data de início e de término em dias e será considerada como coluna "diff"

  • Segunda parte da consulta:

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

    Explicação: A união de todos herdará o resultado da consulta acima até que o resultado fique nulo. Portanto, o resultado "StartTime" é herdado da consulta CTE gerada e, do diff, diminui - 1, então parece 3, 2 e 1 até 0

Por exemplo

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

Especificação do resultado

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
  • Terceira parte da consulta

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

    Ele adicionará o dia "diff" em "data de início", portanto o resultado deve ser o seguinte

Resultado

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

1

Resposta mais curta do que aceita, mesma ideia:

(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

Para quem deseja isso como uma visualização salva (o MySQL não suporta instruções de seleção aninhadas nas visualizações):

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;

Você pode então fazer

select * from date_range

para obter

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

1

Solução elegante usando a nova funcionalidade recursiva (Common Table Expressions) no MariaDB> = 10.3 e 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;

O exemplo acima retorna uma tabela de datas entre '2019-01-01' e '2019-04-30'. Também é decentemente rápido. Devolver 1000 anos de datas (~ 365.000 dias) leva cerca de 400ms na minha máquina.


1

É uma boa ideia gerar essas datas rapidamente. No entanto, não me sinto à vontade para fazer isso com uma faixa bastante grande, então acabei com a seguinte solução:

  1. Criou uma tabela "DatesNumbers" que conterá os números usados ​​para o cálculo das datas:
CREATE TABLE DatesNumbers (
    i MEDIUMINT NOT NULL,
    PRIMARY KEY (i)
)
COMMENT='Used by Dates view'
;
  1. Preenchida a tabela usando as técnicas acima com números de -59999 a 40000. Esse intervalo fornece datas de 59999 dias (~ 164 anos) a 40000 dias (109 anos) à frente:
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. Criou uma visualização "Datas":
SELECT
      i,
      CURRENT_DATE() + INTERVAL i DAY AS Date
FROM
    DatesNumbers

É isso aí.

  • (+) Consultas fáceis de ler
  • (+) Não em tempo real números gerações
  • (+) Dá datas no passado e no futuro e NÃO há UNIÃO à vista para isso, como neste post .
  • (+) As datas "somente no passado" ou "somente no futuro" podem ser filtradas usando WHERE i < 0ou WHERE i > 0(PK)
  • (-) tabela e exibição 'temporárias' são usadas

0

Tudo bem .. Tente o seguinte: http://www.devshed.com/c/a/MySQL/Delving-Deeper-into-MySQL-50/
http://dev.mysql.com/doc/refman/5.0/en/ loop-statement.html
http://www.roseindia.net/sql/mysql-example/mysql-loop.shtml

Use isso para, por exemplo, gerar uma tabela temporária e, em seguida, selecione * na tabela temporária. Ou imprima os resultados um de cada vez.
O que você diz que deseja fazer não pode ser feito com uma instrução SELECT , mas pode ser possível com coisas específicas ao MySQL.
Por outro lado, talvez você precise de cursores: http://dev.mysql.com/doc/refman/5.0/en/cursors.html


0

Para Oracle, minha solução é:

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

O Sysdate pode ser alterado para uma data específica e o número do nível pode ser alterado para fornecer mais datas.


0

se você quiser a lista de datas entre duas datas:

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

* mexa aqui: 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

Versão SQLite da melhor solução RedFilters

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

aprimorado com o dia da semana e ingressando em uma tabela de feriados personalizada Microsoft MSSQL 2012 para tabela de datas do 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

Também é possível criar um procedimento para criar tabela de calendário com um mapa de data e hora diferente do dia. Se você quer uma mesa para cada trimestre

por exemplo

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

você pode usar

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

e depois manipular através

select ts, dt from calendar_table_tmp;

que também te dão ts

'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'

a partir daqui, você pode começar a adicionar outras informações, como

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

ou crie uma tabela real com a instrução create table


0

Uma resposta mais genérica que funciona no 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

Mais uma solução para o mysql 8.0.1 e o mariadb 10.2.2 usando expressões recursivas de tabela comum:

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;
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.