查找儿童部门的员工 - PHP

Posted

技术标签:

【中文标题】查找儿童部门的员工 - PHP【英文标题】:Find Staff in Child Departments - PHP 【发布时间】:2017-09-08 01:19:43 【问题描述】:

我正在使用 Codeigniter + mysql + Active Record 构建一个带有组织结构图的项目。

有部门列为组织树,人员信息的人员,人员角色和人员_部门,我存储匹配: 部门 - 员工 - 角色

你可以看到下面的结构:

部门 (parent_id用于构建树)

员工 (原始员工信息)

员工角色 (权重最低,层级最高)

员工部门 (在哪个部门 - 谁 - 什么角色)

在以后的阶段,员工可能会属于 2 个或更多具有不同角色的部门。这就是为什么我为多对多使用单独的表 Staff_departments。在这种情况下,让我们保持简单,假设 1 个员工属于 1 个部门。

我想要做什么:

    一个部门的经理(role weight=0 || role_id=1),可以查看在其部门工作的员工(员工和主管)所有属于其部门子级的部门的员工(员工和主管)。树的深度未知。 主管可以查看仅在其部门工作的员工(仅限员工)。 员工只能查看自己。

对于主管和员工来说,这个过程很简单,所以我觉得我可以接受。对于经理来说,我可能必须做一些递归的事情,但每次我开始编写一些代码行时我都在挣扎。

我的想法是在我的控制器中有一个函数 find_related_staff($staff_id) ,我将传递登录的员工的 ID,它会返回一个包含 ID 的数组他的相关工作人员。我唯一得到的是登录的员工的 ID。

如果经理返回与其部门相关的经理、主管和员工的 ID,以及来自的经理、主管和员工的 ID 他部门的子部门。

如果主管仅返回其部门相关的主管和员工的 ID。

如果员工返回他的 ID

关于如何实现这一目标的任何想法?

【问题讨论】:

如果你把role_id和dept_id放在staff表中,你的查询会变得更容易 我不能这样做,因为员工可以是一个部门的经理和另一个部门的主管。这种关系是多对多的。 那么同一员工在一个部门担任经理(可以看到所有主管和员工)但在另一个部门担任主管(看不到该部门的主管)?对 总的来说是的,没错,但在那个阶段,让我们保持简单,承认员工只能在一个部门中担任经理或主管。多对多关系适用于员工,而不适用于经理或主管。 结构化涉及所有可能性。无论如何,这个逻辑需要 php 检查以及您的查询 【参考方案1】:

我认为您的主要问题是如何向下遍历,以便getRecursiveDepts() 解决。我还没有完成代码,但你可以尝试这样的事情

文件db.php

class DB 
    private $servername = "127.0.0.1";
    private $username = "root";
    private $password = "root";
    private $dbname = "test";
    private $port = '3306';

    public function getRecursiveDepts($deptIds) 
        if (!is_array($deptIds)) 
            $deptIds = array($deptIds);
        
        $sql = "SELECT id FROM Departments WHERE parentId IN (";
        $sql .= implode(', ', $deptIds);
        $sql .= ")";

        $conn = new mysqli($this->servername, $this->username, $this->password, $this->dbname, $this->port);
        if ($conn->connect_error) 
            die("Connection failed: " . $conn->connect_error);
        
        $result = $conn->query($sql);
        if ($result->num_rows > 0) 
            $newDept = array();
            while($row = $result->fetch_assoc()) 
                array_push($newDept, $row['id']);
            
            $conn->close();
            $moreDepts = $this->getRecursiveDepts($newDept);
            if (is_null($moreDepts)) 
                $finalIds = array_unique(array_merge($deptIds, $newDept));
             else 
                $finalIds = array_unique(array_merge($deptIds, $newDept, $moreDepts));
            
            return $finalIds;
         else 
            $conn->close();
            return null;
        
    

    public function getRoles($empId) 
        $sql = "SELECT role_id, department_id FROM staff_departmen_role WHERE staff_id = '$empId' GROUP BY role_id, department_id";
        $conn = new mysqli($this->servername, $this->username, $this->password, $this->dbname, $this->port);
        if ($conn->connect_error) 
            die("Connection failed: " . $conn->connect_error);
        
        $result = $conn->query($sql);
        if ($result->num_rows > 0) 
            $emp = array();
            while($row = $result->fetch_assoc()) 
                if (!array_key_exists($row['role_id'], $emp)) 
                    $emp[$row['role_id']] = array();
                
                array_push($emp[$row['role_id']], $row['department_id']);
            
        
        $conn->close();
        return $emp;
    

    public function getEmpDetails($empId) 
        $sql = "SELECT role_id, department_id FROM staff_departmen_role WHERE staff_id = '$empId' GROUP BY role_id, department_id";
        $conn = new mysqli($this->servername, $this->username, $this->password, $this->dbname, $this->port);
        if ($conn->connect_error) 
            die("Connection failed: " . $conn->connect_error);
        
        $result = $conn->query($sql);
        if ($result->num_rows > 0) 
            $emp = array();
            while($row = $result->fetch_assoc()) 
                if (!array_key_exists($row['role_id'], $emp)) 
                    $emp[$row['role_id']] = array();
                
                array_push($emp[$row['role_id']], $row['department_id']);
            
        
        $conn->close();
        return $emp;
    

