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

【Sql递归查询】Mysql、Oracle、SQL Server、PostgreSQL 实现递归查询的区别与案例(详解)

在这里插入图片描述

文章目录

    • Mysql 5.7 递归查询
    • Mysql 8 实现递归查询
    • Oracle递归示例
    • SQL Server 递归查询示例
    • PostgreSQL 递归查询示例

更多相关内容可查看

Mysql 5.7 递归查询

MySQL 5.7 本身不直接支持标准 SQL 中的递归查询语法(如 WITH RECURSIVE 这种常见的递归查询方式),但可以通过使用存储过程、临时表或自连接等方式来实现递归查询的效果。

  1. 使用自连接实现递归查询

通过自连接的方式模拟递归查询,适合处理简单的递归结构。假设我们有一个表示部门层级关系的表 departments,结构如下:

CREATE TABLE departments (
    id INT PRIMARY KEY,
    name VARCHAR(50),
    parent_id INT
);

向表中插入一些示例数据:

INSERT INTO departments (id, name, parent_id) VALUES
(1, '总公司', NULL),
(2, '研发部', 1),
(3, '市场部', 1),
(4, '研发一组', 2),
(5, '研发二组', 2);

使用自连接查询所有部门及其子部门:

在这里插入图片描述

SELECT
    t1.id AS root_id,
    t1.name AS root_name,
    t2.id AS child_id,
    t2.name AS child_name
FROM
    departments t1
JOIN
    departments t2
ON
    t1.id = t2.parent_id
UNION
SELECT
    id AS root_id,
    name AS root_name,
    id AS child_id,
    name AS child_name
FROM
    departments
WHERE
    parent_id IS NULL;

在这个查询中,通过 JOIN 语句将父部门和子部门关联起来,然后使用 UNION 操作符将顶级部门(parent_idNULL)也包含在结果中。

  1. 使用存储过程实现递归查询
DELIMITER //

-- 创建一个名为 recursive_departments_func 的函数,该函数接收两个整数参数 p_parent_id 和 p_level,并返回一个整数
CREATE FUNCTION recursive_departments_func(p_parent_id INT, p_level INT) RETURNS INT
DETERMINISTIC
BEGIN
    -- 声明一个整数变量 done,用于标记游标是否已经完成遍历,初始值为 FALSE
    DECLARE done INT DEFAULT FALSE;
    -- 声明一个整数变量 v_id,用于存储从游标中获取的部门 id
    DECLARE v_id INT;
    -- 声明一个字符串变量 v_name,用于存储从游标中获取的部门名称
    DECLARE v_name VARCHAR(50);
    -- 声明一个游标 cur,用于查询 departments 表中 parent_id 等于 p_parent_id 的记录
    DECLARE cur CURSOR FOR
        SELECT id, name FROM departments WHERE parent_id = p_parent_id;
    -- 声明一个继续处理程序,当游标没有更多数据时,将 done 置为 TRUE
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;

    -- 创建一个临时表 temp_departments,用于存储递归调用的结果
    -- 仅在该表不存在时创建,包含三个列:id、name 和 level
    CREATE TEMPORARY TABLE IF NOT EXISTS temp_departments (
        id INT,
        name VARCHAR(50),
        level INT
    );


    -- 打开游标 cur,以便开始读取数据
    OPEN cur;

    -- 定义一个名为 read_loop 的循环标签
    read_loop: LOOP
        -- 从游标 cur 中获取数据并存储到 v_id 和 v_name 中
        FETCH cur INTO v_id, v_name;
        -- 检查 done 变量是否为 TRUE,如果是则离开循环
        IF done THEN
            LEAVE read_loop;
        END IF;

        -- 将当前部门的信息插入到临时表 temp_departments 中
        INSERT INTO temp_departments (id, name, level) VALUES (v_id, v_name, p_level);

        -- 递归调用 recursive_departments_func 函数,将当前部门的 id 作为新的父部门 id,层级加 1
        SET @result = recursive_departments_func(v_id, p_level + 1);
    END LOOP;

    -- 关闭游标 cur
    CLOSE cur;

    -- 函数最终返回 1
    RETURN 1;
END //

DELIMITER ;

Mysql 8 实现递归查询

在 MySQL 8 中,可以使用 WITH RECURSIVE 子句来实现递归查询。

  1. 创建示例数据

假设我们有一个表示员工层级关系的表 employees,其中包含员工编号、姓名、上级员工编号:

