2012-04-05 21 views
6

Aquí es mi SQL:¿Cómo generar datos en MySQL?

SELECT 
    COUNT(id), 
    CONCAT(YEAR(created_at), '-', MONTH(created_at), '-', DAY(created_at)) 
FROM my_table 
GROUP BY YEAR(created_at), MONTH(created_at), DAY(created_at) 

Quiero una fila para aparecer incluso para los días en que no había ID creado. En este momento me faltan muchas fechas para los días en los que no había actividad.

¿Alguna idea sobre cómo cambiar esta consulta para hacer eso?

Respuesta

1

La manera de hacerlo en una consulta:

SELECT COUNT(my_table.id) AS total, 
CONCAT(YEAR(dates.ddate), '-', MONTH(dates.ddate), '-', DAY(dates.ddate)) 
FROM (
    -- Creates "on the fly" 65536 days beginning from 2000-01-01 (179 years) 
    SELECT DATE_ADD("2000-01-01", INTERVAL (b1.b + b2.b + b3.b + b4.b + b5.b + b6.b + b7.b + b8.b + b9.b + b10.b + b11.b + b12.b + b13.b + b14.b + b15.b + b16.b) DAY) AS ddate FROM 
    (SELECT 0 AS b UNION SELECT 1) b1, 
    (SELECT 0 AS b UNION SELECT 2) b2, 
    (SELECT 0 AS b UNION SELECT 4) b3, 
    (SELECT 0 AS b UNION SELECT 8) b4, 
    (SELECT 0 AS b UNION SELECT 16) b5, 
    (SELECT 0 AS b UNION SELECT 32) b6, 
    (SELECT 0 AS b UNION SELECT 64) b7, 
    (SELECT 0 AS b UNION SELECT 128) b8, 
    (SELECT 0 AS b UNION SELECT 256) b9, 
    (SELECT 0 AS b UNION SELECT 512) b10, 
    (SELECT 0 AS b UNION SELECT 1024) b11, 
    (SELECT 0 AS b UNION SELECT 2048) b12, 
    (SELECT 0 AS b UNION SELECT 4096) b13, 
    (SELECT 0 AS b UNION SELECT 8192) b14, 
    (SELECT 0 AS b UNION SELECT 16384) b15, 
    (SELECT 0 AS b UNION SELECT 32768) b16 
) dates 
LEFT JOIN my_table ON dates.ddate = my_table.created_at 
GROUP BY dates.ddate 
ORDER BY dates.ddate 

El código siguiente es sólo es necesario si desea probar y no tiene la "mi_tabla" indica en la pregunta:

create table `my_table` (
    `id` int (11), 
    `created_at` date 
); 
insert into `my_table` (`id`, `created_at`) values('1','2000-01-01'); 
insert into `my_table` (`id`, `created_at`) values('2','2000-01-01'); 
insert into `my_table` (`id`, `created_at`) values('3','2000-01-01'); 
insert into `my_table` (`id`, `created_at`) values('4','2001-01-01'); 
insert into `my_table` (`id`, `created_at`) values('5','2100-06-06'); 
9

SQL es notoriamente malo al devolver datos que no están en la base de datos. Puede encontrar los valores iniciales y finales para huecos de fechas, pero obtener todas las fechas es difícil.

La solución es crear una tabla de calendario con un registro para cada fecha y OUTER ÚNASE a su consulta.

Aquí es un ejemplo suponiendo que created_at es de tipo FECHA:

SELECT calendar_date, COUNT(`id`) 
FROM calendar LEFT OUTER JOIN my_table ON calendar.calendar_date = my_table.created_at 
GROUP BY calendar_date 

(que supongo que es realmente created_at DATETIME, por lo que tendrá que hacer un poco más de gimnasia para unir las tablas).

+1

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

+1

Para ser claros, de hecho, no hay una buena solución a este problema usando SQL. –

+0

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). –

7

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!

+1

Woah - súper completo. ¿Es posible crear una tabla temporal para consultar contra en una declaración? –

+0

Puede [crear tablas temporales] (http://dev.mysql.com/doc/refman/5.6/en/create-table.html) en una sola declaración. Sin embargo, no creo que esto sea útil porque (lo que creo que planea hacer) crearía la tabla, la poblaría, la consultaría y luego la eliminaría.Sería mejor tener la tabla llena o usar una tabla derivada (como en el enfoque dinámico: 'select * from (derived_table) dt left join ...') porque la parte costosa del procedimiento es la población del mesa. –

0

Banco de pruebas:

create table testbed (id integer, created_at date); 
insert into testbed values 
     (1, '2012-04-01'), 
     (1, '2012-04-30'), 
     (2, '2012-04-02'), 
     (3, '2012-04-03'), 
     (3, '2012-04-04'), 
     (4, '2012-04-04'); 

También uso any_table, que he creado artificialmente como esto:

create table any_table (id integer); 
insert into any_table values (1), (2), (3), (4), (5), (6), (7), (8), (9), (10); 
insert into any_table select * from any_table; -- repeat this insert 7-8 times 

Se puede utilizar cualquier tabla en su base de datos que se espera que tenga más filas a continuación max(created_dt) - min(created_dt) gama , al menos 365 para cubrir un año.

Consulta:

SELECT concat(year(dr._date),'-',month(dr._date),'-',day(dr._date)), 
     -- or, instead of concat(), simply: dr._date 
     count(id) 
    FROM (
     SELECT date_add(r.mindt, INTERVAL @dist day) _date, 
       @dist := @dist + 1 AS days_away 
      FROM any_table t 
      JOIN (SELECT min(created_at) mindt, 
         max(created_at) maxdt, 
         @dist := 0 
        FROM testbed) r 
     WHERE date_add(r.mindt, INTERVAL @dist day) <= r.maxdt) dr 
    LEFT JOIN testbed tb ON dr._date = tb.created_at 
GROUP BY dr._date; 
Cuestiones relacionadas