当前位置: 首页 > article >正文

USER与多组织关联的SQL查询以及几个关键函数用法

文章目录

  • 场景说明
  • 表结构及数据
  • 查询语句
    • 查询结构及各部分功能
    • 总结
  • 结果示例
  • 关键点解析
    • 查询结构及各部分功能
    • 整体解释
  • 总结
    • GROUP_CONCAT
      • 语法
      • 示例
      • 注意
    • SEPARATOR
      • 示例
    • COALESCE
      • 语法
      • 示例
    • LENGTH
      • 示例
      • 常见用法
      • FIND_IN_SET
      • 语法
      • 示例
      • 在 JOIN 中的应用
      • COUNT(*)
      • 示例
      • 注意
    • 综合示例
      • 结果说明
    • ifnull 函数
      • 语法
      • 示例
    • 总结


场景说明

假设存在两张表:

  • users 表存储用户信息,其中 org_ids 字段以逗号分隔形式保存关联的组织ID(如 “101,205,308”)。
  • departments 表存储部门/组织信息。

需要实现以下功能:

  1. 查询用户详情,并展示其关联的所有组织名称(合并为一行)。
  2. 计算每个用户关联的组织总数(通过 org_ids 解析)。
  3. 验证计算结果与实际关联数量是否一致。

表结构及数据

部门表 (departments)
在这里插入图片描述
在这里插入图片描述

用户表 (users)
在这里插入图片描述
在这里插入图片描述


查询语句

从users表和departments表中获取用户及其关联的所有部门信息,同时计算每个用户关联的部门数量,并验证这个计算的数量是否与实际关联的部门数量一致

SELECT 
  u.id AS user_id,
  u.name AS user_name,
  -- 使用 GROUP_CONCAT 合并组织名称
  GROUP_CONCAT(d.name ORDER BY d.id SEPARATOR ', ') AS department_names,
  -- 计算 org_ids 中的组织总数(处理空值)
  CASE 
    WHEN COALESCE(u.org_ids, '') = '' THEN 0 
    ELSE LENGTH(u.org_ids) - LENGTH(REPLACE(u.org_ids, ',', '')) + 1 
  END AS calculated_count,
  -- 实际关联数量(通过 COUNT 聚合)
  COUNT(d.id) AS actual_count
FROM 
  users u
LEFT JOIN departments d 
  -- 使用 FIND_IN_SET 匹配逗号分隔的ID
  ON FIND_IN_SET(d.id, u.org_ids) > 0
GROUP BY 
  u.id, u.name;

这段SQL查询旨在从users表和departments表中获取用户及其关联的所有部门信息,同时计算每个用户关联的部门数量,并验证这个计算的数量是否与实际关联的部门数量一致。下面是逐行详细解释:

查询结构及各部分功能

  GROUP_CONCAT(d.name ORDER BY d.id SEPARATOR ', ') AS department_names,
  • GROUP_CONCAT(d.name ORDER BY d.id SEPARATOR ', ') AS department_names: 使用GROUP_CONCAT函数将所有关联的部门名称按部门id排序后合并成一个字符串,用, 作为分隔符,并命名为department_names。这一步是将多个部门名称合并为一个字符串,以便在一个结果集中展示。
  CASE 
    WHEN COALESCE(u.org_ids, '') = '' THEN 0 
    ELSE LENGTH(u.org_ids) - LENGTH(REPLACE(u.org_ids, ',', '')) + 1 
  END AS calculated_count,
  • CASE WHEN COALESCE(u.org_ids, '') = '' THEN 0 ELSE LENGTH(u.org_ids) - LENGTH(REPLACE(u.org_ids, ',', '')) + 1 END AS calculated_count:
    • COALESCE(u.org_ids, ''): 如果org_ids为NULL,则返回空字符串''
    • WHEN COALESCE(u.org_ids, '') = '' THEN 0: 如果org_ids为空或NULL,则返回0,表示没有关联的部门。
    • ELSE LENGTH(u.org_ids) - LENGTH(REPLACE(u.org_ids, ',', '')) + 1: 计算逗号的数量(即部门的数量)。通过比较原始字符串长度和移除所有逗号后的字符串长度差来确定逗号的数量,再加上1得到部门总数。
  COUNT(d.id) AS actual_count
  • COUNT(d.id) AS actual_count: 统计每个用户实际关联的部门数量。由于使用了LEFT JOIN,即使没有匹配的部门记录也会保留用户记录,此时COUNT(d.id)将为0。