-- 创建表
CREATE TABLE employees (
    employee_id INT PRIMARY KEY,
    name VARCHAR(50),
    manager_id INT,
    FOREIGN KEY (manager_id) REFERENCES employees(employee_id)
);

-- 插入数据
INSERT INTO employees (employee_id, name, manager_id) VALUES
(1, 'CEO', NULL),
(2, 'CTO', 1),
(3, 'CFO', 1),
(4, 'Lead Developer', 2),
(5, 'Developer 1', 4),
(6, 'Developer 2', 4),
(7, 'Accountant', 3);
  1. 递归查询所有员工及其下属

使用 WITH RECURSIVE 子句进行递归查询,查找某个员工及其所有下属。以下是查询 CEO 及其所有下属的示例:

WITH RECURSIVE employee_hierarchy AS (
    -- 初始查询,找到CEO
    SELECT employee_id, name, manager_id
    FROM employees
    WHERE name = 'CEO'
    UNION ALL
    -- 递归部分,找到下属员工
    SELECT e.employee_id, e.name, e.manager_id
    FROM employees e
    INNER JOIN employee_hierarchy eh ON e.manager_id = eh.employee_id
)
SELECT * FROM employee_hierarchy;
  • 2.1. CTE(公共表表达式)定义
    • WITH RECURSIVE employee_hierarchy AS (...) 定义了一个名为 employee_hierarchy 的递归 CTE。
    • 初始查询部分:
      SELECT employee_id, name, manager_id
      FROM employees
      WHERE name = 'CEO'
      
      这部分找到 CEO 的记录,作为递归的起点。
    • UNION ALL 用于将初始查询结果和递归查询结果合并。
    • 递归部分:
      SELECT e.employee_id, e.name, e.manager_id
      FROM employees e
      INNER JOIN employee_hierarchy eh ON e.manager_id = eh.employee_id
      
      这部分通过连接 employees 表和递归生成的 employee_hierarchy 表,找到每个员工的下属。
  1. 反向递归查询(查找某个员工的所有上级)

查找某个员工(例如 Developer 1)的所有上级:

WITH RECURSIVE manager_hierarchy AS (
    -- 初始查询,找到Developer 1
    SELECT employee_id, name, manager_id
    FROM employees
    WHERE name = 'Developer 1'
    UNION ALL
    -- 递归部分,找到上级员工
    SELECT e.employee_id, e.name, e.manager_id
    FROM employees e
    INNER JOIN manager_hierarchy mh ON e.employee_id = mh.manager_id
)
SELECT * FROM manager_hierarchy;

这个查询同样使用 WITH RECURSIVE,但递归方向是从指定员工向上查找其所有上级。

  1. 组织递归查询示例
-- 假设我们有一个 organizations 表存储组织信息
CREATE TABLE organizations (
    id INT PRIMARY KEY AUTO_INCREMENT,
    parent_id INT,
    name VARCHAR(255),
    level INT
);

-- 假设我们有一个 employees 表存储员工信息
CREATE TABLE employees (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(255),
    organization_id INT
);

-- 插入一些示例数据到 organizations 表
INSERT INTO organizations (parent_id, name, level) VALUES
    (NULL, '总公司', 1),
    (1, '分公司 A', 2),
    (1, '分公司 B', 2),
    (2, '部门 A1', 3),
    (2, '部门 A2', 3),
    (3, '部门 B1', 3),
    (3, '部门 B2', 3),
    (4, '小组 A1-1', 4),
    (4, '小组 A1-2', 4);

-- 插入一些示例数据到 employees 表
INSERT INTO employees (name, organization_id) VALUES
    ('员工 1', 1),
    ('员工 2', 2),
    ('员工 3', 2),
    ('员工 4', 3),
    ('员工 5', 4),
    ('员工 6', 4),
    ('员工 7', 4),
    ('员工 8', 5),
    ('员工 9', 6),
    ('员工 10', 7),
    ('员工 11', 8);

