显示每个父节点的所有子节点

13
我有一张表 People。我想展示一个HTML表格,其中包含每个父级及其所有直接位于其下方的子级。
 _________
|People   |_____________________________________________
|-------------------------------------------------------|
| id     | parent | firstname     | lastname            |
|-------------------------------------------------------|
| 1        0        James           Donovan             |
| 2        0        Jeffrey         Williams            |
| 3        0        Emmit           Herring             |
| 4        2        Carol           Williams            |
| 5        2        Sarah           Williams            |
| 6        1        Nikolai         Donovan             | 
|_______________________________________________________|

预期输出:

 ________________________________________________
|Jeffrey Williams                                |
|------------------------------------------------|
|  - Carol Williams                              |
|  - Sarah Williams                              |
|________________________________________________|
|James Donovan                                   |
|------------------------------------------------|
|  - Nikolai Donovan                             |
|________________________________________________|
|Emmit Herring                                   |
|------------------------------------------------|
|________________________________________________|

如何构建一个包含正确结果集的关联数组以进行迭代?我对构建最终数组的正确SQL和正确PHP感到困惑。

具体来说,我不确定如何显示两个MySQL表之间的层次关系。据我所知,SQL结果集不是多维的。将SQL查询放入for循环中会对性能产生巨大的影响。那么应该怎么办呢?

我猜我正在寻找MySQL中的相邻列表实现。

如果我可以将所有内容拆分为两个表,那么这个问题应该很容易解决,但不幸的是,我必须坚持使用这种非标准的表结构。


为什么我需要使用jQuery显示像父元素的子元素这样简单的东西?这里难道没有PHP的解决方案吗? - Chris G.
这是一个两层的树吗?还是您允许有孙子等级的存在? - Niloct
在Oracle中,使用CONNECT BY子句非常容易实现,但是在MySQL中,邻接列表没有这样的机制。 - Chris G.
不,每个孩子都有一个父母。为了这个例子,他们都没有母亲。 - Chris G.
离题 - 你使用工具让这些表格在文本字段中看起来好看吗? - 472084
显示剩余5条评论
4个回答

14

有几种方法可以实现:

1. 显而易见的方法是首先获取所有父级的列表,然后在循环中为每个父级运行一个单独的子查询。你说这会“极大地影响性能”,但实际上不应该,假设您已经在parent列上创建了索引并且您的 MySQL 服务器没有位于地球的另一端。


2. 如果你真的想要在一个查询中完成这个任务,你可以在表格本身上使用LEFT JOIN

SELECT
  p.id AS parent_id,
  p.firstname AS parent_firstname,
  p.lastname  AS parent_lastname,
  c.id AS child_id,
  c.firstname AS child_firstname,
  c.lastname  AS child_lastname
FROM
  People AS p
  LEFT JOIN People AS c ON c.parent = p.id
WHERE p.parent = 0
ORDER BY p.id

重申一遍,你真的、真的需要在parent列上创建索引。 ORDER BY子句是为了确保每个父项的子项在一起排序;如果你想按字母顺序对名称进行排序,可以将其更改为类似于p.lastname,p.firstname,p.id,c.lastname,c.firstname,c.id的内容。然后在PHP中,你需要遍历结果并在父ID更改时打印新标头(记得处理child_*列为空的情况),像这样:

$res = mysql_query( $sql );
$last_parent_id = 0;
while ( $row = mysql_fetch_object( $res ) ) {
    if ( $row->parent_id != $last_parent_id ) {
        // print parent header
        $last_parent_id = $row->parent_id;
    }
    if ( $row->child_id ) {
        // print child row
    }
}

3. 第三种选择是使用简单的SELECT * FROM People查询获取所有行,并在 PHP 中构建树形结构:


$res = mysql_query( "SELECT * FROM People" );  // add WHERE clauses if needed
$names = array();
$parents = array();
$children = array();

while ( $row = mysql_fetch_object( $res ) ) {
    $names[ $row->id ] = array( $row->firstname, $row->lastname );
    if ( $row->parent == 0 ) {
        $parents[] = $row->id;
    } else {
        if ( !array_key_exists( $row->parent, $children ) )
            $children[ $row->parent ] = array();
        $children[ $row->parent ][] = $row->id;
    }
}

foreach ( $parents as $parent_id ) {
    // print parent header
    if ( array_key_exists( $parent_id, $children ) ) {
        foreach ( $children[ $parent_id ] as $child_id ) {
            // print child row
        }
    }
}

