在此前,我也写过一个行转列的文章,是用存储过程sql处理的一个动态的逻辑
Mysql 存储过程\Mybatis框架call调用 实现动态行转列
那么后面我们同样又接收了业务的一个新需求,针对的是不同的业务数据,做的同样的一个展示数据报表,同样还是产品归属信息表头,拼接查询年月表头列,动态展示多个查询年月的产品产量数据。
- 这里我们具体分析下业务需求:
- 首先是一个业务产量表,记录着每个产品编码的产量数,产量时间
- 另外是一个编码归属表,记录着每个产品编码的归属信息,属于哪个产品,哪个项目等的归属信息
一般情况下,我们先设计后台表,然后先进行数据的sql处理,那么我们这里就从dao层开始来展开:
参数就是基于前台请求的参数类来传递,比如前端传递查询对象: 开始时间:2022-06,结束时间:2023-06, 只有时间,或者说还带上了一些 产品:产品A等
参数类:
package xxx.vo;
import java.util.List;
@Data
public class ProductionShipmentVO implements Serializable {
private static final long serialVersionUID = 952332486490268095L;
private String label;
private String value;
private List<String> proList;
private List<String> itemList;
private List<String> codeList;
private List<String> taskNoList;
private List<String> customerList;
private List<String> contractList;
private List<String> subList;
private String isFlag;
private String startTime;
private String endTime;
private String code;
private Integer num;
private String date;
private String product; //产品名
private String project; //项目
private String jobNum; //任务令号
private String planStarttime; //计划开工时间 plan_starttime
private String planEndtime; //计划完工时间 plan_endtime
private String releaseTime; //释放时间 release_time
private String jobStatus; //任务令状态 job_status
private String itemcode; //编码
private String transactionQuantity; //交易数量 transaction_quantity
private String transactionTime; //交易时间 transaction_time
private String transactionDate; //交易日期 transaction_date
private String transactionType; //交易类型 transaction_type
private String groupId; //组织ID group_id
private String subinventory; //子库
private String supplierName; //供应商名称 supplier_name
private String itemcodeCout; //单板点数 itemcode_cout
private String processingLocationcode; //加工地代码 processing_locationcode
private String processingLocationname; //加工地名称 processing_locationname
private String productFamily; //产品族 product_family
private String productSmall; //产品小类 product_small
private String productBig; //产品大类 product_big
private String smtLine; //SMT线体 smt_line
private String itemcodeLine; //模块线体 itemcode_line
private String system; //系统源
private String lastUpdatetime; //最后更新时间 last_updatetime
private String lv1; //l1部门
private String lv2; //l2部门
private String lv3; //l3部门
private String lv4; //l4部门
private String type; //类型
private String locator; //货位
}
dao层 mapper接口
public interface IProductionShipmentDao {
public List<ProductionShipmentVO> getYieldTabelProduct(ProductionShipmentVO productionShipmentVO);
}
dao层 xml文件映射
1. <sql id="yieldTabel"> 标签语句
我们把产量明细表 左联 编码归属表 的结果语句sql抽象出来放到一个sql标签中,作为共用调用的作用,可以结合业务场景判断,如果这个基础连表查询后续会多个地方需要调用,那么代码sql就可以抽出来一个sql标签中,避免重复代码过多
2.<sql id="listWhere"> 标签语句
同理与第一点,目前还是抽象出公共的语句,避免多处的接口sql重复代码过多,这里是一个参数的请求条件,也是极具通用的 多处地方可能会用到,所以就单独定义一个sql标签进行调用
3.<select id="getYieldTabelProduct" resultType="com.xxx.vo.ProductionShipmentVO">
调用标签1 作为临时表,然后根据产品 时间分组,对产量数求和,先得到一个表,每天数据表示: 产品A | 年月| 产量 的一个结果表,当然这里还需要将我们的年月的具体时间给转成列,并且对于的字段内容就是对应产品对应年月的产量。这个操作我们在service层再进行处理
<sql id="yieldTabel">
select A.*,IFNULL(B.PRODUCT_SERIES,'') PRODUCT_SERIES,IFNULL(B.MODEL,'') MODEL, IFNULL(B.MASS_DATE,'2500-01-01') MASS_DATE from txny.dwr_quality_xxx_proces_f A
left join txny.dwr_mt_xxx_report_attr_f B on A.ITEM_CODE = B.PRODUCT_CODE where substr(A.JOB_NUM,3,1)='Z'
</sql>
<sql id="listWhere">
<where>
<if test='isFlag == "1"'>
and date_format(TIME_PERIOD,'%Y-%m-%d') > MASS_DATE
</if>
<if test='isFlag == "0"'>
and date_format(TIME_PERIOD,'%Y-%m-%d') <![CDATA[ <= ]]> MASS_DATE
</if>
<if test="startTime != null and endTime != null">
and date_format(TIME_PERIOD,'%Y-%m-%d') between #{startTime} and #{endTime}
</if>
<if test="proList != null and proList.size > 0">
and PRODUCT_SERIES in
<foreach collection='proList' item="item" open="(" separator="," close=")">
#{item}
</foreach>
</if>
<if test="itemList != null and itemList.size > 0">
and MODEL in
<foreach collection='itemList' item="item" open="(" separator="," close=")">
#{item}
</foreach>
</if>
<if test="codeList != null and codeList.size > 0">
and ITEM_CODE in
<foreach collection='codeList' item="item" open="(" separator="," close=")">
#{item}
</foreach>
</if>
</where>
</sql>
<select id="getYieldTabelProduct" resultType="com.xxx.vo.ProductionShipmentVO">
with table1 as (
<include refid="yieldTabel"/>
)
select sum(QUANTITY) num,PRODUCT_SERIES product,date_format(TIME_PERIOD,'%Y-%m') date from table1
<include refid="listWhere"/>
group by PRODUCT_SERIES,date_format(TIME_PERIOD,'%Y-%m') order by PRODUCT_SERIES,date_format(TIME_PERIOD,'%Y-%m')
</select>
service层 impl实现类
通过运用java8新特性的 stream流处理数据转换,将dao层的接口方法返回的结果表:产品A | 年月| 产量 转换成一个 List<Map<String,Object>>集合,集合中每个元素Map就是一条横线的数据,比如:产品有多少个,list就有多少个元素,查询时间有多少个月,分别就会展示每个月该产品的产量以及合计产量
从而完成了数据年月的行转列
@Named
public class ProductionShipmentService implements IProductionShipmentService {
@Inject
private IProductionShipmentDao iProductionShipmentDao;
@Override
public List<Map<String, Object>> getYieldTabelProduct(ProductionShipmentVO productionShipmentVO) {
//取出前端传参的日期 格式为 yyyy-mm-dd 取出 起始年 月 截止年 月
String[] sArr = productionShipmentVO.getStartTime().split("-");
String[] eArr = productionShipmentVO.getEndTime().split("-");
Integer sYear = Integer.valueOf(sArr[0]);
Integer eYear = Integer.valueOf(eArr[0]);
Integer sMonth = Integer.valueOf(sArr[1]);
Integer eMonth = Integer.valueOf(eArr[1]);
final List<Map<String, Object>> result = new ArrayList<Map<String, Object>>();
List<ProductionShipmentVO> totaList = iProductionShipmentDao.getYieldTabelProduct(productionShipmentVO);
totaList.stream().collect(Collectors.groupingBy(ProductionShipmentVO::getProduct,Collectors.collectingAndThen(Collectors.toList(), m ->{
Map<String, Object> map = new HashMap<String,Object>();
//遍历从起始到截止的查询年月 转换出区间内的每个年月 比如是23年6-9月 那么
//date 就是表示 2023-06 2023-07 2023-08 2023-09 四个日期
for (int i = sYear; i <= eYear; i++) {
for (int j = 1; j <= 12; j++) {
if((i== eYear && j> eMonth) || (i== sYear && j < sMonth)){
continue;
}
String date = String.valueOf(i)+"-"+(j < 10 ? "0"+String.valueOf(j): String.valueOf(j));
//anyMatch 有一个或一个以上的元素满足函数参数计算结果为true那整个方法返回值为true
//判断组内的数据中是否有该日期的数据 有则将其一条数据取出
if(m.stream().anyMatch(f->f.getDate().equals(date))){
ProductionShipmentVO res = m.stream().filter(f->f.getDate().equals(date)).findFirst().get();
//map中插入日期 对应的产量 以及产品名称
map.put(res.getDate(), res.getNum());
map.put("product", res.getProduct());
}else {
//如果没有该日期数据 就插入产量null
map.put(date, null);
}
}
}
//每个年月数据取好之后,最后就是插入总累计产量
Integer sum = m.stream().mapToInt(ProductionShipmentVO::getNum).sum();
map.put("num", sum);
//把该产品的一条记录map插入list集合 依次将不同产品的map遍历插入
result.add(map);
return null;
})));
return result;
}
}
那么前端框架,通过获取得到返回的数据,根据list中的元素map的键值对数,就能判断表头需要动态加载几列,比如前面展示的 查询3-6个月的数据那么表头展示就是这样:动态赋值获取表列数