sql
<select id="getPersonInfo" resultType="com.purvar.report.facade.vo.jxappuser.PersonDetailVO">
SELECT
#{request.memberId} AS id,
NULL AS departRank,
NULL AS levelRank,
NVL(f.visitCount, 0) AS visitCount,
CASE
WHEN NVL(p.totalProjectCount, 0) = 0 THEN 0
ELSE ROUND(NVL(p.futureIndustryCount, 0) * 100.0 / p.totalProjectCount, 2)
END AS futureIndustryRate,
NVL(d.projectCount, 0) AS projectCount,
NULL AS irr,
NULL AS tvpi,
NULL AS leadRatio,
NULL AS manageNum
FROM DUAL
LEFT JOIN (
SELECT COUNT(1) AS visitCount
FROM ADS.jx_proj_follow_info f
WHERE (f.is_delete = 0 OR f.is_delete IS NULL)
<choose>
<when test="request.memberName != null and request.memberName != ''">
AND f.follower IS NOT NULL
AND INSTR(
',' || REPLACE(REPLACE(f.follower, ',', ','), ' ', '') || ',',
',' || REPLACE(REPLACE(#{request.memberName}, ',', ','), ' ', '') || ','
) > 0
</when>
<otherwise>
AND 1 = 0
</otherwise>
</choose>
) f ON 1 = 1
LEFT JOIN (
SELECT
COUNT(DISTINCT p.proj_id) AS totalProjectCount,
COUNT(DISTINCT CASE WHEN p.is_future_industry = '1' THEN p.proj_id END) AS futureIndustryCount
FROM ADS.jx_proj_info p
WHERE (p.is_delete = 0 OR p.is_delete IS NULL)
<choose>
<when test="request.memberId != null">
AND p.member_id = #{request.memberId}
</when>
<otherwise>
AND 1 = 0
</otherwise>
</choose>
) p ON 1 = 1
LEFT JOIN (
SELECT COUNT(DISTINCT d.proj_id) AS projectCount
FROM ADS.jx_decision_info d
WHERE (d.is_delete = 0 OR d.is_delete IS NULL)
<choose>
<when test="request.memberId != null">
AND d.member_id = TO_CHAR(#{request.memberId})
</when>
<otherwise>
AND 1 = 0
</otherwise>
</choose>
) d ON 1 = 1
</select>
SQL 语句详细解析
这段 SQL 是一个 MyBatis 映射文件中的查询语句,用于获取个人(成员)的详细信息。下面我将从多个维度进行详细解析:
1. 整体功能
此 SQL 的主要功能是查询指定成员(员工)的项目相关统计信息,包括:
- 访问次数(visitCount)
- 未来产业项目占比(futureIndustryRate)
- 负责项目数量(projectCount)
- 其他预留的统计字段(如 IRR、TVPI 等,当前为 NULL)
2. 核心结构解析
2.1 主查询部分
sql
SELECT
#{request.memberId} AS id, -- 直接使用传入的成员ID作为结果ID
NULL AS departRank, -- 部门排名(暂未实现)
NULL AS levelRank, -- 级别排名(暂未实现)
NVL(f.visitCount, 0) AS visitCount, -- 访问次数,空值转为0
CASE
WHEN NVL(p.totalProjectCount, 0) = 0 THEN 0
ELSE ROUND(NVL(p.futureIndustryCount, 0) * 100.0 / p.totalProjectCount, 2)
END AS futureIndustryRate, -- 未来产业项目占比(百分比)
NVL(d.projectCount, 0) AS projectCount, -- 决策项目数量
NULL AS irr, -- 内部收益率(暂未实现)
NULL AS tvpi, -- 投资回报倍数(暂未实现)
NULL AS leadRatio, -- 领导力比例(暂未实现)
NULL AS manageNum -- 管理数量(暂未实现)
FROM DUAL
- DUAL:Oracle 中的虚拟表,用于构建单行查询
- NVL():Oracle 的空值处理函数,将 NULL 转为指定值(这里是0)
- CASE WHEN:计算未来产业项目占比,避免除零错误
2.2 三个 LEFT JOIN 子查询
2.2.1 访问次数统计(f 子查询)
sql
LEFT JOIN (
SELECT COUNT(1) AS visitCount
FROM ADS.jx_proj_follow_info f
WHERE (f.is_delete = 0 OR f.is_delete IS NULL)
<choose>
<when test="request.memberName != null and request.memberName != ''">
AND f.follower IS NOT NULL
AND INSTR(
',' || REPLACE(REPLACE(f.follower, ',', ','), ' ', '') || ',',
',' || REPLACE(REPLACE(#{request.memberName}, ',', ','), ' ', '') || ','
) > 0
</when>
<otherwise>
AND 1 = 0 -- 条件不满足时不统计
</otherwise>
</choose>
) f ON 1 = 1
功能 :统计该成员的项目跟进记录数
关键逻辑:
- 从
jx_proj_follow_info表统计未删除的记录 - 当传入成员姓名时,使用
INSTR函数在follower字段中查找该姓名 REPLACE函数处理中文逗号和空格,确保格式统一1 = 0表示条件不满足时返回空结果
2.2.2 项目统计(p 子查询)
sql
LEFT JOIN (
SELECT
COUNT(DISTINCT p.proj_id) AS totalProjectCount,
COUNT(DISTINCT CASE WHEN p.is_future_industry = '1' THEN p.proj_id END) AS futureIndustryCount
FROM ADS.jx_proj_info p
WHERE (p.is_delete = 0 OR p.is_delete IS NULL)
<choose>
<when test="request.memberId != null">
AND p.member_id = #{request.memberId}
</when>
<otherwise>
AND 1 = 0
</otherwise>
</choose>
) p ON 1 = 1
功能 :统计成员负责的项目总数和未来产业项目数
关键逻辑:
totalProjectCount:成员负责的独立项目总数futureIndustryCount:标记为未来产业(is_future_industry = '1')的项目数- 条件判断:当传入成员ID时才进行统计
2.2.3 决策项目统计(d 子查询)
sql
LEFT JOIN (
SELECT COUNT(DISTINCT d.proj_id) AS projectCount
FROM ADS.jx_decision_info d
WHERE (d.is_delete = 0 OR d.is_delete IS NULL)
<choose>
<when test="request.memberId != null">
AND d.member_id = TO_CHAR(#{request.memberId})
</when>
<otherwise>
AND 1 = 0
</otherwise>
</choose>
) d ON 1 = 1
功能 :统计成员参与的决策项目数量
关键逻辑:
- 从
jx_decision_info表统计未删除的决策记录 TO_CHAR():将数值型成员ID转为字符串进行匹配- 同样使用条件判断确保参数有效性
3. MyBatis 动态 SQL 标签解析
<choose>、<when>、<otherwise>
- 作用:实现条件分支逻辑
- 示例 :当
request.memberName不为空时,执行姓名匹配逻辑;否则返回空结果 - 优势:避免在参数为空时产生无效查询
#{request.memberId} 和 #{request.memberName}
- 作用:MyBatis 的参数占位符,防止 SQL 注入
- 区别 :
memberId:用于精确匹配(项目表和决策表)memberName:用于模糊匹配(跟进表的 follower 字段)
4. 业务场景分析
4.1 适用场景
此 SQL 可能用于以下业务场景:
- 个人工作台/仪表盘:展示个人的关键绩效指标
- 领导查看下属数据:了解团队成员的项目参与情况
- 绩效考核:作为项目参与度和贡献度的参考数据
- 人才盘点:评估员工在重点项目(未来产业)中的参与比例
4.2 数据流向
成员ID/姓名 → 参数传入 → 三个子查询并行统计 → 主查询汇总 → VO对象返回
4.3 设计特点
- 模块化设计:三个子查询职责清晰,便于维护
- 空值安全 :全程使用
NVL处理 NULL 值 - 条件防御:参数为空时返回空结果,避免全表扫描
- 扩展性:预留了多个统计字段(如 IRR、TVPI 等)为后续功能留出接口
5. 潜在优化建议
5.1 性能方面
- 三个子查询可考虑合并或使用 WITH 子句(CTE)
INSTR函数对follower字段的模糊匹配可能影响性能,建议建立专门的关联表- 频繁查询时可考虑添加适当的索引
5.2 可读性方面
- 可添加注释说明各字段的业务含义
- 复杂的字符串处理逻辑可提取为数据库函数
- 魔法数字(如
'1'表示未来产业)可定义为常量
5.3 功能扩展
- 当前许多字段返回 NULL,后续可根据业务需求逐步实现
- 可添加时间范围过滤(如统计最近一年的数据)
- 可考虑添加项目状态过滤(只统计进行中的项目)
6. 总结
这个 SQL 语句是一个典型的个人数据统计查询,具有以下特点:
- 安全性:使用参数化查询防止 SQL 注入
- 健壮性:全面处理 NULL 值和边界条件
- 模块化:不同统计维度分离,便于理解和维护
- 可扩展:预留字段支持未来功能扩展
- 业务导向:紧密围绕项目管理的核心指标设计
在实际使用中,建议:
- 确保传入参数的合法性验证
- 监控大数据量下的查询性能
- 根据业务发展适时调整统计逻辑