上周给以前做的模块新增了个报表统计的功能,感觉有点意思,写出来分享下,也方便以后回顾。
其实报表统计一般是要用到插件的,但是我这个是用html拼成的,虽然是low了点,但是要做好坑还是很多的。
本来想详细说一下业务和代码的,给sql都加上注释,但是发现业务这东西说清楚太麻烦了,所以就大概描述一下吧,最重要的还是最后面的一条sql。
用到的表为A01(人员基本信息表)、A11(人员培训信息表)、B00(机构信息表);A01为主表,查近五年的培训信息
首先做完第一个页面大概是长成这样子的
这种查询以前查过类似的,所以做的比较快
这是拼接html的后台代码
@Action
public String getBmPxxs() {
HttpServletRequest request = ActionContext.getActionContext().getHttpServletRequest();
Map<String, Object> map = new HashMap<String, Object>();
String B00 = request.getParameter("B00");// 查询的部门id
String year = request.getParameter("year");// 当前年
map.put("B00", B00);
map.put("year", year);
List<Map<String, Object>> bmpxxs_list = this.bdsoftMybatisUtils.selectList("jypx.cxtj.pxxxhz.bmpxxs", map);
StringBuffer sbtest = new StringBuffer();
// html表头
sbtest.append("<div style='width:5000px;'><table border='1' cellpadding='0' cellspacing='0'>");
sbtest.append("<tr style='background:#eeeeee;text-align:center;'>");
sbtest.append("<td style='width:200px;' rowspan='2'>单位</td>");
sbtest.append("<td style='width:150px;' rowspan='2'>姓名</td>");
sbtest.append("<td style='width:600px; text-align:center;' colspan='6'>培训学时</td>");
sbtest.append("</tr>");
sbtest.append("<tr style='background:#eeeeee;text-align:center;'>");
sbtest.append("<td style='width:100px;' rowspan='1'>" + (Integer.parseInt(year) - 4) + "年</td>");
sbtest.append("<td style='width:100px;' rowspan='1'>" + (Integer.parseInt(year) - 3) + "年</td>");
sbtest.append("<td style='width:100px;' rowspan='1'>" + (Integer.parseInt(year) - 2) + "年</td>");
sbtest.append("<td style='width:100px;' rowspan='1'>" + (Integer.parseInt(year) - 1) + "年</td>");
sbtest.append("<td style='width:100px;' rowspan='1'>" + year + "年</td>");
sbtest.append("<td style='width:100px;' rowspan='1'>合计</td>");
sbtest.append("</tr>");
// html数据填充
for (int i = 0; i < bmpxxs_list.size(); i++) {
if (Integer.parseInt(bmpxxs_list.get(i).get("合计").toString()) < 300) {
sbtest.append("<tr style='background:#eeeee0;text-align:center;'>");
} else {
sbtest.append("<tr style='text-align:center;'>");
}
sbtest.append("<td style='width:200px;' rowspan='1'>" + bmpxxs_list.get(i).get("所在部门") + "</td>");
sbtest.append("<td style='width:150px;' rowspan='1'>" + bmpxxs_list.get(i).get("姓名") + "</td>");
sbtest.append("<td style='width:100px;' rowspan='1'>" + bmpxxs_list.get(i).get("J1") + "</td>");
sbtest.append("<td style='width:100px;' rowspan='1'>" + bmpxxs_list.get(i).get("J2") + "</td>");
sbtest.append("<td style='width:100px;' rowspan='1'>" + bmpxxs_list.get(i).get("J3") + "</td>");
sbtest.append("<td style='width:100px;' rowspan='1'>" + bmpxxs_list.get(i).get("J4") + "</td>");
sbtest.append("<td style='width:100px;' rowspan='1'>" + bmpxxs_list.get(i).get("J5") + "</td>");
sbtest.append("<td style='width:100px;' rowspan='1'>" + bmpxxs_list.get(i).get("合计") + "</td>");
sbtest.append("</tr>");
}
sbtest.append("</table></div>");
return sbtest.toString();
}
这块主要就是把查出来的list动态拼接成table,下面是查询的sql语句
<select id="bmpxxs" parameterType="String" resultType="java.util.LinkedHashMap" >
SELECT A01.A00 人员主键,
B01.B00 部门主键,
G099.DMCPT 所在部门,
A01.A0101 姓名,
NVL(J.J1,0) "J1",
NVL(J.J2,0) "J2",
NVL(J.J3,0) "J3",
NVL(J.J4,0) "J4",
NVL(J.J5,0) "J5",
NVL(J.J6,0) 合计
FROM A01
inner JOIN B01 ON B01.B00 = A01.ZDYXA0176
LEFT JOIN
(select E.A00,
sum(case WHEN E.年份 = (#{year}-4) then E.学时 else 0 end)J1,
sum(case WHEN E.年份 = (#{year}-3) then E.学时 else 0 end)J2,
sum(case WHEN E.年份 = (#{year}-2) then E.学时 else 0 end)J3,
sum(case WHEN E.年份 = (#{year}-1) then E.学时 else 0 end)J4,
sum(case WHEN E.年份 = #{year} then E.学时 else 0 end)J5,
sum(case WHEN E.年份 =(#{year}-4) then E.学时 else 0 end)
+ sum(case WHEN E.年份 = (#{year}-3) then E.学时 else 0 end)
+ sum(case WHEN E.年份 = (#{year}-2) then E.学时 else 0 end)
+ sum(case WHEN E.年份 =(#{year}-1) then E.学时 else 0 end)
+ sum(case WHEN E.年份 = #{year} then E.学时 else 0 end) J6
FROM (select
A01.A00,A01.A0101 姓名,TO_CHAR(A.A1107,'YYYY')年份,
to_char(A.A1107,'YYYY.MM.DD')开始日期,to_char(A.A1111,'YYYY.MM.DD')结束日期,nvl(A.A1156,0)
天数,nvl2(A.A1156,A.A1156*8,0) 学时
FROM A01
LEFT JOIN (SELECT * FROM A11 WHERE A11.DELETEFLAG != '1' or A11.DELETEFLAG is null) A on A.a00 = A01.a00
WHERE #{year} >= TO_CHAR(A.A1107,'YYYY') AND TO_CHAR(A.A1107,'YYYY') >= (#{year}-4)
) E GROUP BY E.A00) J ON A01.A00 = J.A00
LEFT JOIN G099 ON G099.DMCOD = A01.ZDYXA0176
WHERE A01.ZDYXA0109 = '1' AND (A01.DELFLAG IS NULL OR A01.DELFLAG!='1')
AND A01.ZDYXA0176 IN (SELECT B00 FROM B01 START WITH B00 =
#{B00} CONNECT BY PRIOR B00 = B0144B )
ORDER BY (B01.PINDEX) ASC,A01.PINDEX ASC
</select>
这种统计类sql以前写的时候还是想了比较久的,第一次写的时候本来想找组长帮忙的,但是他发烧请假了,然后自己边想边写就写出来了,回头看其实挺简单的,主要就是一个行转列的操作,oracle中有个函数可以直接转,但是我没转成功,就直接用最原始的case when来写了,也比较容易理解吧。然后group by这个东西还是要慎用 (如果在返回集字段中,字段要么就要包含在Group By语句的后面,作为分组的依据;要么就要被包含在聚合函数中),然后就是个递归函数 START WITH...CONNECT BY PRIOR...来进行节点控制。
下面是导出功能的实现
@Action
public void exportBMhtml() throws Exception {
HttpServletRequest request = ActionContext.getActionContext().getHttpServletRequest();
HttpServletResponse response = ActionContext.getActionContext().getHttpServletResponse();
Map<String, Object> map = new HashMap<String, Object>();
// 获取制定的excel
String sourceSrc = System.getProperty("resourceFiles.location") + File.separator + "jypx" + File.separator;
// 获取模板名称
String templateName = request.getParameter("templateName");
// 获取需要导出的文件名称
String fileName = request.getParameter("fileName");
// 获取文件类型
String fileType = request.getParameter("fileType");
// 获取模板路径
String excelFilePath = sourceSrc + templateName;
try {
fileName = fileName + "." + fileType;
InputStream in = new FileInputStream(new File(excelFilePath));
Workbook wb = new Workbook(in);
Worksheet worksheet = wb.getWorksheets().get(0);
Cells cells = worksheet.getCells();
String B00 = request.getParameter("B00");
String year = request.getParameter("year");
map.put("B00", B00);
map.put("year", year);
List<Map<String, Object>> bmpxxs_datalist = new ArrayList<Map<String, Object>>();
bmpxxs_datalist = this.bdsoftMybatisUtils.selectList("jypx.cxtj.pxxxhz.bmpxxs", map);
// excel表头
cells.get(0, 0).putValue((Integer.parseInt(year) - 4) + "—" + year + "年参训学时汇总表");
cells.merge(0, 0, 1, 8);// 合并行,列(起始行,起始列,合并行数,合并列数)
// cells.setColumnWidth(0, 30); //设置宽度
AddBorder(cells, 0, 0);
AddBorder(cells, 1, 3);
AddBorder(cells, 1, 4);
AddBorder(cells, 1, 5);
AddBorder(cells, 1, 6);
AddBorder(cells, 1, 7);
AddBorders(cells, 0, 0);
cells.get(1, 0).putValue("单位");
cells.merge(1, 0, 2, 1);
cells.setColumnWidth(0, 30); // 设置行宽
AddBorder(cells, 1, 0);
AddBorder(cells, 2, 0);
AddBorders(cells, 1, 0);
cells.get(1, 1).putValue("姓名");
cells.merge(1, 1, 2, 1);
cells.setColumnWidth(1, 20); // 设置行宽
AddBorder(cells, 1, 1);
AddBorders(cells, 1, 1);
cells.get(1, 2).putValue("培训学时");
cells.merge(1, 2, 1, 6);
AddBorder(cells, 1, 2);
AddBorders(cells, 1, 2);
cells.get(2, 2).putValue((Integer.parseInt(year) - 4) + "年");
cells.merge(2, 2, 1, 1);
cells.setColumnWidth(2, 15); // 设置行宽
AddBorder(cells, 2, 2);
AddBorders(cells, 2, 2);
cells.get(2, 3).putValue((Integer.parseInt(year) - 3) + "年");
cells.merge(2, 3, 1, 1);
cells.setColumnWidth(3, 15); // 设置行宽
AddBorder(cells, 2, 3);
AddBorders(cells, 2, 3);
cells.get(2, 4).putValue((Integer.parseInt(year) - 2) + "年");
cells.merge(2, 4, 1, 1);
cells.setColumnWidth(4, 15); // 设置行宽
AddBorder(cells, 2, 4);
AddBorders(cells, 2, 4);
cells.get(2, 5).putValue((Integer.parseInt(year) - 1) + "年");
cells.merge(2, 5, 1, 1);
cells.setColumnWidth(5, 15); // 设置行宽
AddBorder(cells, 2, 5);
AddBorders(cells, 2, 5);
cells.get(2, 6).putValue(year + "年");
cells.merge(2, 6, 1, 1);
cells.setColumnWidth(6, 15); // 设置行宽
AddBorder(cells, 2, 6);
AddBorders(cells, 2, 6);
cells.get(2, 7).putValue("合计");
cells.merge(2, 7, 1, 1);
cells.setColumnWidth(7, 15); // 设置行宽
AddBorder(cells, 2, 7);
AddBorders(cells, 2, 7);
// 存放表头
List<String> list_BT = new ArrayList<>();
list_BT.add("所在部门");
list_BT.add("姓名");
list_BT.add("J1");
list_BT.add("J2");
list_BT.add("J3");
list_BT.add("J4");
list_BT.add("J5");
list_BT.add("合计");
// excel填充数据
for (int i = 0; i < bmpxxs_datalist.size(); i++) {
for (int j = 0; j < 8; j++) {
cells.get(i + 3, j).putValue(bmpxxs_datalist.get(i).get(list_BT.get(j)));
// 标记近5年培训总学时小于300的人员
if (Integer.parseInt((bmpxxs_datalist.get(i).get("合计")).toString()) < 300) {
Style style = cells.get(i + 3, j).getStyle();
style.setPattern(BackgroundType.SOLID);
style.setForegroundColor(Color.getLightGray());
cells.get(i + 3, j).setStyle(style);
}
}
}
// 调整样式
for (int i = 3; i < bmpxxs_datalist.size() + 3; i++) {
for (int j = 0; j < 8; j++) {
AddBorder(cells, i, j);
}
}
response.addHeader("Content-Disposition",
"attachment;filename=" + new String(java.net.URLEncoder.encode(fileName, "UTF-8")));
response.addHeader("Content-Type", "application/octet-stream; charset=UTF-8");
wb.save(response.getOutputStream(), SaveFormat.XLSX);
} catch (Exception e) {
throw e;
}
}
// 添加边框
private void AddBorder(Cells cells, int row, int column) {
Style style = cells.get(row, column).getStyle();
style.setBorder(BorderType.TOP_BORDER, 1, Color.getBlack());
style.setBorder(BorderType.LEFT_BORDER, 1, Color.getBlack());
style.setBorder(BorderType.RIGHT_BORDER, 1, Color.getBlack());
style.setBorder(BorderType.BOTTOM_BORDER, 1, Color.getBlack());
style.setTextWrapped(true); // 单元格内容自动换行
style.setHorizontalAlignment(TextAlignmentType.CENTER);// 文字居中
style.setVerticalAlignment(TextAlignmentType.CENTER);
style.setName("仿宋_GB2312");
style.getFont().setSize(14);
cells.get(row, column).setStyle(style);
}
上面这些都没啥好说的,就是一些和业务相关的逻辑,比较简单。
然后重点是第二个页面
页面是长这样的
开始看到这个页面我是自闭的,有三个难点,第一是动态合并单元格,第二个是动态拼接合计行,第三就是要查出人员没有年份的培训记录,就是说16年即使这个人没有培训记录但是要查出来;
第一个难点和第二个难点我想的是把每个人都查一遍放到一个list里面再拼接,而不是一次查所有人,然后再在java里面做些逻辑处理就行了,然后第三点问了我旁边的sql大神,只要建张年份临时表,然后再cross join (笛卡尔积) A01表,再left jion A11表,最后再加些限制条件就OK了,反正他刚说的时候我也是很懵,我还以为他没听懂我要干嘛,没想到写出来的结果确实是我想要的,然后就开始动手写了,这种循环查每个人的培训信息的思路sql会很简单,但是java逻辑比较复杂。下面是拼成html的java代码
public String getRyPxxs2() {
HttpServletRequest request = ActionContext.getActionContext().getHttpServletRequest();
Map<String, Object> map = new HashMap<String, Object>();
String B00 = request.getParameter("B00");// 查询的部门id
String year = request.getParameter("year");// 当前年
map.put("B00", B00);
map.put("year", year);
StringBuffer sbtest = new StringBuffer();
// html表头
sbtest.append("<div style='width:5000px;'><table border='1' cellpadding='0' cellspacing='0'>");
sbtest.append("<tr style='background:#eeeeee;text-align:center;'>");
sbtest.append("<td style='width:100px;' text-align:center; rowspan='1'>姓名</td>");
sbtest.append("<td style='width:150px;' text-align:center; rowspan='1'>年份</td>");
sbtest.append("<td style='width:380px;' text-align:center; rowspan='1'>培训班</td>");
sbtest.append("<td style='width:150px;' text-align:center; rowspan='1'>开始日期</td>");
sbtest.append("<td style='width:150px;' text-align:center; rowspan='1'>结束日期</td>");
sbtest.append("<td style='width:80px;' text-align:center; rowspan='1'>天数</td>");
sbtest.append("<td style='width:80px;' text-align:center; rowspan='1'>学时</td>");
sbtest.append("</tr>");
// 存放人员A00
List<Map<String, Object>> A00_list = this.bdsoftMybatisUtils.selectList("jypx.cxtj.pxxxhz.queryA00", map);
Map<String, Object> map1 = new HashMap<String, Object>();
// html填充数据
for (int i = 0; i < A00_list.size(); i++) {
map1.put("A00", A00_list.get(i).get("A00"));
map1.put("year", year);
// 存放人员培训信息
List<Map<String, Object>> rypxxx_list = this.bdsoftMybatisUtils.selectList("jypx.cxtj.pxxxhz.queryrypxxx",map1);
// 统计天数
int day = 0;
for (int j = 0; j < rypxxx_list.size(); j++) {
day += Integer.parseInt(rypxxx_list.get(j).get("天数").toString());
// 取到第一条数据时,合并姓名单元格
if (j == 0) {
sbtest.append("<tr style='text-align:center;'>");
sbtest.append("<td style='width:100px;' rowspan='" + (rypxxx_list.size() + 1) + "'>" + rypxxx_list.get(0).get("姓名") + "</td>");
sbtest.append("<td style='width:150px;' rowspan='1'>" + rypxxx_list.get(j).get("年份") + "</td>");
sbtest.append("<td style='width:380px;' rowspan='1'>" + (rypxxx_list.get(j).get("培训班") == null ? "-" : rypxxx_list.get(j).get("培训班")) + "</td>");
sbtest.append("<td style='width:150px;' rowspan='1'>" + (rypxxx_list.get(j).get("开始日期") == null ? "-" : rypxxx_list.get(j).get("开始日期")) + "</td>");
sbtest.append("<td style='width:150px;' rowspan='1'>" + (rypxxx_list.get(j).get("结束日期") == null ? "-" : rypxxx_list.get(j).get("结束日期")) + "</td>");
sbtest.append("<td style='width:80px;' rowspan='1'>" + rypxxx_list.get(j).get("天数") + "</td>");
sbtest.append("<td style='width:80px;' rowspan='1'>" + rypxxx_list.get(j).get("学时") + "</td>");
sbtest.append("</tr>");
} else {
sbtest.append("<tr style='text-align:center;'>");
sbtest.append("<td style='width:150px;' rowspan='1'>" + rypxxx_list.get(j).get("年份") + "</td>");
sbtest.append("<td style='width:380px;' rowspan='1'>" + (rypxxx_list.get(j).get("培训班") == null ? "-" : rypxxx_list.get(j).get("培训班")) + "</td>");
sbtest.append("<td style='width:150px;' rowspan='1'>" + (rypxxx_list.get(j).get("开始日期") == null ? "-" : rypxxx_list.get(j).get("开始日期")) + "</td>");
sbtest.append("<td style='width:150px;' rowspan='1'>" + (rypxxx_list.get(j).get("结束日期") == null ? "-" : rypxxx_list.get(j).get("结束日期")) + "</td>");
sbtest.append("<td style='width:80px;' rowspan='1'>" + rypxxx_list.get(j).get("天数") + "</td>");
sbtest.append("<td style='width:80px;' rowspan='1'>" + rypxxx_list.get(j).get("学时") + "</td>");
sbtest.append("</tr>");
}
}
// 个人培训信息遍历完后添加合计行
sbtest.append("<tr style='background:#eeeeee;text-align:center;'>");
sbtest.append("<td style='width:150px;' rowspan='1'>" + (Integer.parseInt(year) - 4) + "—" + year + "年合计" + "</td>");
sbtest.append("<td style='width:380px;' rowspan='1'></td>");
sbtest.append("<td style='width:150px;' rowspan='1'></td>");
sbtest.append("<td style='width:150px;' rowspan='1'></td>");
sbtest.append("<td style='width:80px;' rowspan='1'>" + day + "</td>");
sbtest.append("<td style='width:80px;' rowspan='1'>" + (day * 8) + "</td>");
sbtest.append("</tr>");
// 清空list
rypxxx_list.clear();
}
sbtest.append("</table></div>");
return sbtest.toString();
}
下面是导出功能的后台代码
@Action
public void exportRYhtml() throws Exception {
HttpServletRequest request = ActionContext.getActionContext().getHttpServletRequest();
HttpServletResponse response = ActionContext.getActionContext().getHttpServletResponse();
Map<String, Object> map = new HashMap<String, Object>();
// 获取制定的excel
String sourceSrc = System.getProperty("resourceFiles.location") + File.separator + "jypx" + File.separator;
// 获取模板名称
String templateName = request.getParameter("templateName");
// 获取需要导出的文件名称
String fileName = request.getParameter("fileName");
// 获取文件类型
String fileType = request.getParameter("fileType");
// 获取模板路径
String excelFilePath = sourceSrc + templateName;
try {
fileName = fileName + "." + fileType;
InputStream in = new FileInputStream(new File(excelFilePath));
Workbook wb = new Workbook(in);
Worksheet worksheet = wb.getWorksheets().get(0);
Cells cells = worksheet.getCells();
String B00 = request.getParameter("B00");
String year = request.getParameter("year");
map.put("B00", B00);
map.put("year", year);
List<Map<String, Object>> A00_list = new ArrayList<Map<String, Object>>();
A00_list = this.bdsoftMybatisUtils.selectList("jypx.cxtj.pxxxhz.queryA00", map);
Map<String, Object> map1 = new HashMap<String, Object>();
// 存放表头
List<String> list_BT = new ArrayList<>();
list_BT.add("姓名");
list_BT.add("年份");
list_BT.add("培训班");
list_BT.add("开始日期");
list_BT.add("结束日期");
list_BT.add("天数");
list_BT.add("学时");
// excel表头
cells.get(0, 0).putValue((Integer.parseInt(year) - 4) + "—" + year + "年人员参训情况汇总表");
cells.merge(0, 0, 1, 7);// 合并行,列(起始行,起始列,合并行数,合并列数)
cells.get(1, 0).putValue(list_BT.get(0));
cells.setColumnWidth(0, 15); // 设置行宽
cells.get(1, 1).putValue(list_BT.get(1));
cells.setColumnWidth(1, 22); // 设置行宽
cells.get(1, 2).putValue(list_BT.get(2));
cells.setColumnWidth(2, 45); // 设置行宽
cells.get(1, 3).putValue(list_BT.get(3));
cells.setColumnWidth(3, 18); // 设置行宽
cells.get(1, 4).putValue(list_BT.get(4));
cells.setColumnWidth(4, 18); // 设置行宽
cells.get(1, 5).putValue(list_BT.get(5));
cells.setColumnWidth(5, 13); // 设置行宽
cells.get(1, 6).putValue(list_BT.get(6));
cells.setColumnWidth(6, 13); // 设置行宽
// 填充数据起始行数
int startCol = 2;
// 填充数据
for (int i = 0; i < A00_list.size(); i++) {
map1.put("A00", A00_list.get(i).get("A00"));
map1.put("year", year);
// 存放人员培训信息
List<Map<String, Object>> rypxxx_list = this.bdsoftMybatisUtils.selectList("jypx.cxtj.pxxxhz.queryrypxxx", map1);
// 统计天数
int day = 0;
for (int j = 0; j < rypxxx_list.size(); j++) {
day += Integer.parseInt(rypxxx_list.get(j).get("天数").toString());
for (int k = 0; k < 7; k++) {
// 当循环到个人的第一条培训信息
if (j == 0 && k == 0) {
cells.get(startCol, k).putValue(rypxxx_list.get(j).get(list_BT.get(k)));
// 合并单元格
cells.merge(startCol, 0, rypxxx_list.size() + 1, 1);// 合并行,列(起始行,起始列,合并行数,合并列数)
} else if (k != 0) {
cells.get(startCol, k).putValue(rypxxx_list.get(j).get(list_BT.get(k)));
}
}
startCol += 1;
}
cells.get(startCol, 1).putValue((Integer.parseInt(year) - 4) + "—" + year + "年合计");
cells.get(startCol, 5).putValue(day);
cells.get(startCol, 6).putValue(day * 8);
startCol += 1;
}
// 调整样式
for (int i = 0; i < startCol; i++) {
for (int j = 0; j < 7; j++) {
AddBorder(cells, i, j);
AddBorders(cells, i, j);
}
}
response.addHeader("Content-Disposition",
"attachment;filename=" + new String(java.net.URLEncoder.encode(fileName, "UTF-8")));
response.addHeader("Content-Type", "application/octet-stream; charset=UTF-8");
wb.save(response.getOutputStream(), SaveFormat.XLSX);
} catch (Exception e) {
throw e;
}
}
这个是sql
<!-- 通过B00查找A00 -->
<select id="queryA00" parameterType="String" resultType="java.util.LinkedHashMap" >
SELECT A00 FROM A01 LEFT JOIN B01 ON A01.ZDYXA0176 = B01.B00
WHERE A01.ZDYXA0109 = '1' AND (A01.DELFLAG IS NULL OR A01.DELFLAG!='1')
AND A01.ZDYXA0176 IN (SELECT B00 FROM B01 START WITH B00 =#{B00}
CONNECT BY PRIOR B00 = B0144B )
and a01.a00 is not null
ORDER BY (B01.PINDEX) ASC,A01.PINDEX ASC
</select>
<!-- 通过A00查询个人培训信息 -->
<select id="queryrypxxx" parameterType="String" resultType="java.util.LinkedHashMap" >
SELECT T.A00,T.A0101 姓名,T.YEAR 年份 ,NVL(A11.A1131,'-') 培训班, NVL(to_char(A11.A1107,'YYYY.MM.DD'),'-')开始日期,
NVL(to_char(A11.A1111,'YYYY.MM.DD'),'-') 结束日期,NVL(A11.A1156,0) 天数,nvl2(A11.A1156,A11.A1156*8,0) 学时
FROM (
SELECT DATE_YEAR_TEMP.YEAR,A01.A00,A01.A0101 FROM (SELECT * FROM DATE_YEAR_TEMP WHERE #{year} >= YEAR AND YEAR >= (#{year}-4) ) DATE_YEAR_TEMP
CROSS JOIN (SELECT * FROM A01 WHERE A01.A00 = #{A00} ) A01) T
LEFT JOIN (SELECT TO_CHAR(A11.A1107,'YYYY') YEAR,A11.* FROM A11 WHERE A11.DELETEFLAG != '1' or A11.DELETEFLAG is null) A11 ON A11.A00=T.A00 AND A11.YEAR = T.YEAR
ORDER BY T.YEAR ASC,A11.A1107 ASC
</select>
写完运行测试,然后问题就来了,点开这个页面的时候发现啥都没有,看了下前端和后台都没报错,然后我就纳闷了,怎么没数据呢,好歹报个错啊,惆怅了十几秒后数据出来了,原来是因为加载太慢了,可以看到上面的代码查询的时候用了三层for循环,从数据库里查了几千次,运行完需要十几秒的时间。这样肯定是不行的,不然客户打开还以为死机了,所以我想加个类似这种提示框,但是加上去之后这个框怎么也去不掉(恶心的ext.js)。没办法了,只能硬着头皮去重构优化sql让它查快一点,想让它查快就不能查几千次,只能一次性把数据查出来,但是这样的话,之前提到的第一个难点和第二个难点就很难处理了。
虽然不好写,但是经过大神的指点后还是能看到希望的。查询结果为单个list的话,得知道什么时候开始合并单元格,以及要合并多少行的问题只能在sql里面解决,所以写起来就很复杂了,以下是优化后的java代码和sql
// 加载人员培训信息html
@Action
public String getRyPxxs() {
HttpServletRequest request = ActionContext.getActionContext().getHttpServletRequest();
Map<String, Object> map = new HashMap<String, Object>();
String B00 = request.getParameter("B00");// 查询的部门id
String year = request.getParameter("year");// 当前年
int year_4 = Integer.parseInt(year) - 4;
map.put("B00", B00);
map.put("year", year);
map.put("heji", year_4 + "年-" + year + "合计");
StringBuffer sbtest = new StringBuffer();
// html表头
sbtest.append("<div style='width:5000px;'><table border='1' cellpadding='0' cellspacing='0'>");
sbtest.append("<tr style='background:#eeeeee;text-align:center;'>");
sbtest.append("<td style='width:100px;' text-align:center; rowspan='1'>姓名</td>");
sbtest.append("<td style='width:150px;' text-align:center; rowspan='1'>年份</td>");
sbtest.append("<td style='width:380px;' text-align:center; rowspan='1'>培训班</td>");
sbtest.append("<td style='width:150px;' text-align:center; rowspan='1'>开始日期</td>");
sbtest.append("<td style='width:150px;' text-align:center; rowspan='1'>结束日期</td>");
sbtest.append("<td style='width:80px;' text-align:center; rowspan='1'>天数</td>");
sbtest.append("<td style='width:80px;' text-align:center; rowspan='1'>学时</td>");
sbtest.append("</tr>");
// 存放人员培训信息
List<Map<String, Object>> pxxx_list = this.bdsoftMybatisUtils.selectList("jypx.cxtj.pxxxhz.queryRyInfoForPxxx",
map);
// html填充数据
for (int i = 0; i < pxxx_list.size(); i++) {
String rn = "0";
if (pxxx_list.get(i).get("RN") != null) {
rn = pxxx_list.get(i).get("RN").toString();
}
// 跨行数
int rowspan = Integer.parseInt(pxxx_list.get(i).get("记录条数").toString()) + 1;
// 取到第一条数据时,合并姓名单元格
if ("1".equals(rn)) {
sbtest.append("<tr style='text-align:center;'>");
sbtest.append("<td style='width:100px;' rowspan='" + rowspan + "'>" + pxxx_list.get(i).get("姓名") + "</td>");
sbtest.append("<td style='width:150px;' rowspan='1'>" + pxxx_list.get(i).get("年份") + "</td>");
sbtest.append("<td style='width:380px;' rowspan='1'>" + (pxxx_list.get(i).get("培训班") == null ? "-" : pxxx_list.get(i).get("培训班")) + "</td>");
sbtest.append("<td style='width:150px;' rowspan='1'>" + (pxxx_list.get(i).get("开始日期") == null ? "-" : pxxx_list.get(i).get("开始日期")) + "</td>");
sbtest.append("<td style='width:150px;' rowspan='1'>" + (pxxx_list.get(i).get("结束日期") == null ? "-" : pxxx_list.get(i).get("结束日期")) + "</td>");
sbtest.append("<td style='width:80px;' rowspan='1'>" + pxxx_list.get(i).get("天数") + "</td>");
sbtest.append("<td style='width:80px;' rowspan='1'>" + pxxx_list.get(i).get("学时") + "</td>");
sbtest.append("</tr>");
} else {
sbtest.append("<tr style='text-align:center;'>");
sbtest.append("<td style='width:150px;' rowspan='1'>" + pxxx_list.get(i).get("年份") + "</td>");
sbtest.append("<td style='width:380px;' rowspan='1'>" + (pxxx_list.get(i).get("培训班") == null ? "-" : pxxx_list.get(i).get("培训班")) + "</td>");
sbtest.append("<td style='width:150px;' rowspan='1'>" + (pxxx_list.get(i).get("开始日期") == null ? "-" : pxxx_list.get(i).get("开始日期")) + "</td>");
sbtest.append("<td style='width:150px;' rowspan='1'>" + (pxxx_list.get(i).get("结束日期") == null ? "-" : pxxx_list.get(i).get("结束日期")) + "</td>");
sbtest.append("<td style='width:80px;' rowspan='1'>" + pxxx_list.get(i).get("天数") + "</td>");
sbtest.append("<td style='width:80px;' rowspan='1'>" + pxxx_list.get(i).get("学时") + "</td>");
sbtest.append("</tr>");
}
}
sbtest.append("</table></div>");
return sbtest.toString();
}
// 培训信息导出
@Action
public void exportRYhtml() throws Exception {
HttpServletRequest request = ActionContext.getActionContext().getHttpServletRequest();
HttpServletResponse response = ActionContext.getActionContext().getHttpServletResponse();
Map<String, Object> map = new HashMap<String, Object>();
// 获取制定的excel
String sourceSrc = System.getProperty("resourceFiles.location") + File.separator + "jypx" + File.separator;
// 获取模板名称
String templateName = request.getParameter("templateName");
// 获取需要导出的文件名称
String fileName = request.getParameter("fileName");
// 获取文件类型
String fileType = request.getParameter("fileType");
// 获取模板路径
String excelFilePath = sourceSrc + templateName;
try {
fileName = fileName + "." + fileType;
InputStream in = new FileInputStream(new File(excelFilePath));
Workbook wb = new Workbook(in);
Worksheet worksheet = wb.getWorksheets().get(0);
Cells cells = worksheet.getCells();
String B00 = request.getParameter("B00");
String year = request.getParameter("year");
int year_4 = Integer.parseInt(year) - 4;
map.put("B00", B00);
map.put("year", year);
map.put("heji", year_4 + "年-" + year + "合计");
List<Map<String, Object>> pxxx_list = this.bdsoftMybatisUtils.selectList("jypx.cxtj.pxxxhz.queryRyInfoForPxxx", map);
// 存放表头
List<String> list_BT = new ArrayList<>();
list_BT.add("姓名");
list_BT.add("年份");
list_BT.add("培训班");
list_BT.add("开始日期");
list_BT.add("结束日期");
list_BT.add("天数");
list_BT.add("学时");
// excel表头
cells.get(0, 0).putValue((Integer.parseInt(year) - 4) + "—" + year + "年人员参训情况汇总表");
cells.merge(0, 0, 1, 7);// 合并行,列(起始行,起始列,合并行数,合并列数)
cells.get(1, 0).putValue(list_BT.get(0));
cells.setColumnWidth(0, 15); // 设置行宽
cells.get(1, 1).putValue(list_BT.get(1));
cells.setColumnWidth(1, 22); // 设置行宽
cells.get(1, 2).putValue(list_BT.get(2));
cells.setColumnWidth(2, 45); // 设置行宽
cells.get(1, 3).putValue(list_BT.get(3));
cells.setColumnWidth(3, 18); // 设置行宽
cells.get(1, 4).putValue(list_BT.get(4));
cells.setColumnWidth(4, 18); // 设置行宽
cells.get(1, 5).putValue(list_BT.get(5));
cells.setColumnWidth(5, 13); // 设置行宽
cells.get(1, 6).putValue(list_BT.get(6));
cells.setColumnWidth(6, 13); // 设置行宽
// 填充数据
for (int i = 0; i < pxxx_list.size(); i++) {
String rn = "0";
if (pxxx_list.get(i).get("RN") != null) {
rn = pxxx_list.get(i).get("RN").toString();
}
// 合并行数
int rowspan = Integer.parseInt(pxxx_list.get(i).get("记录条数").toString()) + 1;
for (int j = 0; j < 7; j++) {
// 当循环到个人的第一条培训信息
if (j == 0 && "1".equals(rn)) {
cells.get(i + 2, j).putValue(pxxx_list.get(i).get(list_BT.get(j)) == null ? ""
: pxxx_list.get(i).get(list_BT.get(j)).toString());
// 合并单元格
cells.merge(i + 2, 0, rowspan, 1);// 合并行,列(起始行,起始列,合并行数,合并列数)
} else if (j != 0) {
cells.get(i + 2, j).putValue(pxxx_list.get(i).get(list_BT.get(j)) == null ? ""
: pxxx_list.get(i).get(list_BT.get(j)).toString());
}
}
}
// 调整样式
for (int i = 0; i < pxxx_list.size() + 2; i++) {
for (int j = 0; j < 7; j++) {
AddBorder(cells, i, j);
AddBorders(cells, i, j);
}
}
response.addHeader("Content-Disposition",
"attachment;filename=" + new String(java.net.URLEncoder.encode(fileName, "UTF-8")));
response.addHeader("Content-Type", "application/octet-stream; charset=UTF-8");
wb.save(response.getOutputStream(), SaveFormat.XLSX);
} catch (Exception e) {
throw e;
}
}
<!-- 查询个人培训信息(优化后) -->
<select id="queryRyInfoForPxxx" parameterType="String" resultType="java.util.LinkedHashMap" >
SELECT T5.A00,T5.姓名 姓名,T5.年份 年份,T5.培训班 培训班,T5.开始时间 开始日期,T5.结束时间 结束日期,T5.天数 天数,T5.学时 学时,T5.记录条数 记录条数,T5.rn rn FROM (
SELECT * FROM (SELECT r.A00 A00,'' 姓名,'${heji}' 年份,'' 培训班,'' 开始时间,'' 结束时间,r.总天数 天数,to_char(r.总天数*8) 学时,r.记录条数,'' rn from
(SELECT T.A00 ,to_char(COUNT(T.A00)) 记录条数,to_char(SUM(T.天数)) 总天数 FROM (
SELECT A01.A00,A01.A0101,DATE_YEAR_TEMP.YEAR year,A11.A1131 培训班,to_char(A11.A1107,'YYYY.MM.DD') 开始时间,to_char(A11.A1111,'YYYY.MM.DD') 结束时间,
nvl(A11.A1156,0) 天数,nvl(A11.A1156*8,0) 学时 FROM
<!-- 在职未删除 -->
(SELECT * FROM A01 WHERE A01.ZDYXA0109 = '1' AND (A01.DELFLAG IS NULL OR A01.DELFLAG!='1')
<!-- 节点控制 -->
AND A01.ZDYXA0176 IN (SELECT B00 FROM B01 START WITH B00 = #{B00} CONNECT BY PRIOR B00 = B0144B ))A01
CROSS JOIN (SELECT * FROM DATE_YEAR_TEMP WHERE #{year} >= YEAR AND YEAR >= (#{year}-4) ) DATE_YEAR_TEMP
LEFT JOIN (SELECT * FROM A11
WHERE TO_CHAR(A11.A1107,'YYYY')>=(#{year}-4) AND #{year}>= TO_CHAR(A11.A1107,'YYYY')
AND (A11.DELETEFLAG != '1' or A11.DELETEFLAG is null))A11
on A11.A00 = A01.A00 AND to_char(A11.A1107,'YYYY') = DATE_YEAR_TEMP.YEAR) T GROUP BY T.A00) r ) t1
<!-- 连上合计行子表 -->
UNION ALL
select T4.a00,t4.姓名 姓名,t4.年份 年份,t4.培训班 培训班,t4.开始时间 开始时间,t4.结束时间 结束时间,t4.天数 天数,t4.学时 学时,t4.记录条数 记录条数,to_char(t4.rn) rn from
(SELECT * from (SELECT t2.* ,ROW_NUMBER () OVER (PARTITION BY t2.A00 ORDER BY t2.年份 ASC, t2.开始时间 ASC) rn FROM
(SELECT A01.A00,A01.A0101 姓名,TO_CHAR(DATE_YEAR_TEMP.YEAR) 年份,A11.A1131 培训班,to_char(A11.A1107,'YYYY.MM.DD') 开始时间,
to_char(A11.A1111,'YYYY.MM.DD') 结束时间,to_char(nvl(A11.A1156,0)) 天数,to_char(nvl(A11.A1156*8,0))学时
FROM (SELECT * FROM A01 WHERE A01.ZDYXA0109 = '1' AND (A01.DELFLAG IS NULL OR A01.DELFLAG!='1')
AND A01.ZDYXA0176 IN (SELECT B00 FROM B01 START WITH B00 = #{B00} CONNECT BY PRIOR B00 = B0144B ))A01
CROSS JOIN (SELECT * FROM DATE_YEAR_TEMP WHERE #{year} >= YEAR AND YEAR >= (#{year}-4)) DATE_YEAR_TEMP
LEFT JOIN (SELECT * FROM A11 WHERE TO_CHAR(A11.A1107,'YYYY')>=(#{year}-4) AND #{year}>= TO_CHAR(A11.A1107,'YYYY')
AND (A11.DELETEFLAG != '1' or A11.DELETEFLAG is null))A11
on A11.A00 = A01.A00 AND to_char(A11.A1107,'YYYY') = DATE_YEAR_TEMP.YEAR)t2)t8
LEFT JOIN
<!-- 人员记录条数子表 -->
(SELECT T.A00 a00_1 ,to_char(COUNT(T.A00)) 记录条数 FROM (
SELECT A01.A00,A01.A0101,DATE_YEAR_TEMP.YEAR year,A11.A1131 培训班,to_char(A11.A1107,'YYYY.MM.DD') 开始时间,
to_char(A11.A1111,'YYYY.MM.DD') 结束时间,nvl(A11.A1156,0) 天数,nvl(A11.A1156*8,0)学时
FROM (SELECT * FROM A01 WHERE A01.ZDYXA0109 = '1' AND (A01.DELFLAG IS NULL OR A01.DELFLAG!='1')
AND A01.ZDYXA0176 IN (SELECT B00 FROM B01 START WITH B00 = #{B00} CONNECT BY PRIOR B00 = B0144B ))A01
CROSS JOIN (SELECT * FROM DATE_YEAR_TEMP WHERE #{year} >= YEAR AND YEAR >= (#{year}-4)) DATE_YEAR_TEMP
LEFT JOIN (SELECT * FROM A11 WHERE TO_CHAR(A11.A1107,'YYYY')>=(#{year}-4)
AND #{year}>= TO_CHAR(A11.A1107,'YYYY') AND (A11.DELETEFLAG != '1' or A11.DELETEFLAG is null))A11
on A11.A00 = A01.A00 AND to_char(A11.A1107,'YYYY') = DATE_YEAR_TEMP.YEAR
) T GROUP BY T.A00) t3 on T8.A00 = T3.A00_1) t4 ) t5
LEFT JOIN (SELECT A00,ZDYXA0176,pindex from a01) a01 on A01.A00 = t5.A00
LEFT JOIN (SELECT B00,pindex from B01) B01 on B01.B00 = A01.ZDYXA0176
ORDER BY B01.PINDEX ASC,A01.PINDEX ASC,T5.A00 ASC,T5.RN
</select>
写完这条sql有种身体被掏空的感觉,查询时间也缩短到了一秒以内,整体思路就是t1(培训信息字表)left join t2(个人记录条数表),连完之后再union all t3(统计行表) ,这条sql能正常运行出来其中也是蛮多坑的,以前以为union的时候字段名相同就行了,现在才知道类型也要一样,所以字段基本都进行了to_char()处理,然后最精妙的还是
ROW_NUMBER () OVER (PARTITION BY ... ORDER BY ...) 函数的运用,这个函数确实帮了很大的忙。
总结
写完上面那条sql后我都在怀疑是不是我写出来的,因为第二天我就看不懂我写的是什么了,更不想细看或者去改动,可能是看到那么多select就感觉太复杂了,懒得看,其实静下心看还是能看出来每个select在干嘛,我也不想写这么多查询,只是每个查询都是必要的,可能还有精简优化的余地我没发现。想了下这几个月我进步最大的还是写sql吧,越写越感叹于sql的博大精深,其实我感觉写sql也可以用面向对象来理解,说简单点就是把每张表当个整体,想要什么就查什么,无非就是一些连接操作和一些函数的运用,一层不行就套两层,两层不行就再套一层,套完之后验证一下数据的正确性,看是不是自己想要的结果,最后再当成一张表进行操作,不用理里面的查询是怎么实现的,虽然到最后可能自己都看不懂在干什么,但是只要思路清晰,写起来就得心应手,水到渠成了。