一次sql优化

上周给以前做的模块新增了个报表统计的功能,感觉有点意思,写出来分享下,也方便以后回顾。

其实报表统计一般是要用到插件的,但是我这个是用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也可以用面向对象来理解,说简单点就是把每张表当个整体,想要什么就查什么,无非就是一些连接操作和一些函数的运用,一层不行就套两层,两层不行就再套一层,套完之后验证一下数据的正确性,看是不是自己想要的结果,最后再当成一张表进行操作,不用理里面的查询是怎么实现的,虽然到最后可能自己都看不懂在干什么,但是只要思路清晰,写起来就得心应手,水到渠成了。

 

 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值