Mysql
 sql >> Datenbank >  >> RDS >> Mysql

Wählen Sie jeden Monat aus, obwohl der Monat nicht in der MySQL-Tabelle vorhanden ist

Betrachten Sie das folgende Schema, wobei die dritte Tabelle die erwähnte Hilfstabelle für Jahr/Monat ist. Hilfstabellen sind weit verbreitet und können natürlich im gesamten Code wiederverwendet werden. Ich überlasse es Ihnen, es mit wesentlichen Datumsdaten zu füllen. Beachten Sie jedoch, wie das Enddatum für jeden Monat für diejenigen von uns zusammengestellt wurde, die weniger Arbeit erledigen möchten, während die db-Engine Schaltjahre für uns herausfinden kann.

Sie könnten nur eine Spalte in dieser Hilfstabelle haben. Aber das würde die Verwendung von Funktionsaufrufen für Enddaten in einigen Ihrer Funktionen erfordern, und das bedeutet mehr Langsamkeit. Wir mögen schnell.

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");

Hilfstabelle unten

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

Das ist also eine Hilfstabelle. Einmal einrichten und für ein paar Jahre vergessen

Hinweis :Vergessen Sie nicht, das obige Update stmt

auszuführen

Schnelltest für Ihre Anfrage

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 |
+----------+---------------+---------------+

Es funktioniert gut. Die erste MySQL-Tabelle ist die Helper-Tabelle. Eine Linksverknüpfung, um die Arbeiterdatensätze einzufügen (wobei Null zulässig ist). Machen wir hier eine Pause. Das war schließlich der Punkt Ihrer Frage:Fehlende Daten . Schließlich die Worker-Tabelle in einem Cross Join.

Der cross join ist das Initialisieren einer Variablen (@soloName ), das ist der Name des Arbeiters. Während der Nullstatus fehlender Daten, wie Sie es angefordert haben, problemlos über ifnull() abgeholt wird Funktion, die 0 zurückgibt, haben wir diesen Luxus für den Namen eines Arbeiters nicht. Das erzwingt den cross join .

Ein Kreuzjoin ist ein kartesisches Produkt. Aber da es sich um eine einzelne Zeile handelt, leiden wir nicht unter den normalen Problemen, die man mit Kartesianern bekommt, die zu viele Zeilen in der Ergebnismenge verursachen. Jedenfalls funktioniert es.

Aber hier ist ein Problem:Es ist zu schwierig, Werte an 6 Stellen zu pflegen und einzufügen, wie man sieht. Betrachten Sie also unten eine gespeicherte Prozedur dafür.

Gespeicherter Prozess

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 ;

Testen Sie die gespeicherte Prozedur mehrmals

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 ist viel einfacher, damit zu arbeiten (in PHP, c#, Java, wie Sie es nennen). Die Wahl liegt bei Ihnen, gespeicherter Prozess oder nicht.

Gespeicherter Bonusprozess

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 ;

Schnelltest davon

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 |
+----------+---------------+----------+-----------------+

Der Imbiss

Helfertische werden seit Jahrzehnten verwendet. Scheuen Sie sich nicht und schämen Sie sich nicht, sie zu benutzen. Tatsächlich ist es manchmal fast unmöglich, ohne sie zu versuchen, eine spezielle Arbeit zu erledigen.