Considere el siguiente esquema en el que la tercera tabla es la Tabla auxiliar de año/mes mencionada. Las tablas auxiliares son muy comunes y se pueden reutilizar en todo el código de forma natural. Dejaré que usted lo cargue con datos sustanciales de fechas. Tenga en cuenta, sin embargo, la forma en que se armó la fecha de finalización de cada mes para aquellos de nosotros que queremos hacer menos trabajo, mientras permitimos que el motor de base de datos calcule los años bisiestos por nosotros.
Podría tener solo una columna en esa tabla auxiliar. Pero eso requeriría el uso de llamadas a funciones para las fechas de finalización en algunas de sus funciones y eso significa más lentitud. Nos gusta lo rápido.
Esquema
create table workerRecords
( id int auto_increment primary key,
the_date date not null,
staff_no int not null
);
-- truncate workerRecords;
insert workerRecords(the_date,staff_no) values
('2016-06-10',1),
('2016-06-09',1),
('2016-05-09',1),
('2016-04-09',1),
('2016-03-02',2),
('2016-07-02',2);
create table workers
( staff_no int primary key,
full_name varchar(100) not null
);
-- truncate workers;
insert workers(staff_no,full_name) values
(1,'David Higgins'),(2,"Sally O'Riordan");
Tabla de ayuda a continuación
create table ymHelper
( -- Year Month helper table. Used for left joins to pick up all dates.
-- PK is programmer's choice.
dtBegin date primary key, -- by definition not null
dtEnd date null
);
-- truncate ymHelper;
insert ymHelper (dtBegin,dtEnd) values
('2015-01-01',null),('2015-02-01',null),('2015-03-01',null),('2015-04-01',null),('2015-05-01',null),('2015-06-01',null),('2015-07-01',null),('2015-08-01',null),('2015-09-01',null),('2015-10-01',null),('2015-11-01',null),('2015-12-01',null),
('2016-01-01',null),('2016-02-01',null),('2016-03-01',null),('2016-04-01',null),('2016-05-01',null),('2016-06-01',null),('2016-07-01',null),('2016-08-01',null),('2016-09-01',null),('2016-10-01',null),('2016-11-01',null),('2016-12-01',null),
('2017-01-01',null),('2017-02-01',null),('2017-03-01',null),('2017-04-01',null),('2017-05-01',null),('2017-06-01',null),('2017-07-01',null),('2017-08-01',null),('2017-09-01',null),('2017-10-01',null),('2017-11-01',null),('2017-12-01',null),
('2018-01-01',null),('2018-02-01',null),('2018-03-01',null),('2018-04-01',null),('2018-05-01',null),('2018-06-01',null),('2018-07-01',null),('2018-08-01',null),('2018-09-01',null),('2018-10-01',null),('2018-11-01',null),('2018-12-01',null),
('2019-01-01',null),('2019-02-01',null),('2019-03-01',null),('2019-04-01',null),('2019-05-01',null),('2019-06-01',null),('2019-07-01',null),('2019-08-01',null),('2019-09-01',null),('2019-10-01',null),('2019-11-01',null),('2019-12-01',null);
-- will leave as an exercise for you to add more years. Good idea to start, 10 in either direction, at least.
update ymHelper set dtEnd=LAST_DAY(dtBegin); -- data patch. Confirmed leap years.
alter table ymHelper modify dtEnd date not null; -- there, ugly patch above worked fine. Can forget it ever happened (until you add rows)
-- show create table ymHelper; -- this confirms that dtEnd is not null
Esa es una mesa auxiliar. Configúralo una vez y olvídate de él durante unos años
Nota :No olvide ejecutar la instrucción de actualización anterior
Prueba rápida para su consulta
SELECT DATE_FORMAT(ymH.dtBegin,'%b %Y') as month,
ifnull(COUNT(wr.the_date),0) as total_records,@soloName as full_name
FROM ymHelper ymH
left join workerRecords wr
on wr.the_date between ymH.dtBegin and ymH.dtEnd
and wr.staff_no = 1 and wr.the_date between '2016-04-01' and '2016-07-31'
LEFT JOIN workers w on w.staff_no = wr.staff_no
cross join (select @soloName:=full_name from workers where staff_no=1) xDerived
WHERE ymH.dtBegin between '2016-04-01' and '2016-07-31'
GROUP BY ymH.dtBegin
order by ymH.dtBegin;
+----------+---------------+---------------+
| month | total_records | full_name |
+----------+---------------+---------------+
| Apr 2016 | 1 | David Higgins |
| May 2016 | 1 | David Higgins |
| Jun 2016 | 2 | David Higgins |
| Jul 2016 | 0 | David Higgins |
+----------+---------------+---------------+
Funciona bien. La primera tabla mysql es la tabla auxiliar. Una combinación izquierda para traer los registros del trabajador (permitiendo nulo). Hagamos una pausa aquí. Después de todo, ese era el punto de su pregunta:datos faltantes . Finalmente la mesa del trabajador en una unión cruzada.
La cross join
es inicializar una variable (@soloName
) que es el nombre del trabajador. Mientras que el estado nulo de las fechas que faltan como lo solicitó se detecta bien a través de ifnull()
función que devuelve 0, no tenemos ese lujo para el nombre de un trabajador. Eso fuerza la cross join
.
Una combinación cruzada es un producto cartesiano. Pero dado que es una sola fila, no sufrimos los problemas normales que uno tiene con los cartesianos que causan muchas filas en el conjunto de resultados. De todos modos, funciona.
Pero aquí hay un problema:es demasiado difícil de mantener y conectar valores en 6 lugares, como se puede ver. Así que considere a continuación un proceso almacenado para ello.
Proceso almacenado
drop procedure if exists getOneWorkersRecCount;
DELIMITER $$
create procedure getOneWorkersRecCount
(pStaffNo int, pBeginDt date, pEndDt date)
BEGIN
SELECT DATE_FORMAT(ymH.dtBegin,'%b %Y') as month,ifnull(COUNT(wr.the_date),0) as total_records,@soloName as full_name
FROM ymHelper ymH
left join workerRecords wr
on wr.the_date between ymH.dtBegin and ymH.dtEnd
and wr.staff_no = pStaffNo and wr.the_date between pBeginDt and pEndDt
LEFT JOIN workers w on w.staff_no = wr.staff_no
cross join (select @soloName:=full_name from workers where staff_no=pStaffNo) xDerived
WHERE ymH.dtBegin between pBeginDt and pEndDt
GROUP BY ymH.dtBegin
order by ymH.dtBegin;
END$$
DELIMITER ;
Pruebe el proceso almacenado varias veces
call getOneWorkersRecCount(1,'2016-04-01','2016-06-09');
call getOneWorkersRecCount(1,'2016-04-01','2016-06-10');
call getOneWorkersRecCount(1,'2016-04-01','2016-07-01');
call getOneWorkersRecCount(2,'2016-02-01','2016-11-01');
Ah, es mucho más fácil trabajar con él (en PHP, C#, Java, lo que sea). La elección es suya, proceso almacenado o no.
Proceso almacenado de bonificación
drop procedure if exists getAllWorkersRecCount;
DELIMITER $$
create procedure getAllWorkersRecCount
(pBeginDt date, pEndDt date)
BEGIN
SELECT DATE_FORMAT(ymH.dtBegin,'%b %Y') as month,ifnull(COUNT(wr.the_date),0) as total_records,w.staff_no,w.full_name
FROM ymHelper ymH
cross join workers w
left join workerRecords wr
on wr.the_date between ymH.dtBegin and ymH.dtEnd
and wr.staff_no = w.staff_no and wr.the_date between pBeginDt and pEndDt
-- LEFT JOIN workers w on w.staff_no = wr.staff_no
-- cross join (select @soloName:=full_name from workers ) xDerived
WHERE ymH.dtBegin between pBeginDt and pEndDt
GROUP BY ymH.dtBegin,w.staff_no,w.full_name
order by ymH.dtBegin,w.staff_no;
END$$
DELIMITER ;
Prueba rápida
call getAllWorkersRecCount('2016-03-01','2016-08-01');
+----------+---------------+----------+-----------------+
| month | total_records | staff_no | full_name |
+----------+---------------+----------+-----------------+
| Mar 2016 | 0 | 1 | David Higgins |
| Mar 2016 | 1 | 2 | Sally O'Riordan |
| Apr 2016 | 1 | 1 | David Higgins |
| Apr 2016 | 0 | 2 | Sally O'Riordan |
| May 2016 | 1 | 1 | David Higgins |
| May 2016 | 0 | 2 | Sally O'Riordan |
| Jun 2016 | 2 | 1 | David Higgins |
| Jun 2016 | 0 | 2 | Sally O'Riordan |
| Jul 2016 | 0 | 1 | David Higgins |
| Jul 2016 | 1 | 2 | Sally O'Riordan |
| Aug 2016 | 0 | 1 | David Higgins |
| Aug 2016 | 0 | 2 | Sally O'Riordan |
+----------+---------------+----------+-----------------+
La comida para llevar
Las mesas auxiliares se han utilizado durante décadas. No tengas miedo ni te avergüences de usarlos. De hecho, tratar de hacer un trabajo especial sin ellos es casi imposible a veces.