顺便提一下,如果你实际上不想在表格中显示所有的父级和子级,而只是想显示属于一个家庭的内容,那么你仍然应该尝试在SQL中进行过滤,以避免获取过多的记录。


2

根据传统的方法,我认为从SQL开始,加入表格(即使在这种情况下左侧表格和右侧表格相同),可能是一个很好的起点。

这主要是因为使用关系型数据库管理系统,您必须始终处理表格结构,通过这种方式加入表格可以确保数据一致性。

因此,可以从以下内容开始:

SELECT 
       a.id parent_id, a.firstname parent_name, a.lastname parent_lastname, 
       b.id child_id, b.firstname child_firstname, b.lastname child_lastname
FROM 
       People a LEFT OUTER JOIN People b ON a.id = b.parent
WHERE  
       a.parent = 0;

其次,您应该优先使用“fetch_all”策略(例如使用mysqli php扩展,但也可在PDO中使用),这将使您能够通过一次操作将整个结果集提取到双向关联数组中。
此时,您可以选择自己的路径。
全PHP:您可以使用PHP遍历数组,并直接构建呈现标记以按您需要组织的方式显示数据,将html字符串echo到浏览器。
AJAX:如果 - 例如 - 您的PHP脚本是通过AJAX调用的,您也可以遍历查询结果数组,但这次要解释它以构建JSON结构,以响应调用,就像这样:
{
    "1": {
        "id": 1,
        "firstname": "James",
        "lastname": "Donovan",
        "children": {
            "6": {
                "id": 6,
                "firstname": "Nikolai",
                "lastname": "Donovan"   
            }
        }
    },
    "2": {
        "id": 2,
        "firstname": "Jeffrey",
        "lastname": "Williams",
        "children": {
            "4": {
                "id": 4,
                "firstname": "Carol",
                "lastname": "Williams"  
            },
            "5": {
                "id": 5,
                "firstname": "Sarah",
                "lastname": "Williams"  
            }
        }
    },
    "3": {
        "id": 3,
        "firstname": "Emmit",
        "lastname": "Herring",
        "children": { }
    }
}

这种表示法更适合数据交换,因为您的客户端JavaScript可以无缝地识别它并遍历它以填充预先存在的空表格框架。当然,您可以直接使用PHP的json_encode()函数对结果数组进行编码,而不是将其重组成像这样的其他形式,但您会发现自己得到的东西不会比您已经拥有的坚实的记录集数组表示法更进一步。
最后,全mysql解决方案是准备一个存储过程,有目的地构建您正在寻找的数据结构,例如每个家庭1行,将父母的全名作为第一列,将子女的全名作为后续列(如果该人没有孩子,则为空字段,如Emmit Herring)。
您可以再次使用PHP的“fetch_all”函数获取结果集,遍历数组,然后完成操作。
因此,如果性能是问题,即使必须说服务器在计算负载和内存占用方面付出了代价,如果要处理大量数据,这种最后的方法也应保证为您提供最佳结果。

1
你可以在循环中使用另一个循环:
$res = mysql_query("SELECT PARENT");
while( $row = mysql_fetch_assoc($res) )
{

  // echo parent

  $res2 = mysql_query("SELECT CHILD WHERE PARENT SOMETHING");
  while( $row2 = mysql_fetch_assoc($res2) )
  {

    // echo child
  }
}

或者,稍后保留它,并存储一个标志。

$people = array();

$res = mysql_query("SELECT PARENT");
while( $row = mysql_fetch_assoc($res) )
{
  $people[] = array('is_parent' => true,
                    'info'      => $row);

  $res2 = mysql_query("SELECT CHILD WHERE PARENT SOMETHING");
  while( $row2 = mysql_fetch_assoc($res2) )
  {    
    $people[] = array('is_parent' => false,
                      'info'      => $row2);
  }
}

// later

foreach( $people as $person )
{
  if( $person['is_parent'] )
  {
    // echo parent
  }
  else
  {
    // echo child
  }
}

当然你可以这样做,但正如我在问题中提到的那样,随着表格的增长,这种方法会导致无法接受的性能问题。 - Chris G.

0
为什么不在JavaScript中创建一个多维数组?之后,只需通过循环遍历数组即可在DOM中获取结果。

1
因为网站用户不应该被要求启用Javascript。为什么不能显示一个父->子关系列表,而不需要使用Javascript操纵DOM?我正在使用PHP和MySQL。 - Chris G.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接