LEFT JOIN departments d 
  ON FIND_IN_SET(d.id, u.org_ids) > 0
  • LEFT JOIN departments d ON FIND_IN_SET(d.id, u.org_ids) > 0:
    • LEFT JOIN departments d: 将departments表(别名为d)与users表进行左连接。这意味着即使没有匹配的部门记录也会保留用户记录。
    • ON FIND_IN_SET(d.id, u.org_ids) > 0: 使用FIND_IN_SET函数检查departments表中的id是否存在于users表的org_ids字段中(以逗号分隔的形式)。如果存在,则返回该ID的位置(大于0),否则返回0。
GROUP BY 
  u.id, u.name;
  • GROUP BY u.id, u.name: 根据用户idname进行分组。这样每个用户的多条部门记录会被合并成一条结果,包含所有关联的部门名称。

总结

这段查询的核心在于:

  1. GROUP_CONCAT:将同一个用户关联的所有部门名称合并为一个字符串。
  2. CASE语句:计算org_ids字段中逗号分隔的部门ID数量,处理可能存在的空值情况。
  3. COUNT(d.id):统计实际关联的部门数量,确保即使没有关联的部门也能正确返回0。
  4. LEFT JOINFIND_IN_SET:有效地将用户与他们的关联部门匹配起来,即使某些用户没有关联任何部门。

这种方法适用于需要在非规范化数据结构中解析用户与多个组织之间的关系的情况。然而,对于更高效的数据处理,推荐考虑数据库设计的规范化,例如使用中间表来存储用户和部门之间的多对多关系。

结果示例

在这里插入图片描述

关键点解析

  1. LEFT JOIN + FIND_IN_SET
    通过 FIND_IN_SET(d.id, u.org_ids) 将用户表与部门表关联,兼容逗号分隔的ID。

  2. GROUP_CONCAT
    将关联的多个部门名称合并为一行字符串,按 id 排序并以逗号分隔。

  3. 计算逗号数量
    LENGTH(org_ids) - LENGTH(REPLACE(org_ids, ',', '')) + 1 通过替换逗号前后的长度差,确定组织数量。

  4. 子查询验证(可选)
    可通过子查询验证实际关联数量:
    为users表中的每个用户计算其org_ids字段中所列出的部门ID对应的部门数量

   SELECT 
     u.id,
     (SELECT COUNT(*) 
      FROM departments 
      WHERE FIND_IN_SET(id, u.org_ids) > 0
     ) AS subquery_count
   FROM users u;

这段SQL查询的目的是为users表中的每个用户计算其org_ids字段中列出的部门ID对应的部门数量。具体来说,它通过子查询的方式对每个用户的org_ids字段进行解析,并统计这些ID在departments表中存在的记录数。下面是逐行详细解释:

查询结构及各部分功能

SELECT 
  u.id,
  • SELECT u.id,: 这一部分选择users表中的id字段。u是给users表起的别名,用于简化后续引用。
  (SELECT COUNT(*) 
   FROM departments 
   WHERE FIND_IN_SET(id, u.org_ids) > 0
  ) AS subquery_count
  • (SELECT COUNT(*) FROM departments WHERE FIND_IN_SET(id, u.org_ids) > 0) AS subquery_count:
    • SELECT COUNT(*) FROM departments: 子查询从departments表中选取所有记录。
    • WHERE FIND_IN_SET(id, u.org_ids) > 0: 使用FIND_IN_SET函数检查departments表中的id是否存在于当前用户(u)的org_ids字段中(以逗号分隔的形式)。如果存在,则返回该ID的位置(大于0),否则返回0。这意味着只有当departments.id出现在u.org_ids中时,这条记录才会被计数。
    • COUNT(*): 计算满足上述条件的记录总数,即用户关联的有效部门数量。
    • AS subquery_count: 将这个子查询的结果命名为subquery_count,表示对于每个用户,其org_ids中实际存在的部门数量。
FROM users u;
  • FROM users u;: 指定查询的数据源是users表,并给它起别名u。这允许在查询中使用更简洁的方式来引用users表中的字段。

