存储动态列的结果集(行列互转)--java、mybatis、orcale

业务上需要存储动态列的数据 通过行列互转的方式实现

数据库中动态列的几种设计思路

  • 使用数据库DDL进行动态创建

  • 使用数据库预留字段(宽表)

  • 使用数据库中的json数据类型

  • 使用非关系型数据库(MongoDB等)

这是网上给出的几个基本思路,考虑到我们业务上的功能不太适合这些方法,所有最后选择通过行列互转的方式实现。

数据库设计

数据库使用了4个字段,key是原表中的列名,value是对应的值,sort有两个作用,排序和分组,row_id用于关联主表。数据库结构如下表table

名称 类型 注释
COL_KEY VARCHAR2(50) 列名(key)
COL_VALUE VARCHAR2(255) 列值(value)
ROW_ID VARCHAR2(50) 所属主表id
SORT NUMBER 排序

行转列(存储)

第一步是将动态列的结果集存储起来

列名1 列名2 列名3 ... 列名N
a b c ... e
1 2 3 ... 5
A B C ... E
6 7 8 ... 10

结果集返回的数据是List<Map> 结构,这里不做太多赘述了

直接上代码

//存入列表(列转行)
List<Table> list= new ArrayList<>();//新建一个table类型的集合
long index = 1;//排序索引
for (Map map : list) { //list是返回的结果集,直接遍历
// 通过keySet方法获取list中一个map的所有key
Set<String> keySet = map.keySet();
for (String s : keySet) { //继续遍历所有key
//新建临时对象
Table table = new Table();
table.setColKey(s);//设置属性Key
//设置属性value,通过get(key)的方式,同时将value的类型都处理成String
table.setColValue(String.valueOf( map.get(s)));
table.setSort(index);//设置分组条件的同时还能排序
table.setRowId(“自定义”);//根据传过来的值设置属于那个主表
list.add(table);//加入集合中
}
index++;
}
service.insertBeath(list);

Mapper.xml代码

//Oracle实现方式
<insert id="insertBeath">
insert into table(col_key,col_value,row_id,sort)
select A.* from (
<foreach collection="list" item="item" index="index" separator="UNION ALL">
select
#{item.colKey,jdbcType=VARCHAR} col_key,
#{item.colValue,jdbcType=VARCHAR} col_value,
#{item.rowId,jdbcType=VARCHAR} row_id,
#{item.sort,jdbcType=INTEGER} sort
from DUAL
</foreach>
) A
</insert>

列转行(显示)

上一步已经将数据存入table表中,数据如下

col_key col_value row_id sort
列名1 a id 1
列名2 b id 1
列名3 c id 1
列名4 d id 1
列名1 1 id 2
列名2 2 id 2
列名3 3 id 2
列名4 4 id 2

存完之后下一步就该显示了,虽然我们是通过列的方式存的,但是我们显示的时候还是应该将列转换成行来显示,就和存之前的结果集一样

我用的数据库是Oracle,因此有两种方式来实现

SQL通用方式

SQL语句如下

select sort,
MAX(case col_key when '列名1' then col_value end) as 列名1,
MAX(case col_key when '列名2' then col_value end) as 列名2,
MAX(case col_key when '列名3' then col_value end) as 列名3,
MAX(case col_key when '列名4' then col_value end) as 列名4
from table
where row_id = id
group by sort order by sort

这种方式是使用case when来实现,根据分组后的结果使用聚合函数MAX()查询将列换成行来显示。

Oracle专用方式

SQL语句如下

SELECT * FROM table
PIVOT (
MAX(col_value) FOR col_key IN ('列名1', '列名2', '列名3','列名4')
)
where row_id = id
order by sort

这是用Oracle独有的关键字pivot实现,根据列名自动将列换成行显示,语法更加简单。

结合Java和Mybatis

上面两种方式都有个缺点,就是都只能将列名写死来分组。而实践上存储的结果集是动态的列,网上有很多都是通过数据库存储过程来实现的。

下面是我通过Mybatis框架实现的代码,大致思路是先根据分组查询出所有的列名,再将列名作为查询条件去行转列中查询

Java

List<String> group = service.selectGroup(rowId);
List<Map> list= service.selectDetailView(rowId, group);
System.err.println(list);

Mapper.xml

//查询当前表中所有的列名(根据分组来查)
<select id="selectGroup" resultType="java.lang.String">
select col_key from monitor_detail where monitor_id = #{monitorId} group by col_key
</select>
//SQL通用方式
<select id="selectDetailView" resultType="Map">
select
<foreach item="item" collection="group" separator="," index="index">
MAX(case col_key when '${item}' then col_value end) as ${item}
</foreach>
from monitor_detail where monitor_id = #{monitorId} group by sort order by sort
</select>
//Orcale专用方式
<select id="selectDetailView" resultType="Map">
SELECT * FROM monitor_detail
PIVOT (
MAX(col_value) FOR col_key IN
<foreach item="item" collection="group" open="(" separator="," close=")" index="index">
'${item}' as ${item}
</foreach>
)
where monitor_id = #{monitorId} order by sort
</select>

到这里有人可能会问,那直接将分组查询列名的语句,作为子查询在pivot in中不是更好。当时我就是这么想的,结果发现pivot in中不能直接写子查询,也不能写变量。

posted @   Cloong  阅读(630)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示