文件index.php

<?php
include_once 'db.php';

$objDB = new DB();

$empId = 2;

$emps = $objDB->getRoles($empId);
foreach ($emps as $roleId => $deptIds) 
    switch ($roleId) 
        case 1:
            $allDeptIds = $objDB->getRecursiveDepts($deptIds);
            break;

        case 2://Supervisor GetEmpIds of current dept role >= 2

            break;

        case 3://Employee GetEmpIds of current dept role >= 2
            $emp = $objDB->getEmpDetails($empId);
            break;

        default:
            # code...
            break;
    

$data = $objDB->getRecursiveDepts($empId);
print_r($data);
?>

【讨论】:

【参考方案2】:

我认为关系数据库中分层数据最强大的架构是transitive-closure-table。

给定departments 表的示例数据:

department_id | parent_id | department_name
--------------|-----------|----------------
            1 |         0 | TEST A
            2 |         1 | TEST B
            3 |         2 | TEST C

你的闭包表(我们就叫它departments_tree)会是这样的:

super_id | sub_id
---------|-------
       1 |      1
       1 |      2
       1 |      3
       2 |      2
       2 |      3
       3 |      3

读作:super_id = 上级部门_id; sub_id = 下属部门_id。

假设登录用户是department_id = 2的部门经理,获取所有“受监管”员工的查询是:

SELECT DISTINCT s.*
FROM departments_tree t
JOIN stuff_departments sd ON sd.department_id = t.sub_id
JOIN staff s ON s.id = sd.staff_id
WHERE t.super_id = 2

您可以使用触发器来填充和更新闭包表。

插入触发器:

DELIMITER //
CREATE TRIGGER `departments_after_insert` AFTER INSERT ON `departments` FOR EACH ROW BEGIN
    INSERT INTO departments_tree (super_id, sub_id)
        SELECT new.department_id, new.department_id
        UNION ALL
        SELECT super_id, new.department_id
        FROM departments_tree
        WHERE sub_id = new.parent_id;
END//
DELIMITER ;

删除触发器:

DELIMITER //
CREATE TRIGGER `departments_before_delete` BEFORE DELETE ON `departments` FOR EACH ROW BEGIN
    DELETE FROM departments_tree
    WHERE sub_id = old.department_id;
END//
DELIMITER ;

更新触发器:

DELIMITER //
CREATE TRIGGER `departments_before_update` BEFORE UPDATE ON `departments` FOR EACH ROW BEGIN
    DELETE t
    FROM       departments_tree p 
    CROSS JOIN departments_tree c
    INNER JOIN departments_tree t
      ON  t.super_id = p.super_id
      AND t.sub_id = c.sub_id
    WHERE p.sub_id   = old.parent_id
      AND c.super_id = new.department_id;

    INSERT INTO departments_tree (super_id, sub_id)
        SELECT p.super_id, c.sub_id
        FROM       departments_tree p
        CROSS JOIN departments_tree c
        WHERE p.sub_id   = new.parent_id
          AND c.super_id = new.department_id;
END//

注意

如果您使用带有ON DELETE CASCADE 的外键,则不需要删除触发器:

CREATE TABLE `departments_tree` (
    `super_id` INT(10) UNSIGNED NOT NULL,
    `sub_id` INT(10) UNSIGNED NOT NULL,
    PRIMARY KEY (`super_id`, `sub_id`),
    INDEX `sub_id_super_id` (`sub_id`, `super_id`),
    FOREIGN KEY (`super_id`) REFERENCES `departments` (`department_id`) ON DELETE CASCADE,
    FOREIGN KEY (`sub_id`)   REFERENCES `departments` (`department_id`) ON DELETE CASCADE
);