-- 使用 WITH RECURSIVE 进行递归查询
WITH RECURSIVE organization_hierarchy AS (
    -- 非递归部分:选择根组织作为起始点
    SELECT id, parent_id, name, level, 0 AS depth
    FROM organizations
    WHERE id = 1
    UNION ALL
    -- 递归部分:选择子组织,深度加 1
    SELECT o.id, o.parent_id, o.name, o.level, oh.depth + 1
    FROM organizations o
    JOIN organization_hierarchy oh ON o.parent_id = oh.id
)
-- 从递归结果中选择信息并统计员工数量
SELECT oh.id, oh.parent_id, oh.name, oh.level, oh.depth, COUNT(e.id) AS employee_count
FROM organization_hierarchy oh
LEFT JOIN employees e ON oh.id = e.organization_id
GROUP BY oh.id, oh.parent_id, oh.name, oh.level, oh.depth
ORDER BY oh.depth, oh.id;

Oracle递归示例

  • 支持版本:Oracle 9i 开始引入递归查询的功能,通过 CONNECT BY 子句实现。从 Oracle 11g 开始支持使用 WITH RECURSIVE 语法(CTE 递归查询)。
  • 示例1:假设有一个表示部门层级关系的表 departments,结构为 (department_id, department_name, parent_department_id)
-- 使用 CONNECT BY 子句
SELECT department_id, department_name, parent_department_id
FROM departments
START WITH parent_department_id IS NULL
CONNECT BY PRIOR department_id = parent_department_id;

-- 使用 WITH RECURSIVE 语法
WITH RECURSIVE department_hierarchy AS (
    SELECT department_id, department_name, parent_department_id
    FROM departments
    WHERE parent_department_id IS NULL
    UNION ALL
    SELECT d.department_id, d.department_name, d.parent_department_id
    FROM departments d
    INNER JOIN department_hierarchy dh ON d.parent_department_id = dh.department_id
)
SELECT * FROM department_hierarchy;
  • 示例2:使用 CONNECT BY 和 START WITH 子句进行递归查询,以查询 id 为 1 的组织(总公司)及其所有子组织。
CREATE TABLE organizations (
    id        NUMBER PRIMARY KEY,
    parent_id NUMBER,
    name      VARCHAR2(100)
);

INSERT INTO organizations (id, parent_id, name) VALUES (1, NULL, '总公司');
INSERT INTO organizations (id, parent_id, name) VALUES (2, 1, '分公司 A');
INSERT INTO organizations (id, parent_id, name) VALUES (3, 1, '分公司 B');
INSERT INTO organizations (id, parent_id, name) VALUES (4, 2, '部门 A1');
INSERT INTO organizations (id, parent_id, name) VALUES (5, 2, '部门 A2');
INSERT INTO organizations (id, parent_id, name) VALUES (6, 3, '部门 B1');
INSERT INTO organizations (id, parent_id, name) VALUES (7, 3, '部门 B2');
INSERT INTO organizations (id, parent_id, name) VALUES (8, 4, '小组 A1-1');
INSERT INTO organizations (id, parent_id, name) VALUES (9, 4, '小组 A1-2');

SELECT o.id, o.parent_id, o.name, LEVEL
FROM organizations o
START WITH o.id = 1
CONNECT BY PRIOR o.id = o.parent_id;
  • 示例3:使用递归查询和 JOIN 操作计算每个组织及其子组织的员工总数。
CREATE TABLE employees (
    id           NUMBER PRIMARY KEY,
    name         VARCHAR2(100),
    organization_id NUMBER
);

INSERT INTO employees (id, name, organization_id) VALUES (1, '员工 1', 2);
INSERT INTO employees (id, name, organization_id) VALUES (2, '员工 2', 2);
INSERT INTO employees (id, name, organization_id) VALUES (3, '员工 3', 3);
INSERT INTO employees (id, name, organization_id) VALUES (4, '员工 4', 4);
INSERT INTO employees (id, name, organization_id) VALUES (5, '员工 5', 4);
INSERT INTO employees (id, name, organization_id) VALUES (6, '员工 6', 5);
INSERT INTO employees (id, name, organization_id) VALUES (7, '员工 7', 6);
INSERT INTO employees (id, name, organization_id) VALUES (8, '员工 8', 7);
INSERT INTO employees (id, name, organization_id) VALUES (9, '员工 9', 8);


WITH org_hierarchy AS (
    SELECT o.id, o.parent_id, o.name, LEVEL AS org_level
    FROM organizations o
    START WITH o.id = 1
    CONNECT BY PRIOR o.id = o.parent_id
)
SELECT oh.id, oh.parent_id, oh.name, oh.org_level, COUNT(e.id) AS employee_count
FROM org_hierarchy oh
LEFT JOIN employees e ON oh.id = e.organization_id
GROUP BY oh.id, oh.parent_id, oh.name, oh.org_level
ORDER BY oh.org_level, oh.id;
  • 示例4:假设 organizations 表有一个 budget 列表示组织的预算,并且预算可以从父组织分配给子组织。我们可以使用递归查询计算每个组织及其子组织的最终预算
