假设我在mysql中有这两个表。
表1:
date staff_no
2016-06-10 1
2016-06-09 1
2016-05-09 1
2016-04-09 1
表2:
staff_no name
1 David
然后,我有这个查询来分析每个月的员工:
SELECT DATE_FORMAT(table1.date,'%b %Y') as month,COUNT(table1.date) as total_records,table2.name as name
FROM table1 as table1
LEFT JOIN table2 as table2 on table2.staff_no = table1.staff_no
WHERE table1.staff_no = "1" and date(table1.date) between = "2016-04-01" and "2016-06-30"
GROUP BY table2.name,DATE_FORMAT(table1.date,'%Y-%m')
ORDER BY DATE_FORMAT(table1.date,'%Y-%m-%d')
此查询将输出:
month total_records name
Apr 2016 1 David
May 2016 1 David
Jun 2016 2 David
但是,如果我从查询中替换“2016-04-01”和“2016-07-31”之间的日期,它就不会显示7月记录,因为它不存在于table1中,这不是我想要的。我仍然希望得到这样的结果:
month total_records name
Apr 2016 1 David
May 2016 1 David
Jun 2016 2 David
Jul 2016 0 David
有人这方面的专家吗?请帮助我。谢谢!
答案 0 :(得分:1)
考虑以下模式,第3个表是提到的年/月助手表。辅助表非常常见,可以自然地在整个代码中重复使用。我会留给你加载大量的日期数据。但是请注意每个月的结束日期是为那些想要做更少工作的人组合在一起的,同时允许数据库引擎为我们计算闰年。
您可以在该帮助程序表中只有一列。但是这需要在一些函数中使用函数调用结束日期,这意味着更慢。我们喜欢快速。
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");
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
这是一个帮助表。设置一次并忘记它几年
注意:不要忘记运行以上更新stmt
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 |
+----------+---------------+---------------+
工作正常。第一个mysql表是Helper表。左连接以引入工作记录(允许为空)。我们暂停一下这就是你问题的重点:缺少数据。最后是一个交叉连接的工作表。
cross join
用于初始化作为工作者姓名的变量(@soloName
)。虽然您请求的缺失日期的空状态通过返回0的ifnull()
函数被正确选取,但我们没有工作人员姓名的奢侈品。这迫使cross join
。
交叉连接是笛卡尔积。但由于它是一行,我们不会遇到笛卡尔因导致结果集中许多行的正常问题。无论如何,它有效。
但是这里有一个问题:可以看出,在6个地方维护和插入值太难了。因此,请考虑下面的存储过程。
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 ;
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');
啊,更容易使用(在PHP,c#,Java,你的名字)。选择是你的,存储过程与否。
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 ;
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 |
+----------+---------------+----------+-----------------+
助手表已经使用了数十年。不要害怕或不好意思使用它们。事实上,试图在没有它们的情况下完成一些专业工作有时几乎是不可能的。
答案 1 :(得分:1)
您可以使用系统中的任何其他表格来构建一个表示所需日期的内联变量,这些表格至少是您尝试表示的月数,即使数据不必具有日期。只有记录,你可以限制。
尝试使用MySql变量的以下语句。 FROM子句声明一个内联到SQL语句的变量" @ Date1"。我将从2016年3月1日开始。现在,选择字段列表将获取该变量,并一次向其添加1个月。因为它与" AnyTableWithAtLeast12Records" (字面上系统中至少有X个记录的任何表),它将创建一个显示日期的结果。这是强制日历类型列表的一种方法。
但请注意,此选择中的SECOND列不会通过:=赋值更改@ Date1。因此,它需要现在的日期,并在结束日期为其添加另一个月。如果您需要更小或更大的日期范围,只需更改记录限制即可创建日历范围......
select
@Date1 := date_add( @Date1, interval 1 month ) StartDate,
date_add( @Date1, interval 1 month ) EndDate
from
AnyTableWithAtLeast12Records,
( select @Date1 := '2016-03-01' ) sqlvars
limit 12;
结果就像......
StartDate EndDate
2016-04-01 2016-05-01
2016-05-01 2016-06-01
2016-06-01 2016-07-01
2016-07-01 2016-08-01
2016-08-01 2016-09-01
2016-09-01 2016-10-01
2016-10-01 2016-11-01
2016-11-01 2016-12-01
2016-12-01 2017-01-01
2017-01-01 2017-02-01
2017-02-01 2017-03-01
2017-03-01 2017-04-01
现在你拥有了自己的动态"日历"在一个简单的查询中完成。现在,使用它作为您需要的所有记录的基础,并按照您的格式进行格式化。因此,将上面的整个查询作为JOIN来查找这些日期范围内的记录...不需要其他查询或存储过程。现在,一个简单的LEFT JOIN将保留所有日期,但仅在每个开始/结束的范围之间显示那些有工作人员的日期。所以例如:大于或等于2016年1月1日,但是不久那么2016年1月5日,包括2016年4月30日晚上11:59:59。
SELECT
DATE_FORMAT(MyCalendar.StartDate,'%b %Y') as month,
COALESCE(COUNT(T1.Staff_no),0) as total_records,
COALESCE(T2.name,"") as name
FROM
( select @Date1 := date_add( @Date1, interval 1 month ) StartDate,
date_add( @Date1, interval 1 month ) EndDate
from
AnyTableWithAtLeast12Records,
( select @Date1 := '2016-03-01' ) sqlvars
limit 12 ) MyCalendar
LEFT JOIN table1 T1
ON T1.Date >= MyCalendar.StartDate
AND T1.Date < MyCalendar.EndDate
AND T1.Staff_No = 1
LEFT JOIN table2 T2
ON T1.staff_no = T2.StaffNo
GROUP BY
T2.name,
DATE_FORMAT(MyCalendar.StartDate,'%Y-%m')
ORDER BY
DATE_FORMAT(MyCalendar.StartDate,'%Y-%m-%d')
答案 2 :(得分:0)
我想说你需要在这里加入RIGHT JOIN以包括第二张桌子的工作人员