注2

在传递闭包表的许多实现中,您会发现depthlevel 列。 但是对于给定的要求,您不需要它。我相信你永远不会真正需要它, 只要您不尝试在 SQL 中格式化树输出。

【讨论】:

【参考方案3】:

好的,我认为,为了让事情更容易理解,我们需要将你的问题分解成小块(我只关注你说你真的需要帮助的部分:经理的递归) .

首先,我们获取与已验证用户关联的当前部门。正如您所说,您只有当前签名的工作人员的 ID,因此我们将从该 ID 开始。假设用户 id 被分配给变量 $user_id。

$user_department = $this->db->get_where('staff_departments', ['staff_id' => $user_id])->row();

现在我们有了部门,我们检查一下用户在该部门中的角色。我们将该信息添加到 $user_department 对象中:

$user_department->role = $this->db->get_where('staff_roles', ['role_id' => $user_department->role_id])->row();

让我们检查一下用户角色的权重,好吗?如果为 0,我们知道它是该部门的经理,因此我们将递归查找嵌套的部门及其员工信息。根据您的逻辑,我们可以在这里检查用户是否也是主管,并在必要时升级。像这样:

if ($user_department->role->role_weight <= 1) 
    // the user is a supervisor OR a manager, but both those can see, at least, the current department's staff information
    $user_department->staff = $this->db->get_where('staff_departments', ['department_id' => $user_department->department_id]);

    // now is the user a manager? If so, let's find nested departments
    if ($user_department->role->role_weight === 0) 
        $user_department->childs = $this->getChildDepartmentsAndStaffOf($user_department->department_id);
    

您可能注意到,有一个函数会被递归调用。一定是这样的:

public function getChildDepartmentsAndStaffOf($department_id)

    $child_departments = $this->db->get_where('departments', ['parent_id' => $department_id]);

    if (! $child_departments) 
        return null;
    

    foreach ($child_departments as &$department) 
        $department->staff = $this->db->get_where('staff_departments', ['department_id' => $department->department_id]);

        $department->childs = $this->getChildDepartmentsAndStaffOf($department->department_id);
    

    return $child_departments;

现在,你已经有了你想要的结构。我知道这可能会被重构,但我认为这足以让您得到答案并为您指明正确的道路。

希望我能帮上一点忙。

【讨论】:

似乎合法!还没试过,但我认为这是正确的方法。【参考方案4】:

是的,要完成它,您必须使用递归过程。 (我使用的是 MySQL 5.6.19)

