2

合計 9 レベルの深さの名前のツリーを持つデータベースがあり、ブランチの任意のポイントからツリーのシグナル ブランチを検索できる必要があります。

データベース:

+----------------------+
| id |  name  | parent |
+----------------------+
| 1  |  tom   |   0    |
| 2  |  bob   |   0    |
| 3  |  fred  |   1    |
| 4  |  tim   |   2    |
| 5  |  leo   |   4    |
| 6  |  sam   |   4    |
| 7  |  joe   |   6    |
| 8  |  jay   |   3    |
| 9  |  jim   |   5    |
+----------------------+

木:

tom
 fred
  jay
bob
 tim
  sam
   joe
  leo
   jim

例えば:

ユーザー「bob」から「j」を検索すると、「joe」と「jim」のみが取得されます。「j」フォームを「leo」で検索すると、「jim」しか表示されません。

これを行う簡単な方法は考えられないので、助けていただければ幸いです。

4

6 に答える 6

9

このようなクエリをはるかに簡単にするModified Preorder Tree Traversalの使用を検討する必要があります。これが MPTT で表現されたテーブルです。一部のクエリが簡単になるため、親フィールドを残しました。

+----------------------+-----+------+
| id |  name  | parent | lft | rght |
+----------------------+-----+------+
| 1  |  tom   |   0    |  1  |   6  |
| 2  |  bob   |   0    |  7  |  18  |
| 3  |  fred  |   1    |  2  |   5  |
| 4  |  tim   |   2    |  8  |  17  |
| 5  |  leo   |   4    | 12  |  15  |
| 6  |  sam   |   4    |  9  |  16  |
| 7  |  joe   |   6    | 10  |  11  |
| 8  |  jay   |   3    |  3  |   4  | 
| 9  |  jim   |   5    | 13  |  14  |
+----------------------+-----+------+

jユーザーから検索するには、との値をbob使用します。lftrghtbob

SELECT * FROM table WHERE name LIKE 'j%' AND lft > 7 AND rght < 18

lftノードを更新したり、ノードを追加、削除、並べ替えたりするためのロジックを実装するrghtことは困難な場合があります (ヒント: 可能であれば既存のライブラリを使用してください) が、クエリは簡単に実行できます。

于 2011-04-20T06:54:00.090 に答える
1

再帰ループの使用について考えたことはありますか?私はcodeigniterの上に構築したcmsのループを使用して、サイトツリーのどこからでも開始し、その後、すべての子>孫>ひ孫などをフィルタリングします。さらに、SQLを短時間で高速に保ちます。多くの複雑な結合に反対するクエリ。あなたの場合は修正が必要かもしれませんが、うまくいくと思います。

/**
 * build_site_tree
 *
 * @return void
 * @author Mike Waites
**/
public function build_site_tree($parent_id)
{
    return $this->find_children($parent_id);
}

/** end build_site_tree **/

// -----------------------------------------------------------------------

/**
 * find_children
 * Recursive loop to find parent=>child relationships
 *
 * @return array $children
 * @author Mike Waites
**/
public function find_children($parent_id)
{
    $this->benchmark->mark('find_children_start');

    if(!class_exists('Account_model'))
            $this->load->model('Account_model');

    $children = $this->Account_model->get_children($parent_id);

    /** Recursively Loop over the results to build the site tree **/
    foreach($children as $key => $child)
    {
        $childs = $this->find_children($child['id']);

        if (count($childs) > 0)
                $children[$key]['children'] = $childs;
    }

    return $children;

    $this->benchmark->mark('find_children_end');
}

/** end find_children **/

ご覧のとおり、これはかなり単純化されたバージョンであり、codeigniterに組み込まれているため、スイートに変更する必要がありますが、基本的には、実行するたびに配列に追加することを呼び出すループがあります。これにより、ツリー全体を取得できます。また、parent_idが最初に使用可能である限り、ツリー内のあるポイントから開始することもできます。

お役に立てれば

于 2011-04-20T06:23:08.670 に答える
1

これを行う簡単な方法はありません。データベースはツリー形式のデータ構造をうまくサポートしていません。

子から親への結果をプルーニングするか、特定のノードから 9 世代すべてを提供するビューを作成し、子孫で OR を使用して一致させるには、レベルごとに作業する必要があります。

于 2011-04-20T05:42:18.643 に答える
1

