PHP MySQL层次结构数据优化

时间:2014-10-31 16:37:50

标签: php mysql optimization hierarchical-data

我有一个表员工,其中包含以下列: employee_id 名称 employee_manager_id employee_manager_id employee_id 的引用。这是一个分层数据。

我使用PHP输出,但只使用一个mySQL查询无法实现。截至目前,我需要使用递归函数在PHP中处理数据,这样我就可以实现这种输出。

Sample Array Output 
0 => (
                employee_id => 2,
                name => Jerald,
                employee_manager_id => 1,
                depth => 1
            ),
        1 => (
                employee_id => 3,
                name => Mark,
                employee_manager_id => 2,
                depth => 2
            ), 
        2 => (
                employee_id => 6,
                name => Cyrus,
                employee_manager_id => 3,
                depth => 3
            ), 
        3 => (
                employee_id => 4,
                name => Gerby,
                employee_manager_id => 2,
                depth => 2
            )

截至目前,这是我在PHP中的递归函数,以实现上面的输出。

function get_employees_by_hierarchy( $_employee_id = 0, $_depth = 0, $_org_array = array() ) {
    if ( $this->org_depth < $_depth ) {
        $this->org_depth = $_depth;
    }

    $_depth++;
    $_query = "SELECT * FROM employees WHERE ";

    if ( !$_employee_id ) {
        $_query .= "employee_manager_id IS NULL OR employee_manager_id = 0";
    }
    else { 
        $_query .= "employee_manager_id = " . $this->dbh->quoteSmart( $_employee_id );
    }
    $_result = $this->query( $_query );

    while ( $_row = $_result->fetchRow() ) {
        $_row['depth'] = $_depth;
        array_push( $_org_array, $_row );
        $_org_array = $this->get_employees_by_hierarchy(
            $_row['employee_id'],
            $_depth,
            $_org_array
        );
    }
    return $_org_array;
}

我的问题是,无论如何,我只能使用一个mysql查询来实现数组输出吗? 如果在mysql查询中不可能,那么在我当前的代码中是否还有优化?

非常感谢任何帮助。

由于

4 个答案:

答案 0 :(得分:0)

您可以尝试嵌套的a.k.a. celko树,但插入和删除非常昂贵。还有闭包和路径枚举(物化路径),但我不是专家。 MySql不支持递归查询。

答案 1 :(得分:0)

我不认为您可以在不对结果进行任何处理的情况下深入了解当前模型,但您不需要进行多次查询。

假设$employees是由employee_id编制索引的员工列表,您可以执行以下操作:

function set_employee_depth(&$employees, $id) {
    if (!isset($employees[$id]['depth'])) {
        $employee_manager_id = (int) $employees[$id]['employee_manager_id'];
        if (!$employee_manager_id) {
            $employees[$id]['depth'] = 0;
        } elseif ($employee_manager_id !== $id) {
            $employees[$id]['depth'] = 1 + set_employee_depth($employees, $employee_manager_id);
        } else {
            throw new \Exception('Employee cannot be its own manager!');
        }
    }
    return $employees[$id]['depth'];
}

foreach ($employees as $id => $employee) {
    set_employee_depth($employees, $id);
}

答案 2 :(得分:0)

因此,您的表格由3列组成(employee_idnameemployee_manager_id)。 employee_manager_idemployee_id的自引用。您想构建一个包含所有记录的数组,添加一个名为depth的额外字段,表示所述员工与“大老板”的距离,只有一个查询数据库。那是对的吗?我还假设数据库结构无法更改。

如果这些假设是正确的,那么这是一个基本的 HIERARCHICAL / TREE 数据结构,因此,您有几种方法可以解决这个问题。


第一个脚本

第一个脚本按顺序运行结果数组,首先找到主节点/ trunk (大老板),然后添加它的子节点,然后是孙子节点,依此类推。每次节点“排序”时,它将从循环中移除,直到没有剩余节点。它假设:

  • 没有孤儿记录(员工管理员身份无效)
  • 没有循环引用,简单( A是B的经理,B是A 的经理)或复杂(* B的经理,C的B经理和A的C经理)
  • 每条路径(从主节点到最后一个节点)可以拥有无​​限数量的节点
  • $results是通过运行简单查询SELECT * FROM employees ORDER BY employee_manager_id
  • 生成的

代码:

$finalArray = array();
$limit = count($results);

while (count($results) > 0) {
    $results[0]['cnt'] = isset($results[0]['cnt']) ? $results[0]['cnt']++ : 0; // set num of times each element was already visited
    if ($results[0]['cnt'] === $limit) { //prevent an infinite cycle
        break;
    }
    $manId = $results[0]['manager_id'];
    if ($manId === null) {
        $results[0]['depth'] = 0;
    } else if ( ($key = searchForId($manId, $finalArray)) !== null ) {
        $results[0]['depth'] = $finalArray[$key]['depth'] + 1; //use the depth of parent to calculate its own
    } else {
        $results[] = $results[0]; //parent was not visited yet so we add it to the end of array
        array_shift($results);
        continue;
    }
    unset($results[0]['cnt']);
    $finalArray[] = array_shift($results);   
}

function searchForId($id, $array) {
   foreach ($array as $key => $val) {
       if ($val['id'] === $id) {
           return $key;
       }
   }
   return null;
}

这个脚本非常简单。它只对DB运行一个查询。在最好的情况下,它只会遍历数组一次。 在更糟糕的情况下,它会访问每个元素count(array) - 1,这对于大数组而言可能会很慢。但是,由于结果是预先排序的,最好的情况场景可能会更常见。


第二个脚本

第二个脚本构建一个实际的元素树。它有点复杂,但取得了类似的结果。此外,深度是动态计算的。

class Employee {
    public $id;
    public $name;
    public $manager;

    public function __construct($id, $name, Employee $manager = null) {
        $this->id = $id;
        $this->name = $name;
        $this->manager = $manager;
    }

    public function setManager(Employee $manager) {
        $this->manager = $manager;
    }

    public function getDepth() {
        if ($this->manager === null) {
            return 0;
        } else {
            return $this->manager->getDepth() + 1;
        }
    }
}

$finalArray = array();
$paths = array();


foreach ($results as $r) {
    $finalArray[(int) $r['id']] = new Employee((int)$r['id'], $r['name']);
    if ($r['manager_id'] !== null) {
        $paths[(int) $r['id']] = (int) $r['manager_id'];
    }
}

foreach ($paths as $k => $v) {
    if (isset($finalArray[$k]) && isset($finalArray[$v])) {
        $finalArray[$k]->setManager($finalArray[$v]);
    }
}

答案 3 :(得分:0)

Here is a link for answer

这是使用php和mysql为层次结构管理制作树结构的完整代码。