整体解释

  1. 外部查询:外部查询遍历users表中的每一行(即每一个用户)。
  2. 子查询:对于每个用户,都会执行一次子查询来计算其org_ids字段中列出的所有部门ID对应的实际有效部门数量。
    • 子查询会遍历departments表中的所有记录,并使用FIND_IN_SET函数检查当前用户的org_ids字段中是否包含departments表中的id
    • 如果包含,则该记录会被计入总和;否则不计入。
  3. 结果集:最终结果集中,每一行代表一个用户及其关联的有效部门数量(subquery_count)。

总结

此案例演示了如何处理逗号分隔的关联ID,结合 FIND_IN_SETGROUP_CONCAT 和字符串函数实现多表查询与数据聚合,适用于非规范化数据结构的常见操作场景。

以下是 SQL 中几个关键函数的详细解释及用法示例:

GROUP_CONCAT

将多行数据合并为一个字符串,常用于分组后聚合文本信息。

语法

GROUP_CONCAT(
  [DISTINCT] 字段名 
  [ORDER BY 排序字段 ASC/DESC] 
  [SEPARATOR '分隔符']
)

示例

假设 users 表与 departments 表关联后,用户张三关联了 技术部市场部

-- 合并部门名称,按 id 排序,用逗号分隔
SELECT 
  u.id,
  GROUP_CONCAT(d.name ORDER BY d.id SEPARATOR ', ') AS departments
FROM users u
LEFT JOIN departments d ON FIND_IN_SET(d.id, u.org_ids) > 0
GROUP BY u.id;

-- 结果示例:
-- | id | departments          |
-- | 1  | 技术部, 市场部       |

注意

  • DISTINCT 可去重(如 GROUP_CONCAT(DISTINCT d.name))。
  • 默认分隔符为逗号(,),可通过 SEPARATOR 自定义(如 SEPARATOR '|')。

SEPARATOR

配合 GROUP_CONCAT 使用,指定合并字符串时的分隔符。

示例

-- 用分号分隔部门名称
GROUP_CONCAT(d.name SEPARATOR '; ')
-- 结果示例:技术部; 市场部

COALESCE

返回参数列表中第一个非 NULL 的值,常用于处理空值。

语法

COALESCE(字段或表达式1, 字段或表达式2, ..., 默认值)

示例

处理用户表中 org_idsNULL 的情况:

-- 若 org_ids 为 NULL,返回空字符串
SELECT 
  COALESCE(org_ids, '') AS safe_org_ids 
FROM users;

-- 用户王五的 org_ids 为 NULL,结果为 ''

LENGTH

返回字符串的字符数(字节数取决于字符编码)

示例

计算 org_ids 的字符数:

SELECT 
  org_ids,
  LENGTH(org_ids) AS length 
FROM users;

-- 若 org_ids = '101,205',结果为 7(字符数)

常见用法

计算逗号分隔值的数量:

-- 公式:逗号数量 + 1 = 元素数量
LENGTH(org_ids) - LENGTH(REPLACE(org_ids, ',', '')) + 1

-- 示例:
-- org_ids = '101,205' → 1 个逗号 → 2 个元素

FIND_IN_SET

检查一个字符串是否存在于逗号分隔的字符串列表中,返回其位置(从 1 开始),不存在则返回 0

语法

FIND_IN_SET(目标字符串, 逗号分隔的字符串)

示例

检查部门 id=101 是否在用户张三的 org_ids 中:

SELECT 
  FIND_IN_SET('101', '101,205') AS position;

-- 结果为 1(表示存在且位于第一个位置)

在 JOIN 中的应用

-- 关联用户和部门表
LEFT JOIN departments d 
  ON FIND_IN_SET(d.id, u.org_ids) > 0

-- 等价逻辑:d.id 存在于 u.org_ids 的逗号分隔列表中

COUNT(*)

统计符合条件的行数(包括 NULL 值)。

示例

统计用户关联的部门数量:

SELECT 
  u.id,
  COUNT(*) AS total 
FROM users u
LEFT JOIN departments d 
  ON FIND_IN_SET(d.id, u.org_ids) > 0
GROUP BY u.id;

-- 用户张三关联 2 个部门 → total = 2
-- 用户王五未关联部门 → total = 1(因为 LEFT JOIN 会保留用户行)

