No hay otro problema asociado a esta pregunta. La pregunta anterior es el problema, dominar los cursos de SQL.
Pentium10
¿Solo necesita una serie de fechas basadas en un intervalo de fechas seleccionado?
Derek Adair
1
Estoy pensando en un uso, para encontrarle un problema ... Si tiene la tarea de completar algunos registros faltantes en su tabla. Y tienes que ejecutar una consulta por cada día que estoy pensando algo así comoinsert into table select ... as days date between '' and ''
Pentium10
13
Un ejemplo para su uso sería generar estadísticas e incluir una fila para fechas sobre las que no tiene datos. Si está haciendo algún tipo de agrupación, puede ser mucho más rápido generar toda la información en SQL y agregarla en cualquier formato que necesite, en lugar de volcar sus datos tal como están en su idioma, y comenzar a recorrer y agregar su envases.
Nanne
1
@Nanne que es precisamente por eso que guardé esta pregunta. Necesito lo anterior para IZQUIERDA UNIRSE en datos que pueden no existir para ciertas fechas.
Josh Diehl
Respuestas:
318
Esta solución no utiliza bucles, procedimientos o tablas temporales . La subconsulta genera fechas para los últimos 10,000 días, y podría extenderse para ir tan atrás o adelante como lo desee.
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'
Salida:
Date
----------2010-01-242010-01-232010-01-222010-01-212010-01-20
Notas sobre rendimiento
Al probarlo aquí , el rendimiento es sorprendentemente bueno: la consulta anterior tarda 0.0009 segundos.
Si ampliamos la subconsulta para generar aprox. 100,000 números (y por lo tanto, alrededor de 274 años en fechas), se ejecuta en 0.0458 segundos.
Por cierto, esta es una técnica muy portátil que funciona con la mayoría de las bases de datos con ajustes menores.
Verá un mejor rendimiento si cambia UNIONa UNION ALL: es perder el tiempo buscando duplicados para eliminar que no existen. Sin embargo, es una OMI demasiado complicada: si va a construir un conjunto de resultados utilizando UNION, ¿por qué no solo especificar la fecha y terminar con ella?
OMG Ponies
77
¿por qué no solo especificar la fecha y terminar con ella ? Debido a que el método anterior le permite crear conjuntos de números (y fechas) arbitrariamente grandes que no requieren la creación de una tabla, sería difícil codificar de la manera que sugiere. Obviamente por 5 fechas es excesivo; pero incluso entonces, si se une contra una tabla en la que no conoce las fechas de antemano, sino solo los valores mínimos y máximos potenciales, tiene sentido.
RedFilter
2
¿Es "doloroso" usar la función DATETIME en lugar de la declaración UNION que ya ha creado? Se alivia la necesidad de la lógica que había que añadir . Por lo tanto, ha complicado demasiado la consulta. La declaración UNION, de cualquier manera, no es escalable: especifica una fecha o número, ¿quién quiere actualizarla para acomodar, por ejemplo, 20 o 30 fechas?
OMG Ponies
23
Es realmente agradable ver una respuesta a la pregunta, no comentarios interminables sobre cómo no se puede o no se debe hacer. La mayoría de las cosas se pueden hacer, y "debería" solo tiene sentido en el contexto, lo que difiere para todos. Esta respuesta me ayudó, aunque soy consciente de que hay mejores formas en la mayoría de las situaciones.
Joe
77
Aquellos de ustedes que no pueden hacer que esta consulta funcione: abofeteen y luego vuelvan a leer el comentario del OP sobre esta consulta generando 1000 fechas. Como 2010 fue hace más de 1000 días, deberá ajustar la consulta en consecuencia.
Noel Baron
32
Aquí hay otra variación usando vistas:
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;
Y luego puedes simplemente hacer (¿ves lo elegante que es?):
SELECT
date
FROM
dates
WHERE
date BETWEEN'2010-01-20'AND'2010-01-24'ORDERBY
date
Actualizar
Vale la pena señalar que solo podrá generar fechas pasadas a partir de la fecha actual . Si desea generar cualquier tipo de rango de fechas (pasado, futuro y intermedio), deberá utilizar esta vista en su lugar:
CREATEVIEW dates ASSELECT
SUBDATE(CURRENT_DATE(), number)AS date
FROM
numbers
UNIONALLSELECT
ADDDATE(CURRENT_DATE(), number +1)AS date
FROM
numbers;
Esto no funciona en todos los casos. SELECCIONE la fecha DESDE las fechas DONDE fecha ENTRE '2014-12-01' Y '2014-12-28'
ORDENAR
3
Buena llamada @ user927258. Esto se debe a que la primera vista datesmencionada anteriormente calcula las fechas a partir de la fecha actual, por lo que no podrá recuperar las fechas establecidas en el futuro. La respuesta de @RedFilter tiene el mismo defecto de diseño. Sin embargo, he agregado una solución en mi respuesta.
Stéphane
El uso de algunas vistas definitivamente simplifica las consultas y las hace reutilizables. Aunque esencialmente están haciendo lo mismo, todas esas UNIONcláusulas se ven raras en una sola declaración SQL.
Stewart
24
La respuesta aceptada no funcionó para PostgreSQL (error de sintaxis en o cerca de "a").
La forma de hacer esto en PostgreSQL es mediante el uso de la generate_seriesfunción, es decir:
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 una expresión de tabla común (CTE) recursiva, puede generar una lista de fechas y luego seleccionarla. Obviamente, normalmente no querría crear tres millones de fechas, por lo que esto solo ilustra las posibilidades. Simplemente podría limitar el rango de fechas dentro del CTE y omitir la cláusula where de la instrucción select usando el 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
Salida
Date
-----2014-01-2312:35:25.2502014-01-2212:35:25.2502014-01-2112:35:25.2502014-01-2012:35:25.250
Si solo me hubiera desplazado un poco más ... suspiro. De todos modos, gracias. Agregué un CAST (<expresión> COMO FECHA) para eliminar la hora en mi versión. También se usa donde a.Date entre GETDATE () - 365 AND GETDATE () ... si ejecuta su consulta hoy, no daría filas si no nota las fechas en WHERE = P
Ricardo C
4
La solución de la vieja escuela para hacer esto sin un bucle / cursor es crear una NUMBERStabla, que tiene una sola columna Entero con valores que comienzan en 1.
Debe completar la tabla con suficientes registros para cubrir sus necesidades:
INSERTINTO NUMBERS (id)VALUES(NULL);
Una vez que tenga la NUMBERSmesa, puede 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 generar listas de fechas o números para IZQUIERDA UNIRSE a. Debería hacer esto para ver dónde hay huecos en los datos, porque DEBE UNIRSE A una lista de datos secuenciales; los valores nulos harán obvio dónde existen huecos.
La DUALmesa está soportada por Oracle y MySQL para su uso como un sustituto de mesa en la FROMcláusula. No existe, la selección de valores devolverá el valor que sea. La idea era tener el suplente porque una consulta SELECT requiere una FROMcláusula que especifique al menos una tabla.
OMG Ponies
1
+1 para crear realmente una tabla de números permanente en lugar de hacer que el RDBMS la cree cada vez que necesite la consulta. ¡Las mesas auxiliares no son malas, gente!
Bacon Bits
4
Para Access 2010 : se requieren varios pasos; Seguí el mismo patrón que el publicado anteriormente, pero pensé que podría ayudar a alguien en Access. Me funcionó muy bien, no tuve que mantener una tabla de fechas sembrada.
Cree una tabla llamada DUAL (similar a cómo funciona la tabla Oracle DUAL)
ID (AutoNumber)
DummyColumn (Texto)
Agregue valores de una fila (1, "DummyRow")
Cree una consulta llamada "ZeroThru9Q"; ingrese manualmente la siguiente sintaxis:
thx Pentium10 - me hiciste unirme a stackoverflow :) - esta es mi portabilidad a msaccess - creo que funcionará en cualquier versión:
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 simplemente porque el acceso necesita una tabla que cuente con al menos 1 registro, en una cláusula from: cualquier tabla con al menos 1 registro funcionaría.
Como se indicó (o al menos aludió) en muchas de las maravillosas respuestas ya dadas, este problema se resuelve fácilmente una vez que tiene un conjunto de números para trabajar.
Nota: El siguiente es T-SQL, pero es simplemente mi implementación particular de conceptos generales ya mencionados aquí y en Internet en general. Debería ser relativamente simple convertir el código a su dialecto de elección.
¿Cómo? Considere esta consulta:
SELECT DATEADD(d, N,'0001-01-22')FROM Numbers -- A table containing the numbers 0 through NWHERE N <=5;
Lo anterior produce el rango de fechas 1/22/0001 - 1/27/0001 y es extremadamente trivial. Hay 2 datos clave en la consulta anterior: la fecha de inicio0001-01-22 y el desplazamiento de 5. Si combinamos estos dos datos, obviamente tenemos nuestra fecha de finalización. Por lo tanto, dadas dos fechas, la generación de un rango puede desglosarse así:
Encuentra la diferencia entre dos fechas dadas (el desplazamiento), fácil:
El uso ABS()aquí garantiza que el orden de fechas sea irrelevante.
Genere un conjunto limitado de números, también 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')
Tenga en cuenta que en realidad no nos importa lo que estamos seleccionando FROMaquí. Solo necesitamos un conjunto para trabajar para que podamos contar el número de filas en él. Yo personalmente uso un TVF, algunos usan un CTE, otros usan una tabla de números en su lugar, se entiende la idea. Abogo por el uso de la solución más eficiente que también comprenda.
La combinación de estos dos métodos resolverá nuestro 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));
El ejemplo anterior es un código horrible, pero demuestra cómo todo se une.
Más diversión
Necesito hacer mucho este tipo de cosas, así que encapsulé la lógica en dos TVF. El primero genera un rango de números y el segundo usa esta funcionalidad para generar un rango de fechas. La matemática es asegurar que el orden de entrada no importe y porque quería usar el rango completo de números disponibles enGenerateRangeSmallInt .
La siguiente función tarda ~ 16 ms de tiempo de CPU para devolver el rango máximo de 65536 fechas.
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
lo que resultará en
row1.02.03.04.0
Las filas ahora se pueden usar para crear una lista de fechas a partir de la fecha de inicio dada. Para incluir la fecha de inicio comenzamos con la fila -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
Explicación: explicación de la consulta recursiva CTE
Primera parte de la consulta:
SELECT DISTINCT convert(varchar(10), StartTime, 101) AS StartTime, datediff(dd, StartTime, endTime) AS diff FROM dbo.testdate
Explicación: la primera columna es "fecha de inicio", la segunda columna es la diferencia de la fecha de inicio y finalización en días y se considerará como columna "diff"
Segunda parte de la consulta:
UNION ALL SELECT StartTime, diff-1 AS diff FROM CTE WHERE diff<>0
Explicación: Union todos heredará el resultado de la consulta anterior hasta que el resultado sea nulo, por lo que el resultado "StartTime" se hereda de la consulta CTE generada, y de diff, disminución - 1, por lo que parece 3, 2 y 1 hasta 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
Tercera parte de la consulta
SELECT DISTINCT DateAdd(dd,diff, StartTime) AS StartTime FROM CTE
Agregará el día "diff" en "startdate", por lo que el resultado debe ser el siguiente
(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 cualquiera que quiera esto como una vista guardada (MySQL no admite sentencias select anidadas en las vistas):
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;
Entonces puedes hacer
select*from date_range
Llegar
date
---2017-06-062017-06-052017-06-042017-06-032017-06-02...
Solución elegante que utiliza la nueva funcionalidad recursiva (expresiones de tabla comunes) en MariaDB> = 10.3 y 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;
Lo anterior devuelve una tabla de fechas entre '2019-01-01' y '2019-04-30'. También es decentemente rápido. Devolver 1000 años de fechas (~ 365,000 días) toma alrededor de 400 ms en mi máquina.
Es una buena idea generar estas fechas sobre la marcha. Sin embargo, no me siento cómodo para hacer esto con un rango bastante amplio, así que terminé con la siguiente solución:
Creó una tabla "DatesNumbers" que contendrá los números utilizados para el cálculo de fechas:
CREATETABLE DatesNumbers (
i MEDIUMINT NOTNULL,PRIMARYKEY(i))
COMMENT='Used by Dates view';
Rellenó la tabla usando las técnicas anteriores con números desde -59999 hasta 40000. Este rango me dará fechas desde 59999 días (~ 164 años) hasta 40000 días (109 años) más adelante:
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
;
Creó una vista "Fechas":
SELECT
i,CURRENT_DATE()+ INTERVAL i DAY AS Date
FROM
DatesNumbers
Eso es.
(+) Consultas fáciles de leer
(+) No sobre la marcha números generaciones
(+) Da fechas en el pasado y en el futuro y no hay NINGUNA UNIÓN a la vista para esto como en esta publicación .
(+) Las fechas "Solo en el pasado" o "Solo en el futuro" se pueden filtrar usando WHERE i < 0oWHERE i > 0 (PK)
Use eso para, digamos, generar una tabla temporal, y luego haga una selección * en la tabla temporal. O envíe los resultados uno a la vez. Lo que dices que quieres hacer no se puede hacer con una instrucción SELECT , pero puede ser posible con cosas específicas de MySQL.
Por otra parte, tal vez necesite 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)
Versión SQLite de la solución superior de RedFilters
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'
Puede crear un procedimiento también para crear una tabla de calendario con un mapa de tiempo diferente del día.
Si quieres una mesa para cada trimestre
Una respuesta más genérica que funciona en 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 ''
Respuestas:
Esta solución no utiliza bucles, procedimientos o tablas temporales . La subconsulta genera fechas para los últimos 10,000 días, y podría extenderse para ir tan atrás o adelante como lo desee.
Salida:
Notas sobre rendimiento
Al probarlo aquí , el rendimiento es sorprendentemente bueno: la consulta anterior tarda 0.0009 segundos.
Si ampliamos la subconsulta para generar aprox. 100,000 números (y por lo tanto, alrededor de 274 años en fechas), se ejecuta en 0.0458 segundos.
Por cierto, esta es una técnica muy portátil que funciona con la mayoría de las bases de datos con ajustes menores.
Ejemplo de SQL Fiddle que devuelve 1,000 días
fuente
UNION
aUNION ALL
: es perder el tiempo buscando duplicados para eliminar que no existen. Sin embargo, es una OMI demasiado complicada: si va a construir un conjunto de resultados utilizando UNION, ¿por qué no solo especificar la fecha y terminar con ella?Aquí hay otra variación usando vistas:
Y luego puedes simplemente hacer (¿ves lo elegante que es?):
Actualizar
Vale la pena señalar que solo podrá generar fechas pasadas a partir de la fecha actual . Si desea generar cualquier tipo de rango de fechas (pasado, futuro y intermedio), deberá utilizar esta vista en su lugar:
fuente
dates
mencionada anteriormente calcula las fechas a partir de la fecha actual, por lo que no podrá recuperar las fechas establecidas en el futuro. La respuesta de @RedFilter tiene el mismo defecto de diseño. Sin embargo, he agregado una solución en mi respuesta.UNION
cláusulas se ven raras en una sola declaración SQL.La respuesta aceptada no funcionó para PostgreSQL (error de sintaxis en o cerca de "a").
La forma de hacer esto en PostgreSQL es mediante el uso de la
generate_series
función, es decir:fuente
Usando una expresión de tabla común (CTE) recursiva, puede generar una lista de fechas y luego seleccionarla. Obviamente, normalmente no querría crear tres millones de fechas, por lo que esto solo ilustra las posibilidades. Simplemente podría limitar el rango de fechas dentro del CTE y omitir la cláusula where de la instrucción select usando el CTE.
En Microsoft SQL Server 2005, la generación de la lista CTE de todas las fechas posibles tomó 1:08. Generar cien años tomó menos de un segundo.
fuente
Consulta MSSQL
Salida
fuente
La solución de la vieja escuela para hacer esto sin un bucle / cursor es crear una
NUMBERS
tabla, que tiene una sola columna Entero con valores que comienzan en 1.Debe completar la tabla con suficientes registros para cubrir sus necesidades:
Una vez que tenga la
NUMBERS
mesa, puede usar:La solución absoluta de baja tecnología sería:
¿Para qué lo usarías?
Para generar listas de fechas o números para IZQUIERDA UNIRSE a. Debería hacer esto para ver dónde hay huecos en los datos, porque DEBE UNIRSE A una lista de datos secuenciales; los valores nulos harán obvio dónde existen huecos.
fuente
DUAL
mesa está soportada por Oracle y MySQL para su uso como un sustituto de mesa en laFROM
cláusula. No existe, la selección de valores devolverá el valor que sea. La idea era tener el suplente porque una consulta SELECT requiere unaFROM
cláusula que especifique al menos una tabla.Para Access 2010 : se requieren varios pasos; Seguí el mismo patrón que el publicado anteriormente, pero pensé que podría ayudar a alguien en Access. Me funcionó muy bien, no tuve que mantener una tabla de fechas sembrada.
Cree una tabla llamada DUAL (similar a cómo funciona la tabla Oracle DUAL)
Cree una consulta llamada "ZeroThru9Q"; ingrese manualmente la siguiente sintaxis:
Cree una consulta llamada "TodayMinus1KQ" (para fechas anteriores a hoy); ingrese manualmente la siguiente sintaxis:
Cree una consulta llamada "TodayPlus1KQ" (para fechas posteriores a hoy); ingrese manualmente la siguiente sintaxis:
Cree una consulta de unión llamada "TodayPlusMinus1KQ" (para fechas +/- 1000 días):
Ahora puedes usar la consulta:
fuente
Procedimiento + tabla temporal:
fuente
thx Pentium10 - me hiciste unirme a stackoverflow :) - esta es mi portabilidad a msaccess - creo que funcionará en cualquier versión:
MSysObjects referenciados simplemente porque el acceso necesita una tabla que cuente con al menos 1 registro, en una cláusula from: cualquier tabla con al menos 1 registro funcionaría.
fuente
Como se indicó (o al menos aludió) en muchas de las maravillosas respuestas ya dadas, este problema se resuelve fácilmente una vez que tiene un conjunto de números para trabajar.
Nota: El siguiente es T-SQL, pero es simplemente mi implementación particular de conceptos generales ya mencionados aquí y en Internet en general. Debería ser relativamente simple convertir el código a su dialecto de elección.
¿Cómo? Considere esta consulta:
Lo anterior produce el rango de fechas 1/22/0001 - 1/27/0001 y es extremadamente trivial. Hay 2 datos clave en la consulta anterior: la fecha de inicio
0001-01-22
y el desplazamiento de5
. Si combinamos estos dos datos, obviamente tenemos nuestra fecha de finalización. Por lo tanto, dadas dos fechas, la generación de un rango puede desglosarse así:Encuentra la diferencia entre dos fechas dadas (el desplazamiento), fácil:
-- Returns 125 SELECT ABS(DATEDIFF(d, '2014-08-22', '2014-12-25'))
El uso
ABS()
aquí garantiza que el orden de fechas sea irrelevante.Genere un conjunto limitado de números, también 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')
Tenga en cuenta que en realidad no nos importa lo que estamos seleccionando
FROM
aquí. Solo necesitamos un conjunto para trabajar para que podamos contar el número de filas en él. Yo personalmente uso un TVF, algunos usan un CTE, otros usan una tabla de números en su lugar, se entiende la idea. Abogo por el uso de la solución más eficiente que también comprenda.La combinación de estos dos métodos resolverá nuestro problema:
El ejemplo anterior es un código horrible, pero demuestra cómo todo se une.
Más diversión
Necesito hacer mucho este tipo de cosas, así que encapsulé la lógica en dos TVF. El primero genera un rango de números y el segundo usa esta funcionalidad para generar un rango de fechas. La matemática es asegurar que el orden de entrada no importe y porque quería usar el rango completo de números disponibles en
GenerateRangeSmallInt
.La siguiente función tarda ~ 16 ms de tiempo de CPU para devolver el rango máximo de 65536 fechas.
fuente
prueba esto.
fuente
Desea obtener un intervalo de fechas.
En su ejemplo, le gustaría obtener las fechas entre '2010-01-20' y '2010-01-24'
solución posible:
Explicación
MySQL tiene una función date_add por lo que
Te regalaré
La función fechada le informaría a menudo que tendría que repetir esto
que vuelve
Obtener una lista de fechas en un rango de fechas se reduce a crear una secuencia de números enteros. generar una secuencia entera en MySQL
La respuesta más votada aquí ha tomado un enfoque similar al https://stackoverflow.com/a/2652051/1497139 como base:
lo que resultará en
Las filas ahora se pueden usar para crear una lista de fechas a partir de la fecha de inicio dada. Para incluir la fecha de inicio comenzamos con la fila -1;
fuente
Si alguna vez necesita más de un par de días, necesita una mesa.
Crear un rango de fechas en mysql
luego,
fuente
Generar fechas entre dos campos de fecha.
Si conoce la consulta SQL CTE, esta solución lo ayudará a resolver su pregunta.
Aquí hay un ejemplo
Tenemos fechas en una tabla.
Nombre de la tabla: "testdate"
Requerir resultado:
Solución:
Explicación: explicación de la consulta recursiva CTE
Primera parte de la consulta:
SELECT DISTINCT convert(varchar(10), StartTime, 101) AS StartTime, datediff(dd, StartTime, endTime) AS diff FROM dbo.testdate
Explicación: la primera columna es "fecha de inicio", la segunda columna es la diferencia de la fecha de inicio y finalización en días y se considerará como columna "diff"
Segunda parte de la consulta:
UNION ALL SELECT StartTime, diff-1 AS diff FROM CTE WHERE diff<>0
Explicación: Union todos heredará el resultado de la consulta anterior hasta que el resultado sea nulo, por lo que el resultado "StartTime" se hereda de la consulta CTE generada, y de diff, disminución - 1, por lo que parece 3, 2 y 1 hasta 0
Por ejemplo
Especificación de resultados
Tercera parte de la consulta
SELECT DISTINCT DateAdd(dd,diff, StartTime) AS StartTime FROM CTE
Agregará el día "diff" en "startdate", por lo que el resultado debe ser el siguiente
Resultado
fuente
Respuesta más corta que la aceptada, misma idea:
fuente
Para cualquiera que quiera esto como una vista guardada (MySQL no admite sentencias select anidadas en las vistas):
Entonces puedes hacer
Llegar
fuente
Solución elegante que utiliza la nueva funcionalidad recursiva (expresiones de tabla comunes) en MariaDB> = 10.3 y MySQL> = 8.0.
Lo anterior devuelve una tabla de fechas entre '2019-01-01' y '2019-04-30'. También es decentemente rápido. Devolver 1000 años de fechas (~ 365,000 días) toma alrededor de 400 ms en mi máquina.
fuente
Es una buena idea generar estas fechas sobre la marcha. Sin embargo, no me siento cómodo para hacer esto con un rango bastante amplio, así que terminé con la siguiente solución:
Eso es.
WHERE i < 0
oWHERE i > 0
(PK)fuente
Muy bien ... Pruebe esto: 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 eso para, digamos, generar una tabla temporal, y luego haga una selección * en la tabla temporal. O envíe los resultados uno a la vez.
Lo que dices que quieres hacer no se puede hacer con una instrucción SELECT , pero puede ser posible con cosas específicas de MySQL.
Por otra parte, tal vez necesite cursores: http://dev.mysql.com/doc/refman/5.0/en/cursors.html
fuente
Para Oracle, mi solución es:
Sysdate se puede cambiar a una fecha específica y el número de nivel se puede cambiar para dar más fechas.
fuente
si desea la lista de fechas entre dos fechas:
* violín aquí: http://sqlfiddle.com/#!6/9eecb/3469
fuente
fuente
fuente
Versión SQLite de la solución superior de RedFilters
fuente
mejorado con el día de la semana uniéndose a una tabla de vacaciones personalizada microsoft MSSQL 2012 para la tabla de fechas powerpivot https://gist.github.com/josy1024/cb1487d66d9e0ccbd420bc4a23b6e90e
fuente
fuente
Puede crear un procedimiento también para crear una tabla de calendario con un mapa de tiempo diferente del día. Si quieres una mesa para cada trimestre
p.ej
puedes usar
y luego manipular a través de
que te dan también ts
desde aquí puede comenzar a agregar otra información como
o crear una tabla real con la declaración de crear tabla
fuente
Una respuesta más genérica que funciona en AWS MySQL.
fuente
Una solución más para mysql 8.0.1 y mariadb 10.2.2 usando expresiones de tabla comunes recursivas:
fuente