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(select0as a unionallselect1unionallselect2unionallselect3unionallselect4unionallselect5unionallselect6unionallselect7unionallselect8unionallselect9)as a
crossjoin(select0as a unionallselect1unionallselect2unionallselect3unionallselect4unionallselect5unionallselect6unionallselect7unionallselect8unionallselect9)as b
crossjoin(select0as a unionallselect1unionallselect2unionallselect3unionallselect4unionallselect5unionallselect6unionallselect7unionallselect8unionallselect9)as c
crossjoin(select0as a unionallselect1unionallselect2unionallselect3unionallselect4unionallselect5unionallselect6unionallselect7unionallselect8unionallselect9)as d
) a
where a.Date between'2010-01-20'and'2010-01-24'
Resultado:
Date
----------2010-01-242010-01-232010-01-222010-01-212010-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.
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:
CREATEVIEW digits ASSELECT0AS digit UNIONALLSELECT1UNIONALLSELECT2UNIONALLSELECT3UNIONALLSELECT4UNIONALLSELECT5UNIONALLSELECT6UNIONALLSELECT7UNIONALLSELECT8UNIONALLSELECT9;CREATEVIEW numbers ASSELECT
ones.digit + tens.digit *10+ hundreds.digit *100+ thousands.digit *1000AS number
FROM
digits as ones,
digits as tens,
digits as hundreds,
digits as thousands;CREATEVIEW dates ASSELECT
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'ORDERBY
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:
CREATEVIEW dates ASSELECT
SUBDATE(CURRENT_DATE(), number)AS date
FROM
numbers
UNIONALLSELECT
ADDDATE(CURRENT_DATE(), number +1)AS date
FROM
numbers;
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-202010-01-212010-01-222010-01-232010-01-24(5rows)
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.
select datetable.Date
from(select DATEADD(day,-(a.a +(10* b.a)+(100* c.a)),getdate())AS Date
from(select0as a unionallselect1unionallselect2unionallselect3unionallselect4unionallselect5unionallselect6unionallselect7unionallselect8unionallselect9)as a
crossjoin(select0as a unionallselect1unionallselect2unionallselect3unionallselect4unionallselect5unionallselect6unionallselect7unionallselect8unionallselect9)as b
crossjoin(select0as a unionallselect1unionallselect2unionallselect3unionallselect4unionallselect5unionallselect6unionallselect7unionallselect8unionallselect9)as c
) datetable
where datetable.Date between'2014-01-20'and'2014-01-24'orderby datetable.Date DESC
Resultado
Date
-----2014-01-2312:35:25.2502014-01-2212:35:25.2502014-01-2112:35:25.2502014-01-2012:35:25.250
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.
Você precisa preencher a tabela com registros suficientes para atender às suas necessidades:
INSERTINTO 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'
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.
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:
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(selecttop1"0"as espr1 from MSysObjects
unionallselecttop1"1"as espr2 from MSysObjects
unionallselecttop1"2"as espr3 from MSysObjects
unionallselecttop1"3"as espr4 from MSysObjects
unionallselecttop1"4"as espr5 from MSysObjects
unionallselecttop1"5"as espr6 from MSysObjects
unionallselecttop1"6"as espr7 from MSysObjects
unionallselecttop1"7"as espr8 from MSysObjects
unionallselecttop1"8"as espr9 from MSysObjects
unionallselecttop1"9"as espr9 from MSysObjects
)as a,(selecttop1"0"as espr1 from MSysObjects
unionallselecttop1"1"as espr2 from MSysObjects
unionallselecttop1"2"as espr3 from MSysObjects
unionallselecttop1"3"as espr4 from MSysObjects
unionallselecttop1"4"as espr5 from MSysObjects
unionallselecttop1"5"as espr6 from MSysObjects
unionallselecttop1"6"as espr7 from MSysObjects
unionallselecttop1"7"as espr8 from MSysObjects
unionallselecttop1"8"as espr9 from MSysObjects
unionallselecttop1"9"as espr9 from MSysObjects
)as b,(selecttop1"0"as espr1 from MSysObjects
unionallselecttop1"1"as espr2 from MSysObjects
unionallselecttop1"2"as espr3 from MSysObjects
unionallselecttop1"3"as espr4 from MSysObjects
unionallselecttop1"4"as espr5 from MSysObjects
unionallselecttop1"5"as espr6 from MSysObjects
unionallselecttop1"6"as espr7 from MSysObjects
unionallselecttop1"7"as espr8 from MSysObjects
unionallselecttop1"8"as espr9 from MSysObjects
unionallselecttop1"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.
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 NWHERE 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:
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(ORDERBY(SELECTNULL))-1FROM(SELECT'A'AS S UNIONALLSELECT'A'UNIONALLSELECT'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.
CREATEFUNCTION dbo.GenerateRangeDate (@date1 DATE,@date2 DATE
)
RETURNS TABLEWITH SCHEMABINDING
ASRETURN(SELECT D = DATEADD(d, N +32768,CASEWHEN@date1 <=@date2 THEN@date1 ELSE@date2 END)FROM dbo.GenerateRangeSmallInt(-32768, ABS(DATEDIFF(d,@date1,@date2))-32768));
GO
CREATEFUNCTION dbo.GenerateRangeSmallInt (@num1 SMALLINT =-32768,@num2 SMALLINT =32767)
RETURNS TABLEWITH SCHEMABINDING
ASRETURN(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))SELECTTOP(ABS(CAST(@num1 AS INT)- CAST(@num2 AS INT))+1)
N = ROW_NUMBER()OVER(ORDERBY(SELECTNULL))+CASEWHEN@num1 <=@num2 THEN@num1 ELSE@num2 END-1FROM Numbers A
, Numbers B
);
SELECT@row:=@row+1asrowFROM(select0unionallselect1unionallselect3unionallselect4unionallselect5unionallselect6unionallselect6unionallselect7unionallselect8unionallselect9) t,(select0unionallselect1unionallselect3unionallselect4unionallselect5unionallselect6unionallselect6unionallselect7unionallselect8unionallselect9) t2,(select0unionallselect1unionallselect3unionallselect4unionallselect5unionallselect6unionallselect6unionallselect7unionallselect8unionallselect9) t3,(select0unionallselect1unionallselect3unionallselect4unionallselect5unionallselect6unionallselect6unionallselect7unionallselect8unionallselect9) t4,(SELECT@row:=0) r
limit 4
o que resultará em
row1.02.03.04.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+1asrowFROM(select0unionallselect1unionallselect3unionallselect4unionallselect5unionallselect6unionallselect6unionallselect7unionallselect8unionallselect9) t,(select0unionallselect1unionallselect3unionallselect4unionallselect5unionallselect6unionallselect6unionallselect7unionallselect8unionallselect9) t2,(select0unionallselect1unionallselect3unionallselect4unionallselect5unionallselect6unionallselect6unionallselect7unionallselect8unionallselect9) t3,(select0unionallselect1unionallselect3unionallselect4unionallselect5unionallselect6unionallselect6unionallselect7unionallselect8unionallselect9) t4,(SELECT@row:=-1) r
) sequence
where date_add('2010-01-20', interval row day)<='2010-01-24'
WITH CTE AS(SELECTDISTINCTconvert(varchar(10),StartTime,101)AS StartTime,
datediff(dd,StartTime, endTime)AS diff
FROM dbo.testdate
UNIONALLSELECT StartTime,
diff -1AS diff
FROM CTE
WHERE diff<>0)SELECTDISTINCT 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
STARTDATE Specification
10/24/2012--> From Record 110/27/2012--> From Record 210/27/2012--> From Record 210/27/2012--> From Record 210/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
(SELECT TRIM('2016-01-05'+ INTERVAL a + b DAY) date
FROM(SELECT0 a UNIONSELECT1 a UNIONSELECT2UNIONSELECT3UNIONSELECT4UNIONSELECT5UNIONSELECT6UNIONSELECT7UNIONSELECT8UNIONSELECT9) d,(SELECT0 b UNIONSELECT10UNIONSELECT20UNIONSELECT30UNIONSELECT40) m
WHERE'2016-01-05'+ INTERVAL a + b DAY <='2016-01-21')
Para quem deseja isso como uma visualização salva (o MySQL não suporta instruções de seleção aninhadas nas visualizações):
createview zero_to_nine asselect0as n unionallselect1unionallselect2unionallselect3unionallselect4unionallselect5unionallselect6unionallselect7unionallselect8unionallselect9;createview date_range asselect curdate()- INTERVAL (a.n +(10* b.n)+(100* c.n)) DAY as date
from zero_to_nine as a
crossjoin zero_to_nine as b
crossjoin zero_to_nine as c;
Você pode então fazer
select*from date_range
para obter
date
---2017-06-062017-06-052017-06-042017-06-032017-06-02...
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
UNIONSELECT 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.
É 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:
Criou uma tabela "DatesNumbers" que conterá os números usados para o cálculo das datas:
CREATETABLE DatesNumbers (
i MEDIUMINT NOTNULL,PRIMARYKEY(i))
COMMENT='Used by Dates view';
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:
INSERTINTO DatesNumbers
SELECT
a.i +(10* b.i)+(100* c.i)+(1000* d.i)+(10000* e.i)-59999AS i
FROM(SELECT0AS i UNIONALLSELECT1UNIONALLSELECT2UNIONALLSELECT3UNIONALLSELECT4UNIONALLSELECT5UNIONALLSELECT6UNIONALLSELECT7UNIONALLSELECT8UNIONALLSELECT9)AS a,(SELECT0AS i UNIONALLSELECT1UNIONALLSELECT2UNIONALLSELECT3UNIONALLSELECT4UNIONALLSELECT5UNIONALLSELECT6UNIONALLSELECT7UNIONALLSELECT8UNIONALLSELECT9)AS b,(SELECT0AS i UNIONALLSELECT1UNIONALLSELECT2UNIONALLSELECT3UNIONALLSELECT4UNIONALLSELECT5UNIONALLSELECT6UNIONALLSELECT7UNIONALLSELECT8UNIONALLSELECT9)AS c,(SELECT0AS i UNIONALLSELECT1UNIONALLSELECT2UNIONALLSELECT3UNIONALLSELECT4UNIONALLSELECT5UNIONALLSELECT6UNIONALLSELECT7UNIONALLSELECT8UNIONALLSELECT9)AS d,(SELECT0AS i UNIONALLSELECT1UNIONALLSELECT2UNIONALLSELECT3UNIONALLSELECT4UNIONALLSELECT5UNIONALLSELECT6UNIONALLSELECT7UNIONALLSELECT8UNIONALLSELECT9)AS e
;
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)
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
set language 'SPANISH'DECLARE@tabletable(fechaDesde datetime , fechaHasta datetime )INSERT@tableVALUES('20151231','20161231');WITH x AS(SELECT DATEADD( m ,1,fechaDesde )as fecha FROM@tableUNIONALLSELECT DATEADD( m ,1,fecha )FROM@table t INNERJOIN x ON DATEADD( m ,1,x.fecha )<= t.fechaHasta
)SELECTLEFT(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)
select d.Date
from(select
date(julianday('2010-01-20')+(a.a +(10* b.a)+(100* c.a)))as Date
from(select0as a unionallselect1unionallselect2unionallselect3unionallselect4unionallselect5unionallselect6unionallselect7unionallselect8unionallselect9)as a
crossjoin(select0as a unionallselect1unionallselect2unionallselect3unionallselect4unionallselect5unionallselect6unionallselect7unionallselect8unionallselect9)as b
crossjoin(select0as a unionallselect1unionallselect2unionallselect3unionallselect4unionallselect5unionallselect6unionallselect7unionallselect8unionallselect9)as c
) d
where
d.Date between'2010-01-20'and'2010-01-24'orderby d.Date
WITH
Digits AS(SELECT0 D UNIONSELECT1UNIONSELECT2UNIONSELECT3UNIONSELECT4UNIONSELECT5UNIONSELECT6UNIONSELECT7UNIONSELECT8UNIONSELECT9),
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'
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
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(select0as a unionallselect1unionallselect2unionallselect3unionallselect4unionallselect5unionallselect6unionallselect7unionallselect8unionallselect9)as a
crossjoin(select0as a unionallselect1unionallselect2unionallselect3unionallselect4unionallselect5unionallselect6unionallselect7unionallselect8unionallselect9)as b
crossjoin(select0as a unionallselect1unionallselect2unionallselect3unionallselect4unionallselect5unionallselect6unionallselect7unionallselect8unionallselect9)as c
) datetable
where datetable.Date between now()- INTERVAL 14 Day and Now()orderby datetable.Date DESC
insert into table select ... as days date between '' and ''
Respostas:
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.
Resultado:
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
fonte
UNION
paraUNION 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?Aqui está outra variação usando visualizações:
E então você pode simplesmente fazer (veja como é elegante?):
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:
fonte
dates
mencionada 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.UNION
cláusulas parecem estranhas em uma única instrução SQL.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_series
função, ou seja:fonte
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.
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.
fonte
Consulta MSSQL
Resultado
fonte
A solução antiga para fazer isso sem um loop / cursor é criar uma
NUMBERS
tabela, que possui uma única coluna Inteira com valores começando em 1.Você precisa preencher a tabela com registros suficientes para atender às suas necessidades:
Depois de ter a
NUMBERS
tabela, você pode usar:A solução de baixa tecnologia absoluta seria:
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.
fonte
DUAL
tabela é suportada pelo Oracle e MySQL para usar como tabela de reserva naFROM
clá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 umaFROM
cláusula especificando pelo menos uma tabela.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)
Crie uma consulta chamada "ZeroThru9Q"; insira manualmente a seguinte sintaxe:
Crie uma consulta chamada "TodayMinus1KQ" (para datas anteriores a hoje); insira manualmente a seguinte sintaxe:
Crie uma consulta denominada "TodayPlus1KQ" (para datas posteriores a hoje); insira manualmente a seguinte sintaxe:
Crie uma consulta de união chamada "TodayPlusMinus1KQ" (para datas +/- 1000 dias):
Agora você pode usar a consulta:
fonte
Procedimento + tabela temporária:
fonte
thx Pentium10 - você me fez participar do stackoverflow :) - esta é minha portabilidade para o msaccess - acho que funcionará em qualquer versão:
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.
fonte
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:
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ício
0001-01-22
e o deslocamento de5
. 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
FROM
aqui. 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:
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.
fonte
tente isso.
fonte
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:
Explicação
O MySQL possui uma função date_add , então
Darei à você
A função datatediff informaria com frequência que você teria que repetir isso
que retorna
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:
o que resultará em
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;
fonte
se você precisar de mais de alguns dias, precisará de uma mesa.
Crie um período no mysql
então,
fonte
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"
Exigir resultado:
Solução:
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
Especificação do resultado
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
fonte
Resposta mais curta do que aceita, mesma ideia:
fonte
Para quem deseja isso como uma visualização salva (o MySQL não suporta instruções de seleção aninhadas nas visualizações):
Você pode então fazer
para obter
fonte
Solução elegante usando a nova funcionalidade recursiva (Common Table Expressions) no MariaDB> = 10.3 e MySQL> = 8.0.
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.
fonte
É 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:
É isso aí.
WHERE i < 0
ouWHERE i > 0
(PK)fonte
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
fonte
Para Oracle, minha solução é:
O Sysdate pode ser alterado para uma data específica e o número do nível pode ser alterado para fornecer mais datas.
fonte
se você quiser a lista de datas entre duas datas:
* mexa aqui: http://sqlfiddle.com/#!6/9eecb/3469
fonte
fonte
fonte
Versão SQLite da melhor solução RedFilters
fonte
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
fonte
fonte
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
você pode usar
e depois manipular através
que também te dão ts
a partir daqui, você pode começar a adicionar outras informações, como
ou crie uma tabela real com a instrução create table
fonte
Uma resposta mais genérica que funciona no AWS MySQL.
fonte
Mais uma solução para o mysql 8.0.1 e o mariadb 10.2.2 usando expressões recursivas de tabela comum:
fonte