ALTER TABLE organizations ADD (budget NUMBER);

UPDATE organizations SET budget = 100000 WHERE id = 1;
UPDATE organizations SET budget = 0 WHERE id IN (2, 3);
UPDATE organizations SET budget = 0 WHERE id IN (4, 5, 6, 7);
UPDATE organizations SET budget = 0 WHERE id IN (8, 9);

WITH budget_allocation AS (
    SELECT o.id, o.parent_id, o.name, o.budget AS original_budget,
           o.budget AS allocated_budget, LEVEL AS org_level
    FROM organizations o
    START WITH o.id = 1
    CONNECT BY PRIOR o.id = o.parent_id
)
SELECT ba.id, ba.parent_id, ba.name, ba.original_budget,
       CASE
           WHEN ba.original_budget = 0 THEN
               NVL((LAG(ba.allocated_budget) OVER (ORDER BY ba.org_level DESC) / COUNT(*) OVER (PARTITION BY ba.parent_id)), 0)
           ELSE ba.allocated_budget
       END AS final_budget,
       ba.org_level
FROM budget_allocation ba;

SQL Server 递归查询示例

  • 支持版本:SQL Server 2005 开始支持 WITH 子句,包括递归 CTE(Common Table Expressions)。
  • 示例:假设有一个员工表 Employees,结构为 (EmployeeID, Name, ManagerID)
WITH RECURSIVE EmployeeHierarchy AS (
    SELECT EmployeeID, Name, ManagerID
    FROM Employees
    WHERE ManagerID IS NULL
    UNION ALL
    SELECT e.EmployeeID, e.Name, e.ManagerID
    FROM Employees e
    INNER JOIN EmployeeHierarchy eh ON e.ManagerID = eh.EmployeeID
)
SELECT * FROM EmployeeHierarchy;

PostgreSQL 递归查询示例

  • 支持版本:PostgreSQL 8.4 开始支持递归 CTE(WITH RECURSIVE)。
  • 示例:假设有一个表示菜单层级关系的表 menus,结构为 (menu_id, menu_name, parent_menu_id)
WITH RECURSIVE menu_hierarchy AS (
    SELECT menu_id, menu_name, parent_menu_id
    FROM menus
    WHERE parent_menu_id IS NULL
    UNION ALL
    SELECT m.menu_id, m.menu_name, m.parent_menu_id
    FROM menus m
    INNER JOIN menu_hierarchy mh ON m.parent_menu_id = mh.menu_id
)
SELECT * FROM menu_hierarchy;

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

相关文章:

  • 数据结构--二叉树
  • 为深度学习创建PyTorch张量 - 最佳选项
  • 蓝桥与力扣刷题(709 转换成小写字母)
  • 一文说清楚Linux gdb
  • 提供的 IP 地址 10.0.0.5 和子网掩码位 /26 来计算相关的网络信息
  • 【声音场景分类--论文阅读】
  • Mac操作系统zip压缩加密
  • 【Vim Masterclass 笔记10】S06L23:Vim 核心操作训练之 —— 文本的搜索、查找与替换操作(第二部分)
  • 【21天学习AI底层概念】day13 (kaggle新手入门教程)Exercise: Underfitting and Overfitting
  • 【搭建JavaEE】(2)Tomcat安装配置和第一个JavaEE程序
  • 详解如何自定义 Android Dex VMP 保护壳
  • 深度学习核函数
  • HTML学习笔记(3)
  • YOLOv10-1.1部分代码阅读笔记-dataset.py
  • Web网页实现上拉加载分页
  • 蓝桥杯刷题第二天——背包问题
  • [信息安全] 1. 企业网络安全基础知识
  • Springboot和Es整合
  • 每天五分钟深度学习:神经网络中的激活函数
  • final修饰的用法
  • nVisual智能运维管理:革新机房布线管理,驱动企业数字化转型
  • 《C++11》并发库:简介与应用
  • SQLite Indexed By
  • 3、C#基于.net framework的应用开发实战编程 - 实现(三、一) - 编程手把手系列文章...
  • TensorFlow DAY3: 高阶 API(Keras,Estimator)(完)
  • 【Golang 面试题】每日 3 题(三十二)