MySQL使用GROUP_CONTACT和JSON_OBJECT代替烦人的子查询

本文介绍如何使用Mybatis的JsonTypeHandler和GROUP_CONCAT函数,避免子查询,高效获取用户表user与角色表role的关联数据,以JSON形式返回所有角色。重点讲解了CASE WHEN与GROUP_CONCAT的结合应用和TypeHandler的使用。

假设一个场景:一个用户可以有多个角色,用户列表需要显示用户所有的角色名称,如下表

ID名称角色
1张三管理员、审核员
2李四普通员工

那么我们可以设计三个表:user(用户表) role(角色表) user_role(用户角色表)

第一种方法就是使用mybatis的association标签关联,但这个方法,有多少条用户记录,就要多少次的子查询来关联角色信息,所以不推荐使用

第二种方法,使用GROUP_CONTACT和JSON_OBJECT把角色列表的数据封装成一个json数组,再使用TypeHandler解析成一个List

Mapper.xml如下:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.system.mapper.SysUserMapper">

    <resultMap type="SysUser" id="SysUserWithJsonResult">
        <id property="id" column="id"/>
        <result property="userName" column="user_name"/>
        <result property="nickName" column="nick_name"/>
        <result property="email" column="email"/>
        <result property="phonenumber" column="phonenumber"/>
        <result property="password" column="password"/>
        <result property="status" column="status"/>
        <result property="delFlag" column="del_flag"/>
        <result property="createBy" column="create_by"/>
        <result property="createTime" column="create_time"/>
        <result property="updateBy" column="update_by"/>
        <result property="updateTime" column="update_time"/>
        <result property="roles" column="roles" javaType="SysRole" typeHandler="com.example.common.mybatis.handler.JsonTypeHandler" />

    </resultMap>


    <select id="selectUserList" parameterType="SysUser" resultMap="SysUserWithJsonResult">
        select DISTINCT
        u.id,u.user_name,u.nick_name,u.email,u.phonenumber,u.status,u.create_time,
        CASE WHEN max(r.id) is not null THEN CONCAT('[',GROUP_CONCAT(JSON_OBJECT('id',r.id,'roleName',r.role_name)),']') END roles
        from sys_user u
        left join sys_user_role ur on u.id = ur.user_id
        left join sys_role r on ur.role_id = r.id
        where u.del_flag = '0'
        <if test="id != null and id != 0">
            AND u.id = #{id}
        </if>
        <if test="userName != null and userName != ''">
            AND u.user_name like concat('%', #{userName}, '%')
        </if>
        <if test="status != null and status != ''">
            AND u.status = #{status}
        </if>
        <if test="phonenumber != null and phonenumber != ''">
            AND u.phonenumber like concat('%', #{phonenumber}, '%')
        </if>
      
        group by u.id
    </select>
    

</mapper> 

其中,关键的语句是:

CASE WHEN max(r.id) is not null THEN CONCAT('[',GROUP_CONCAT(JSON_OBJECT('id',r.id,'roleName',r.role_name)),']') END roles

result中的javaType也不能少

TypeHandler如下:

package com.exapmle.common.mybatis.handler;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class JsonTypeHandler<T> implements TypeHandler<T> {
    private Class<T> type;

    public JsonTypeHandler(Class<T> type) {
        if (type == null) {
            throw new IllegalArgumentException("Type argument cannot be null");
        }
        // 拿到xml中配置的javaType
        this.type = type;
    }

    @Override
    public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
        ps.setString(i, parseJsonString(parameter));
    }

    @Override
    public T getResult(ResultSet rs, String columnName) throws SQLException {
        return parseJavaObject(rs.getString(columnName));
    }

    @Override
    public T getResult(ResultSet rs, int columnIndex) throws SQLException {
        return parseJavaObject(rs.getString(columnIndex));
    }

    @Override
    public T getResult(CallableStatement cs, int columnIndex) throws SQLException {
        return parseJavaObject(cs.getString(columnIndex));
    }

    private String parseJsonString(T parameter) {
        if (parameter == null) {
            return null;
        }
       
        return JSON.toJSONString(parameter, SerializerFeature.WriteClassName,SerializerFeature.NotWriteRootClassName);
    }

    @SuppressWarnings("unchecked")
    private T parseJavaObject(String parameter) {
        if (parameter == null) {
            return null;
        }
        if (parameter.startsWith("[")) {
            return (T) JSON.parseArray(parameter, this.type);
        }
        return JSON.parseObject(parameter, this.type);
    }

}

我有表user_detail。读取了30天的数。dt为日期,user_id为用户id,stats_date_period是根据日期dt,lateral view explode得到的。枚举值是T1/T3/T7/T30。为包含关系分别为1天,3天,7天,30天,相当于做成了新的维度字段,日期区间。假如我想通过这个表。查询每个用户最近一次连续登陆在每个日期区间登陆了几天。单天也算。因为存在T1。例如(20251101-20251103,20251106-20251110,20251128-20251130)这三段日期连续登陆了,那么结果就是T1:1天(范围内有20251130);T3:3天(范围内有20251128-20251130);T7:3天(范围内有20251128-20251130);T30:3天(因为范围内最后一段连续登陆时20251128-20251130)。帮我补全我的aaa逻辑 with user_detail as ( select dt ,b_stats_date_period as stats_date_period ,user_id ,loan_account_id ,active_device_id_list ,active_os_list ,active_platform_name_list ,active_sdk_type_list ,ip ,created_ts ,created_hour ,city ,province ,country ,event_code ,login_type from ( select dt ,case when dt = '20251207' then array('T1','T3','T7','T30') when dt between '20251205' and '20251207' then array('T3','T7','T30') when dt between '20251201' and '20251207' then array('T7','T30') when dt between '20251108' and '20251207' then array('T30') end as stats_date_period ,user_id ,loan_account_id ,fin_device_id as active_device_id_list ,os as active_os_list ,platform_name as active_platform_name_list ,lib as active_sdk_type_list ,ip ,created_ts ,concat( from_unixtime(floor(created_ts / 1000), 'HH'), ':00-', lpad(cast( (cast(from_unixtime(floor(created_ts / 1000), 'HH') as int) + 1) as string), 2, '0'), ':00' ) as created_hour -- 拼接成时间段。如:11:00-12:00 ,city ,province ,country ,case when event_code = 'login_login_result' then 'login' when event_code = 'loan_b1_c148_exposure' then 'repayment_plan_exposure' when event_code = 'auth_b597_d598_click' then 'living_confirmed_photo_click' when event_code = 'AppStart' then 'app_start' when event_code = 'loan_b1_c41_d683_click' then 'credit_increase_click' when event_code = 'auth_b603_d695_click' then 'contact_from_contacts' when event_code = 'AppEnd' then 'app_end' when event_code in ('login_b222_c233_click','loan_b1_c146_d149_exposure') then 'coupon_pageview' when event_code = 'middleincome_b827_c829_d831_click' then 'change_bank_card_click' when event_code = 'general_delete_account_result' and get_json_object(content,'$.is_success') = 'true' then 'delete_account_succ' end as event_code ,if(event_code = 'login_login_result', get_json_object(content,'$.type'), null) as login_type -- 登陆方式 from ec_dwd.dwd_ec_log_di_sa_app_events where dt between '20251108' and '20251207' and user_id = '25274031' ) t LATERAL VIEW explode(stats_date_period) b as b_stats_date_period ) , aaa as ( select dt ,user_id ,loan_account_id ,stats_date_period -- T1/T3/T7/T30 from user_detail where event_code = 'app_start' group by dt ,user_id ,loan_account_id ,stats_date_period ) select user_id, loan_account_id, stats_date_period, app_consecutive_launch_days -- 最后一次连续登陆天数(含单天) from aaa
最新发布
12-11
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值