计算工资界面
老板为工人发工资,需要统计工资。统计结果如下:
这里的计算是非常复杂的,如:张三给老李、老王、小刘都送过水怎么办?所以要计算张三的送水数量,还要给这个数量加上一个时间的区间,比如说一个月统计一次
在写sql之前要知道:
1.送水工 基本工资 每桶提成 送水数量 实发工资
2.实发工资= (送水数量 * 每桶提成)+基本工资
3.前三列都是worker表的(不用计算) 后面的送水数量和始发工资需要我们计算 数量 实发工资
4.先查 SELECT * FROM tb_worker
得到我们想要的数据,然后依照得到的数据再去查询
用左连接去写 :
实发工资= (送水数量 * 每桶提成)+基本工资 红色的条件已经拿到,目前缺少送水的数量
而送水的数量在 tb_history表里,既然是求数量就要使用聚合函数sum(),可以写成SUM (h.send_water_count) 还可以给它起一个别名send_water_count,写成 AS send_water_count,
再给它们一个分组GROUP BY w . wid就可以了(不加分组出不来)
我们数量拿到之后就应该去求实发工资了
SUM (send_water_count * worker_money)+worker_salary
然后可以给它起一个别名(方便查询后展示用的)
5.此时出现了一个问题,没有送水的话也要给他发基本工资,所以不能是空的。(如果送水数量为null,默认显示0),解决方法:要在sql语句中进行一个判断IFNULL(expr1,expr2)
把SUM (h.send_water_count)丢进去,然后expr2为0(意思就是:如果总数量为空就是0)
6.如果实发工资为null,默认显示基本工资
通用的判断操作:LFNULL(SUM (send_water_count * worker_money)+worker_salary , w.worker_salary)
7.具体展示:
SELECT w.worker_name, w.worker_salary,w.worker_money,
-- 如果送水数量为null,默认显示0
IFNULL(SUM(h.send_water_count),0) AS send_water_count,
-- 如果实发工资为null,默认显示基本工资
IFNULL(SUM(send_water_count * w.worker_money)+w.worker_money,w.worker_salary) AS final_salary
FROM
tb_worker w LEFT JOIN tb_history h ON w.wid = h.worker_id
GROUP BY w.wid
1.1 定义计算工资Salary实体类
这个实体类数据库中并没有这张表,这个实体类是我们专门封装的一个类,就是把各个表中需要的字段封装成一个实体类
package com.minzu.entities;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;/*** TODO: 计算工资相关的实体类* @author caojie* @version 1.0*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Salary {/*** 送水工名称*/private String workerName;/*** 送水工底薪*/private Integer workerSalary;/*** 送水工提成*/private Double workerMoney;/*** 送水数量*/private Integer sendWaterCount;/*** 实发工资*/private Double finalSalary;
}
1.2 定义SalaryMapper接口
package com.minzu.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.minzu.entities.Customer;
import com.minzu.entities.Salary;
import org.springframework.stereotype.Repository;import java.util.List;/*** TODO: 计算工资映射器* @author caojie* @version 1.0*/
@Repository
public interface SalaryMapper extends BaseMapper<Customer> {/*** 计算所有送水工的工资* @return 工资列表*/List<Salary> calcSalary();
}
1.3 定义映射文件
定义SalaryMapper
接口对应的映射器SalaryMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.minzu.mapper.SalaryMapper"><select id="calcSalary" resultType="Salary">SELECT w.worker_name , w.worker_salary , w.worker_money,ifnull(sum(h.send_water_count),0) as send_water_count,ifnull(sum(h.send_water_count * w.worker_money)+w.worker_salary , w.worker_salary) as final_salaryFROM tb_worker w left join tb_history h on w.wid = h.worker_idGROUP BY w.widORDER BY final_salary desc</select>
</mapper>
1.4 定义SalaryService接口
package com.minzu.service;import com.minzu.entities.Salary;import java.util.List;/*** TODO:计算工资相关的业务逻辑* @author caojie* @version 1.0*/
public interface SalaryService {/*** 计算所有送水工的工资* @return 工资列表*/List<Salary> calcSalary();
}
1.5 定义SalaryService实现类
package com.minzu.service.impl;import com.minzu.entities.Salary;
import com.minzu.mapper.SalaryMapper;
import com.minzu.service.SalaryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.List;/*** TODO* @author caojie* @version 1.0*/
@Service
public class SalaryServiceImpl implements SalaryService {@Autowiredprivate SalaryMapper salaryMapper;/*** 计算所有送水工的工资** @return 工资列表*/@Overridepublic List<Salary> calcSalary() {return salaryMapper.calcSalary();}
}
1.6 定义Controller控制器
package com.minzu.controller;import com.minzu.entities.Salary;
import com.minzu.service.SalaryService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;import java.util.List;/*** TODO:处理计算工资对应的请求* @author caojie* @version 1.0*/
@RequestMapping("/salary")
@Controller
@Slf4j
public class SalaryController {@Autowiredprivate SalaryService salaryService;/*** 点击"计算工资"显示所有送水工的工资* @param model* @return*/@RequestMapping("/calcSalary")public String calcWorkerSalary(Model model) {List<Salary> salaryList = salaryService.calcSalary();log.info("worker salary list size = "+salaryList.size());model.addAttribute("salaryList",salaryList);return "salaryList";}
}
1.7 定义计算工资列表页面
定义计算工资列表页面salaryList.html
,显示所有送水工的工资
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title> 冯宝宝送水后台管理系统</title><!--Bootstrap固定框架--><link rel='stylesheet' th:href="@{/webjars/bootstrap/3.3.7/css/bootstrap.css}"><link rel='stylesheet' th:href="@{/webjars/bootstrap/3.3.7/css/bootstrap-theme.css}"><!--图标库--><link rel='stylesheet' th:href='@{/css/material-design-iconic-font.min.css}'><!--核心样式--><link rel="stylesheet" th:href="@{/css/style.css}"></head>
<body><div id="viewport"><!-- Sidebar客户列表页面使用th:replace属性替换成主菜单的侧边栏,让代码能够复用th:replace="waterMainMenu::sidebar"waterMainMenu表示主菜单页面的文件名称sidebar表示主菜单页面的片段名称--><div id="sidebar" th:replace="waterMainMenu::sidebar"></div><!-- Content --><div id="content"><!--
th:replace="waterMainMenu::navbar"表示将nav标签里面所有的内容替换为主页面的navbar片段--><nav class="navbar navbar-default" th:replace="waterMainMenu::navbar"></nav><div class="container-fluid"><div class="row"><div class="col-md-12"><div class="col-md-8"><form class="form-inline" th:action="@{/salary/searchCalcSalay}"><input type="date"class="form-control"th:value="${startDate}"th:name="startDate" placeholder="请输入开始时间" required/><input type="date"class="form-control"th:value="${endDate}"th:name="endDate" placeholder="请输入结束时间" /><input type="submit" class="btn btn-primary" name="search" value="搜索"/></form></div></div><div class="col-md-12"><table class="table table-hover table-striped"><thead><tr><td>送水工</td><td>基本工资</td><td>每桶提成</td><td>送水数量</td><td>实发工资</td></tr></thead><tbody><!-- 渲染后客户列表${custList}是Controller渲染到前端列表的数据,是一个集合cust是集合里面的一个对象th:each遍历集合里面所有的对象${cust.custName}对象名.属性名来显示数据。但是程序运行的时候会调用属性对应的get方法th:表示Thymeleaf模板对象注意:后端渲染到前端的动态数据全部包裹在${}里面--><tr th:each="salary : ${salaryList}"><td th:text="${salary.workerName}"></td><td th:text="${salary.workerSalary}"></td><td th:text="${salary.workerMoney}"></td><td th:text="${salary.sendWaterCount}"></td><td th:text="${salary.finalSalary}"></td></tr></tbody></table></div></div></div></div></div></body>
</html>
1.8 修改主界面
<li><a th:href="@{/salary/calcSalary}"><i class="zmdi zmdi-widgets"></i> 计算工资</a></li>
测试结果:
2 根计算某一段时间的工人工资
如1月1日到30日
2.1 编写SalaryMapper接口
在SalaryMapper
接口里面定义根据条件计算工资的方法。
注意:Mapper接口如果有多个方法必须定义@Param注解,否则无法传入Mapper映射文件
/*** 根据搜索条件计算某一段时间的工资* @param startDate 开始时间* @param endDate 结束时间* @return 工资列表*/List<Salary> calcSalaryByCondition(@Param("startDate") String startDate,@Param("endDate") String endDate);
2.2 编写Mapper映射文件
<select id="calcSalaryByCondition" parameterType="string" resultType="Salary">SELECT w.worker_name , w.worker_salary , w.worker_money,ifnull(sum(h.send_water_count),0) as send_water_count,ifnull(sum(h.send_water_count * w.worker_money)+w.worker_salary , w.worker_salary) as final_salaryFROM tb_worker w left join tb_history h on w.wid = h.worker_idwhere h.send_water_time between #{startDate} and #{endDate}GROUP BY w.widORDER BY final_salary desc</select>
2.3 编写SalaryService接口
/*** 根据搜索条件计算某一段时间的工资* @param startDate 开始时间* @param endDate 结束时间* @return 工资列表*/List<Salary> calcSalaryByCondition(String startDate,String endDate);
2.4 编写SalaryService实现类
/*** 根据搜索条件计算某一段时间的工资** @param startDate 开始时间* @param endDate 结束时间* @return 工资列表*/@Overridepublic List<Salary> calcSalaryByCondition(String startDate, String endDate) {// 条件成立:表示“结束时间”为空,系统当前时间就为结束时间,然后再转换为字符串if (StrUtil.isEmpty(endDate)) {Date date = new Date(System.currentTimeMillis()); //获取系统当前时间SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); //date永远都是毫秒数,还是要给他格式化一下的endDate = sdf.format(date); //format:格式}return salaryMapper.calcSalaryByCondition(startDate,endDate); //开始时间可以是任意一天,但结束时间永远是当前时间}
SalaryService实现类中的结束时间操作endDate的操作是因为开始时间可以是任意一天,但结束时间永远是当前时间
2.5 编写Controller控制器
@RequestMapping("/searchCalcSalary")public String searchCalcWorkerSalary(String startDate, String endDate, Model model) {List<Salary> salaryList = salaryService.calcSalaryByCondition(startDate, endDate);model.addAttribute("salaryList",salaryList);// 修复BUG,将搜索条件传入到前端页面model.addAttribute("startDate",startDate);model.addAttribute("endDate",endDate);return "salaryList";}
2.6 编写计算工资列表页面
在salaryList.html
页面中新增加搜索表单
<div class="col-md-12"><div class="col-md-8"><form class="form-inline" th:action="@{/salary/searchCalcSalay}"><input type="date"class="form-control"th:value="${startDate}"th:name="startDate" placeholder="请输入开始时间" required/><input type="date"class="form-control"th:value="${endDate}"th:name="endDate" placeholder="请输入结束时间" /><input type="submit" class="btn btn-primary" name="search" value="搜索"/></form></div></div>
3 修复BUG
如图所示:查询2020-01-01 ~ 2020-01-31时间段送过水的送水工工资,在这个时间段没有为客户送过水的送水工工资却没有显示。这个时间段没有为为客户送过水的送水工应该显示基本工资
正确的查询结果应该是:
3.1 Mapper接口
所以我们还要去给没有给客户送水的送水工信息
/*** 查询没有为客户送水的送水工信息* @return 送水工列表*/List<Worker> queryNotSendWaterWorker() ;
3.2 Mapper映射器配置文件
涉及到联合查询
<!-- union联合查询:查询没有为客户送过水的送水工列表 “联合” 没有在该时间段送水的送水工列表--><select id="queryNotSendWaterWorker" resultType="Worker">SELECT distinct w.worker_name,w.worker_salary,w.worker_moneyFROM tb_worker w left join tb_history h on w.wid = h.worker_idWHERE not h.send_water_time between #{startDate} and #{endDate}UNIONSELECT w.worker_name,w.worker_salary,w.worker_moneyFROM tb_worker w left join tb_history h on w.wid = h.worker_idWHERE h.worker_id is null</select>
distinct:去重
UNION:关联
3.3 SalaryService接口实现类
修改SalaryService
接口实现类的calcSalaryByCondition
方法。计算没有送水的送水工的工资
/*** 根据搜索条件计算某一段时间的工资** @param startDate 开始时间* @param endDate 结束时间* @return 工资列表*/@Overridepublic List<Salary> calcSalaryByCondition(String startDate, String endDate) {// 条件成立:表示“结束时间”为空,系统当前时间为结束时间,然后再转换为字符串if (StrUtil.isEmpty(endDate)) {Date date = new Date(System.currentTimeMillis());SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");endDate = sdf.format(date);}// salaryList 在某个时间段已经为客户送过水的送水工信息List<Salary> salaryList = salaryMapper.calcSalaryByCondition(startDate, endDate);// 没有为客户送过水的送水工信息List<Worker> workerList = salaryMapper.queryNotSendWaterWorker();// 获取以送水的送水工名称//map提取,获取完了之后 在转成list集合List<String> workerNameList = salaryList.stream().map(Salary::getWorkerName).collect(Collectors.toList());// 将没有送水的送水工信息合并到salaryList// 遍历workerList,将worker对象的数据注入到Salary对象中,让后添加到salaryList集合workerList.forEach(worker -> {// 条件成立:表示没有没有送水的送水工在salaryList集合中不存在,将其放入集合if (!workerNameList.contains(worker.getWorkerName())){Salary sa = new Salary();sa.setWorkerName(worker.getWorkerName());sa.setWorkerSalary(worker.getWorkerSalary());sa.setWorkerMoney(worker.getWorkerMoney());// 没有送水的送水工默认送水数量为0sa.setSendWaterCount(0);// 没有送水的送水工默认实发工资为基本工资sa.setFinalSalary(Double.valueOf(worker.getWorkerSalary()));salaryList.add(sa);}});// 将“实发工资”按照降序排序// 需要对每个送水工的”实发工资“进行比较Collections.sort(salaryList,(o1, o2)->{if(o1.getFinalSalary() > o2.getFinalSalary()){return -1;} else if (o1.getFinalSalary() < o2.getFinalSalary()){return 1;} else {return 0;}});return salaryList;}
stream():stream流
collect:收集
Collectors:收集器
contains:包含
Collections.sort:集合排序