新しい「recursive with」コンストラクトが機能しますが、MySQL がそれをサポートしているかどうかはわかりません (まだ)。

with recursive bobs(id) as (
   select id from t where name = 'bob'
   union all
   select t.id from t, bobs where t.parent_id = bobs.id
)
select t.name from t, bobs where t.id = bobs.id
and name like 'j%'
于 2013-01-31T11:07:17.890 に答える
0

ツリー形式でデータを返す単一の SQL クエリはありません。正しい順序でトラバースするための処理が必要です。

1 つの方法は、MySQL にクエリを実行して MPTT を返すことです。

SELECT * FROM テーブル ORDER BY 親 asc;

ツリーのルートがテーブルの最初の項目になり、その子が次に続きます。ツリーは「幅優先」でリストされます (深さの増加するレイヤーで)

次に、PHP を使用してデータを処理し、データ構造を保持するオブジェクトに変換します。

または、ノードを指定して再帰的に検索し、そのすべての子孫のテーブル、またはすべての祖先のテーブルを返す MySQL 検索関数を実装することもできます。これらの手順は遅くなる傾向があるため (再帰的であり、他の基準によってフィルタリングされる過剰なデータを返す)、そのようなデータを何度も何度もクエリしないことがわかっている場合にのみ、これを実行する必要があります。データセットが小さいままであることを知っています (9 レベルの深さと幅?)

于 2011-04-20T07:50:45.747 に答える
0

これは、ストアド プロシージャを使用して次のように行うことができます。

呼び出しの例

mysql> call names_hier(1, 'a');
+----+----------+--------+-------------+-------+
| id | emp_name | parent | parent_name | depth |
+----+----------+--------+-------------+-------+
|  2 | ali      |      1 | f00         |     1 |
|  8 | anna     |      6 | keira       |     4 |
+----+----------+--------+-------------+-------+
2 rows in set (0.00 sec)

mysql> call names_hier(3, 'k');
+----+----------+--------+-------------+-------+
| id | emp_name | parent | parent_name | depth |
+----+----------+--------+-------------+-------+
|  6 | keira    |      5 | eva         |     2 |
+----+----------+--------+-------------+-------+
1 row in set (0.00 sec)

$sqlCmd = sprintf("call names_hier(%d,'%s')", $id, $name);  // dont forget to escape $name
$result = $db->query($sqlCmd);

完全なスクリプト

drop table if exists names;
create table names
(
id smallint unsigned not null auto_increment primary key,
name varchar(255) not null,
parent smallint unsigned null,
key (parent)
)
engine = innodb;

insert into names (name, parent) values
('f00',null), 
  ('ali',1), 
  ('megan',1), 
      ('jessica',3), 
      ('eva',3), 
         ('keira',5), 
            ('mandy',6), 
            ('anna',6);

drop procedure if exists names_hier;

delimiter #

create procedure names_hier
(
in p_id smallint unsigned,
in p_name varchar(255)
)
begin

declare v_done tinyint unsigned default(0);
declare v_dpth smallint unsigned default(0);

set p_name = trim(replace(p_name,'%',''));

create temporary table hier(
 parent smallint unsigned, 
 id smallint unsigned, 
 depth smallint unsigned
)engine = memory;

insert into hier select parent, id, v_dpth from names where id = p_id;

/* http://dev.mysql.com/doc/refman/5.0/en/temporary-table-problems.html */

create temporary table tmp engine=memory select * from hier;

while not v_done do

    if exists( select 1 from names n inner join tmp on n.parent = tmp.id and tmp.depth = v_dpth) then

        insert into hier select n.parent, n.id, v_dpth + 1 
            from names n inner join tmp on n.parent = tmp.id and tmp.depth = v_dpth;

        set v_dpth = v_dpth + 1;            

        truncate table tmp;
        insert into tmp select * from hier where depth = v_dpth;

    else
        set v_done = 1;
    end if;

end while;


select 
 n.id,
 n.name as emp_name,
 p.id as parent,
 p.name as parent_name,
 hier.depth
from 
 hier
inner join names n on hier.id = n.id
left outer join names p on hier.parent = p.id
where
 n.name like concat(p_name, '%');

drop temporary table if exists hier;
drop temporary table if exists tmp;

end #

delimiter ;

-- call this sproc from your php

call names_hier(1, 'a');
call names_hier(3, 'k');
于 2011-04-20T08:43:17.823 に答える