Mysql
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> Mysql

Chọn từng tháng ngay cả khi tháng đó không tồn tại trong bảng mysql

Hãy xem xét lược đồ sau với bảng thứ 3 là Bảng người trợ giúp năm / tháng được đề cập. Bảng trợ giúp rất phổ biến và có thể được sử dụng lại trong suốt mã của bạn một cách tự nhiên. Tôi sẽ giao nó cho bạn để tải nó lên với dữ liệu ngày tháng quan trọng. Tuy nhiên, hãy lưu ý cách ngày kết thúc cho mỗi tháng được kết hợp với nhau cho những người trong chúng ta muốn làm ít công việc hơn, đồng thời cho phép công cụ db tính ra các năm nhuận cho chúng ta.

Bạn có thể chỉ có một cột trong bảng trợ giúp đó. Nhưng điều đó sẽ yêu cầu sử dụng các lệnh gọi hàm cho ngày kết thúc trong một số hàm của bạn và điều đó có nghĩa là chậm hơn. Chúng tôi thích nhanh chóng.

Lược đồ

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

Bảng trợ giúp bên dưới

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

Vì vậy, đó là một bảng trợ giúp. Thiết lập nó một lần và quên nó trong vài năm

Lưu ý :Đừng quên chạy bản cập nhật ở trên stmt

Kiểm tra nhanh cho truy vấn của bạn

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

Nó hoạt động tốt. Bảng mysql đầu tiên là bảng Người trợ giúp. Một phép nối bên trái để mang lại các bản ghi công nhân (cho phép null). Hãy tạm dừng ở đây. Đó chính là mấu chốt của câu hỏi của bạn: thiếu dữ liệu . Cuối cùng là bảng công nhân trong một phép nối chéo.

cross join là khởi tạo một biến (@soloName ) đó là tên của công nhân. Trong khi trạng thái trống của các ngày bị thiếu như bạn yêu cầu được chọn tốt qua ifnull() hàm trả về 0, chúng tôi không có sự xa xỉ đó đối với tên công nhân. Điều đó buộc cross join .

Một liên kết chéo là một sản phẩm của cartesian. Nhưng vì nó là một hàng duy nhất, chúng tôi không gặp phải các vấn đề bình thường mà người ta gặp phải với xe đẩy gây ra nhiều hàng trong tập kết quả. Dù sao, nó vẫn hoạt động.

Nhưng đây là một vấn đề:quá khó để duy trì và cắm các giá trị ở 6 vị trí như có thể thấy. Vì vậy, hãy xem xét bên dưới một proc được lưu trữ cho nó.

Proc được lưu trữ

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 ;

Kiểm tra proc đã lưu trữ một số lần

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, dễ dàng hơn để làm việc với (trong PHP, c #, Java, bạn đặt tên cho nó). Lựa chọn là của bạn, có lưu trữ hay không.

Phần thưởng được lưu trữ 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 ;

Kiểm tra nhanh nó

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

Takeaway

Bàn trợ giúp đã được sử dụng trong nhiều thập kỷ. Đừng sợ hoặc xấu hổ khi sử dụng chúng. Trên thực tế, đôi khi cố gắng hoàn thành một số công việc chuyên môn mà không có chúng là điều gần như không thể.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Cột không xác định trong mệnh đề Where

  2. Làm cách nào để sử dụng CakePHP baking để tìm mysql.sock và nhận ra MySQL trong khi sử dụng MAMP trên Mac OSX?

  3. Làm cách nào để sử dụng xóa thác trong mysql?

  4. Ví dụ DAYOFWEEK () - MySQL

  5. Làm cách nào để làm trễ các cột trong MySQL?