我在存储过程之前创建了一些测试数据:

    根据您的问题要求提供样本数据:

    create table departments
    (
        id int not null primary key auto_increment,
        parent_id int,
        department_name varchar(100)
    );
    
    insert into departments (id,parent_id,department_name)
    values
    (1,0,'Test A'),
    (2,1,'Test B'),
    (3,2,'Test C');
    
    
    create table staff
    (
        id int not null primary key auto_increment,
        ip_address varchar(100),
        username varchar(100)
    );
    
    insert into staff values
    (1,'127.0.0.1','ats'),
    (2,'127.0.0.1','admin'),
    (3,'127.0.0.1','george'),
    (4,'127.0.0.1','jhon')
    ;
    
    create table staff_roles
    (
        role_id int not null primary key auto_increment,
        role_name varchar(100),
        role_height int
    );
    
    insert into staff_roles values
    (1,'Manager',0),
    (2,'Supervisor',1),
    (3,'Employee',2)
    ;
    
    create table staff_departments
    (
        staff_department_id int not null primary key auto_increment,
        department_id int,
        staff_id int,
        role_id int
    );
    
    insert into staff_departments values
    (1,1,2,1),
    (2,2,1,2),
    (3,3,3,3),
    (4,3,4,3);
    

    是时候创建存储过程了:

    find_related_staff是接收staff_id参数的过程,根据该值将在staff_departments表中找到role_id

    变量@result 会将最终结果累积为逗号分隔值。

    find_recursive是在子部门中搜索并将staff_id放入@result变量的过程;

    程序代码:

    delimiter $$
    drop procedure if exists find_related_staff$$
    create procedure  find_related_staff(p_id int)
    begin
        declare p_role_id int;
        declare p_department_id int;
        declare p_return varchar(255) default '';
        declare p_role varchar(100);
    
        select d.role_id, d.department_id, r.role_name
            into p_role_id,p_department_id, p_role
            from staff_departments d
            inner join staff_roles r on d.role_id = r.role_id
            where d.staff_id = p_id
            limit 1;
    
        case p_role_id
        when 3 then -- employee (return the same id)
                set @result = p_id;
    
            when 2 then -- supervisor 
    
                select group_concat(s.staff_id)
            into @result
            from staff_departments s
            where 
                  s.role_id = 3
                  and s.department_id in 
                     ( select d.id 
                       from departments d
                       where d.parent_id = p_department_id )
                  and s.role_id <> p_id;
    
    
            when 1 then -- manager (complex recursive query)
    
                select coalesce(group_concat(s.staff_id),'')
                  into @result
                from staff_departments s
                where 
                  s.department_id =  p_department_id
                  and s.staff_id <>  p_id;
    
               -- here we go!
               call find_recursive(p_department_id);
        end case;
    
        select @result as result, p_role as role;
    end $$
    delimiter ;
    
    delimiter $$
    drop procedure if exists find_recursive$$
    create procedure  find_recursive(p_dept_id int)
    begin
        declare done int default false;
        declare p_department int default false;
    
        declare tmp_result varchar(255) default '';
        -- cursor for all depend departments
        declare c_departments cursor for
            select s.department_id
            from staff_departments s
            where 
                  s.department_id in 
              ( select d.id 
                    from departments d
                    where d.parent_id = p_dept_id );
    
        declare continue handler for not found set done = true;
    
    
        -- getting current departmens
        set tmp_result = 
            (select coalesce(group_concat(s.staff_id),'')
                from staff_departments s
                where 
                  s.department_id in 
                  ( select d.id 
                    from departments d
                    where d.parent_id = p_dept_id ));
    
    
        if length(tmp_result) > 0 then
    
            if length(@result) > 0 then
                set @result = concat(@result,',',tmp_result);
            else
                set @result = tmp_result;
            end if;
    
            open c_departments;
    
            read_loop: loop
                fetch c_departments into  p_department;
                if done then
                  leave read_loop;
                end if;
    
            call find_recursive(p_department);
    
            end loop;
            close c_departments;            
    
        end if;
    
    end $$
    delimiter ;
    

    测试:

    重要提示:递归的最大深度默认为0,我们必须更改该值:

    SET max_sp_recursion_depth=255; 
    

    现在我们在您的staff_departments 表上有如下配置:

    +---------------------+---------------+----------+---------+
    | staff_department_id | department_id | staff_id | role_id |
    +---------------------+---------------+----------+---------+
    |                   1 |             1 |        2 |       1 |
    |                   2 |             2 |        1 |       2 |
    |                   3 |             3 |        3 |       3 |
    |                   4 |             3 |        4 |       3 |
    +---------------------+---------------+----------+---------+
    

    运行每个案例:

    call find_related_staff(2);
    +--------+---------+
    | result | role    |
    +--------+---------+
    | 1,3,4  | Manager |
    +--------+---------+
    
    
    
    call find_related_staff(1);
    +--------+------------+
    | result | role       |
    +--------+------------+
    | 3,4    | Supervisor |
    +--------+------------+
    
    
    call find_related_staff(3);
    +--------+----------+
    | result | role     |
    +--------+----------+
    |      3 | Employee |
    +--------+----------+
    
    call find_related_staff(4);
    +--------+----------+
    | result | role     |
    +--------+----------+
    |      4 | Employee |
    +--------+----------+
    

    享受吧!

【讨论】:

干得好,但我可以问你点什么吗?为什么你用 MySQL 而不是 PHP? (我问过同样的问题)答案是:你也可以只使用mysql。

以上是关于查找儿童部门的员工 - PHP的主要内容,如果未能解决你的问题,请参考以下文章

4查找所有已经分配部门的员工

sql 查找所有已经分配部门的员工

牛客网SQL-第4题-请你查找所有已经分配部门的员工的last_name和first_name以及dept_no,未分配的部门的员工不显示

57使用含有关键字exists查找未分配具体部门的员工的所有信息

SQL-57 使用含有关键字exists查找未分配具体部门的员工的所有信息。

MySQL 题2