注意

  • COUNT(字段名) 会忽略 NULL 值,COUNT(*) 不会。
  • 推荐使用 COUNT(d.id) 替代 COUNT(*) 来准确统计匹配的部门数:
  -- 用户王五的 d.id 为 NULL,COUNT(d.id) = 0
  COUNT(d.id) AS actual_count

综合示例

结合所有函数,实现用户与部门的关联统计:

SELECT 
  u.id,
  COALESCE(GROUP_CONCAT(d.name SEPARATOR ', '), '未关联部门') AS departments,
  CASE 
    WHEN COALESCE(u.org_ids, '') = '' THEN 0 
    ELSE LENGTH(u.org_ids) - LENGTH(REPLACE(u.org_ids, ',', '')) + 1 
  END AS calculated_count,
  COUNT(d.id) AS actual_count
FROM users u
LEFT JOIN departments d 
  ON FIND_IN_SET(d.id, u.org_ids) > 0
GROUP BY u.id;

结果说明

在这里插入图片描述


ifnull 函数

if(ly.plan ='8', ifnull(ly.set_out,0),5) isSend
-- 首先检查ly.plan是否等于 '8'
-- 		如果ly.plan确实等于 '8',那么检查ly.set_out字段:
-- 		如果ly.set_out不是NULL,则返回ly.set_out的实际值
-- 		如果ly.set_out是NULL,则返回 0
-- 如果ly.plan不等于 '8',则直接返回 5

IFNULL 是一个在 SQL 中使用的控制流函数,主要用于处理可能的 NULL 值。它接受两个参数:如果第一个参数不是 NULL,则返回第一个参数的值;如果第一个参数是 NULL,则返回第二个参数指定的值。

语法

IFNULL(expression, replacement)
  • expression:要检查是否为 NULL 的表达式。
  • replacement:如果 expression 是 NULL,则返回该替代值。

示例

假设有一个名为 employees 的表,其中包含 idnamemanager_id 列。如果你想要查询每个员工及其经理的名字,但有些员工没有经理(即 manager_id 为 NULL),你可以使用 IFNULL 来提供一个默认值,比如 “No Manager”。

SELECT NAME, IFNULL(( SELECT NAME FROM employees AS mgr WHERE emp.manager_id = mgr.id ),
	'No Manager' 
) AS manager_name 
FROM
	employees AS emp;

这段 SQL 查询会查找每个员工的经理名字,并且如果某个员工没有经理(manager_id 为 NULL),则显示 “No Manager”。

注意:不同的数据库管理系统可能会有不同的处理 NULL 值的函数

总结

  • GROUP_CONCAT + SEPARATOR:合并多行文本,控制分隔符。
  • COALESCE:处理空值,避免计算错误。
  • LENGTH + REPLACE:计算逗号分隔值的数量。
  • FIND_IN_SET:匹配逗号分隔的列表中的值。
  • COUNT(*) vs COUNT(字段):注意对 NULL 值的处理差异。

这些函数在处理非规范化数据(如逗号分隔的关联ID)时非常实用,但需注意性能问题(如 FIND_IN_SET 无法利用索引)。


http://www.kler.cn/a/584790.html

相关文章:

  • Redis 单线程架构:化繁为简的性能哲学
  • Android 页面封装实战:打造高复用、灵活的 Scaffold 式布局与事件处理
  • vscode 配置golang开发环境
  • 《今日AI-人工智能-编程日报》
  • 从零开始写3D游戏引擎(开发环境VS2022+OpenGL)之九点五 编写运动摄像机镜头的源代码 细嚼慢咽逐条读代码系列
  • flutter 开发中的tips 【持续更新】
  • iframe 内事件冒泡
  • 数据标注质量对AI模型质量的影响分析
  • 微信小程序-实现锚点跳转,页面加载后自动跳转、点击跳转到指定位置
  • vue判断视频链接是否有效
  • windows平台的ffmpeg编译使用
  • 5.1 程序调试
  • Qt-开发设置窗体透明效果
  • Gitlab报错:sudo: a password is required
  • 【模拟CMOS集成电路设计】带隙基准(Bandgap)设计与仿真(基于运放的电流模BGR)
  • autoreconf --install的作用
  • 复试难度解析,西电先进材料与纳米科技学院学院考研录取情况
  • c++ 中的float和double 的区别 开发过程中使用哪个更好
  • 《阿里云Data+AI:开启数据智能新时代》电子书上线啦!
  • 每日一题力扣2697.字典序最小回文串c++