idea general
Hay dos enfoques principales para la generación de datos en MySQL. Uno es generar los datos sobre la marcha al ejecutar la consulta y el otro es tenerlo en la base de datos y usarlo cuando sea necesario. Por supuesto, el segundo sería más rápido que el primero si va a ejecutar su consulta con frecuencia. Sin embargo, el segundo requerirá una tabla en la base de datos que solo tendrá como objetivo generar los datos faltantes. También requerirá que tenga privilegios suficientes para crear esa tabla.
generación de los datos dinámicos
Este enfoque implica hacer UNION
s para generar una tabla falsa que se puede utilizar para unirse a la tabla real con. La consulta horrible y repetitiva es:
select aDate from (
select @maxDate - interval (a.a+(10*b.a)+(100*c.a)+(1000*d.a)) day aDate 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) a, /*10 day range*/
(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) b, /*100 day range*/
(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) c, /*1000 day range*/
(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) d, /*10000 day range*/
(select @minDate := '2001-01-01', @maxDate := '2002-02-02') e
) f
where aDate between @minDate and @maxDate
De todos modos, es más simple de lo que parece. Fabrica productos cartesianos de tablas derivadas con valores numéricos de 10
, por lo que el resultado tendrá 10^X
filas donde X
es la cantidad de tablas derivadas en la consulta. En este ejemplo, hay 10000
rango de días, por lo que podría representar períodos de más de 27
años. Si necesita más, agregue otro UNION
a la consulta y actualice el intervalo, y si no necesita tantos puede eliminar UNION
so valores individuales de las tablas derivadas. Solo para aclarar, puede ajustar el período de la fecha aplicando un filtro con una cláusula WHERE
en las variables @minDate
y @maxDate
(pero no use un período más largo que el que creó con los productos cartesianos).
generación de los datos estáticos
Esta solución se requieren para generar una tabla en la base de datos. El enfoque es similar al anterior. Primero tendrá que insertar datos en esa tabla: un rango de números enteros que van desde 1
a X
donde X
es el rango máximo necesario.De nuevo, si no está seguro solo inserte los valores 100000
y podrá crear rangos de días por más de 273
años. Así, una vez que tienes la secuencia entera, puede transformarlo en un intervalo de fechas así:
select '2012-01-01' + interval value - 1 day aDay from seq
having aDay <= '2012-01-05'
Suponiendo una tabla llamada seq
con una columna llamada value
. En la parte superior, desde fecha y en la parte inferior hasta fecha.
convertir esto en algo útil
Ok, ahora tenemos nuestros períodos de fecha generaron pero todavía les falta una forma de consulta de datos y mostrar los valores que faltan como un real 0
. Aquí es donde left join
viene al rescate. Para asegurarse de que todos estamos en la misma página, un left join
es similar a un inner join
pero con una sola diferencia: conservará todos los registros de la tabla izquierda de la combinación, independientemente de si hay un registro coincidente en la tabla de el derecho. En otras palabras, un inner join
eliminará todas las filas no coincidentes de la unión, mientras que left join
mantendrá las de la tabla de la izquierda y, para los registros de la izquierda que no tienen registro coincidente en la tabla correcta, left join
completará esa "espacio" con un valor null
.
Entonces deberíamos unirnos a nuestra tabla de dominio (la que tiene datos "faltantes") con nuestra tabla recién generada poniendo esta última en la parte izquierda de la unión y la primera en la derecha, para que todos los elementos sean considerados, independientemente de su presencia en la tabla de dominio.
Por ejemplo, si tuviéramos una mesa domainTable
con campos ID, birthDate
y nos gustaría ver un recuento de todos los birthDate
en los primeros 5
días de 2012
por día y si el recuento es 0
para mostrar ese valor, entonces este consulta podría ejecutarse:
select allDays.aDay, count(dt.id) from (
select '2012-01-01' + interval value - 1 day aDay from seq
having aDay <= '2012-01-05'
) allDays
left join domainTable dt on allDays.aDay = dt.birthDate
group by allDays.aDay
Esto genera una tabla derivada con todos los días requried (noto que estoy usando la generación de datos estáticos) y realiza una left join
en contra de nuestra tabla de dominio, por lo que se mostrarán todos los días, sin tener en cuenta de si tienen valores coincidentes en nuestras tablas de dominio. También tenga en cuenta que el count
se debe hacer en el campo que tendrá los valores null
ya que estos no se cuentan.
Notas a tener en cuenta
1) Las consultas se pueden utilizar para consultar otros intervalos (meses, años) realizar pequeños cambios en el código
2) En lugar de codificar las fechas puede consultar para min
y max
los valores de las tablas de dominio como este:
select (select min(aDate) from domainTable) + interval value - 1 day aDay
from seq
having aDay <= (select max(aDate) from domainTable)
esto evitaría la generación de más registros de lo necesario.
En realidad respondiendo a la pregunta
Creo que ya debería haber descubierto la manera de hacer lo que quiera. De todos modos, aquí están los pasos para que otros puedan beneficiarse de ellos también.En primer lugar, cree la tabla de enteros. En segundo lugar, ejecute esta consulta:
select allDays.aDay, count(mt.id) aCount from (
select (select date(min(created_at)) from my_table) + interval value - 1 day aDay
from seq s
having aDay <= (select date(max(created_at)) from my_table)
) allDays
left join my_table mt on allDays.aDay = date(mt.created_at)
group by allDays.aDay
supongo created_at
es una fecha y hora y es por eso que estás concatenación de esa manera. Sin embargo, esa es la forma en que MySQL almacena las fechas de forma nativa, por lo que solo estoy agrupando por el campo de fecha, pero lanzando el created_at
a un tipo de datos real date
. Puede jugar con este usando fiddle.
y aquí está la solución de generación de datos de forma dinámica:
select allDays.aDay, count(mt.id) aCount from (
select @maxDate - interval a.a day aDay 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) a, /*10 day range*/
(select @minDate := (select date(min(created_at)) from my_table),
@maxDate := (select date(max(created_at)) from my_table)) e
where @maxDate - interval a.a day between @minDate and @maxDate
) allDays
left join my_table mt on allDays.aDay = date(mt.created_at)
group by allDays.aDay
Como se puede ver el esqueleto de la consulta es el mismo que el anterior. Lo único que cambia es cómo se genera la tabla derivada allDays
. Ahora, la forma en que se genera la tabla derivada también es ligeramente diferente de la que agregué antes. Esto se debe a que en el ejemplo filddle solo necesitaba un rango de 10
-días. Como puede ver, es más legible que agregar un rango de día de 1000
. Aquí está el fiddle para la solución dinámica para que pueda jugar con él también.
Espero que esto ayude!
Esta es realmente su única opción a menos que pueda crear esas entradas que faltan dentro de su código después de Sele cting los registros que tienes. Sin embargo, tenga en cuenta que deberá mantener esta tabla calendario_fecha llena de fechas y esperar que no olvide agregar más de lo que necesita actualmente. (¿Cuántos años en el futuro irás?) Personalmente, no me gusta esta idea porque también te restringe a la agrupación por el intervalo de fechas que elegiste. ¿Qué pasa si mañana quieres mostrar las cosas agrupadas por hora? – Vyrotek
Para ser claros, de hecho, no hay una buena solución a este problema usando SQL. –
Los archivos de calendario son útiles para una gran cantidad de cosas (especialmente en situaciones de venta minorista, donde el calendario fiscal no siempre se asigna a la gregoriana), incluido este problema en particular. Puede crear enunciados virtuales in-statement ... con CTE recursivos (no presentes en mySQL). –