Betrakta följande schema där den tredje tabellen är den nämnde hjälptabellen för år/månad. Hjälptabeller är mycket vanliga och kan naturligt återanvändas genom hela din kod. Jag överlåter åt dig att ladda upp den med betydande datumdata. Notera dock hur slutdatumet för varje månad sattes ihop för de av oss som vill göra mindre arbete, samtidigt som det låter db-motorn räkna ut skottår för oss.
Du kan bara ha en kolumn i den hjälptabellen. Men det skulle kräva användning av funktionsanrop för slutdatum i vissa av dina funktioner och det innebär mer långsamhet. Vi gillar snabbt.
Schema
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");
Hjälptabell nedan
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
Så det är ett hjälpbord. Ställ in det en gång och glöm det i några år
Obs :Glöm inte att köra ovanstående uppdatering stmt
Snabbtest för din fråga
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 |
+----------+---------------+---------------+
Det fungerar bra. Den första mysql-tabellen är Helper-tabellen. En vänsteranslutning för att få in arbetarregistren (medger null). Låt oss pausa här. Det var trots allt poängen med din fråga:data saknas . Slutligen arbetarbordet i en korsfogning.
cross join
är att initiera en variabel (@soloName
) det är arbetarens namn. Medan nollstatusen för saknade datum som du begärde plockas upp bra via ifnull()
funktion som returnerar 0, vi har inte den lyxen för en arbetares namn. Det tvingar fram cross join
.
En korsfogning är en kartesisk produkt. Men eftersom det är en enda rad, lider vi inte av de normala problem man får med kartesianer som orsakar alldeles för många rader i resultatuppsättningen. Hur som helst, det fungerar.
Men här är ett problem:det är för svårt att upprätthålla och koppla in värden på 6 ställen som kan ses. Så överväg nedan en lagrad proc för det.
Lagrad proc
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 ;
Testa den lagrade processen ett antal gånger
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, mycket lättare att arbeta med (i PHP, c#, Java, you name it). Valet är ditt, lagrad proc eller inte.
Bonus lagrad proc
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 ;
Snabbtest av det
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 |
+----------+---------------+----------+-----------------+
The Takeaway
Hjälpbord har använts i decennier. Var inte rädd eller generad för att använda dem. Faktum är att det ibland är nästan omöjligt att försöka få något specialarbete gjort utan dem.