多くの方法があります。これが私が好きな(そして定期的に使用する)アプローチの1つです。
データベース
次のデータベース構造を検討してください。
CREATE TABLE comments (
id int(11) unsigned NOT NULL auto_increment,
parent_id int(11) unsigned default NULL,
parent_path varchar(255) NOT NULL,
comment_text varchar(255) NOT NULL,
date_posted datetime NOT NULL,
PRIMARY KEY (id)
);
データは次のようになります:
+-----+-------------------------------------+--------------------------+---------------+
| id | parent_id | parent_path | comment_text | date_posted |
+-----+-------------------------------------+--------------------------+---------------+
| 1 | null | / | I'm first | 1288464193 |
| 2 | 1 | /1/ | 1st Reply to I'm First | 1288464463 |
| 3 | null | / | Well I'm next | 1288464331 |
| 4 | null | / | Oh yeah, well I'm 3rd | 1288464361 |
| 5 | 3 | /3/ | reply to I'm next | 1288464566 |
| 6 | 2 | /1/2/ | this is a 2nd level reply| 1288464193 |
... and so on...
使いやすい方法ですべてを選択するのはかなり簡単です:
select id, parent_path, parent_id, comment_text, date_posted
from comments
order by parent_path, date_posted;
parent_path, date_posted
による注文 通常、ページを生成するときに必要な順序で結果が生成されます。ただし、これを適切にサポートするインデックスがコメントテーブルにあることを確認する必要があります。そうでない場合、クエリは機能しますが、実際には非効率的です:
create index comments_hier_idx on comments (parent_path, date_posted);
任意の単一のコメントについて、そのコメントの子コメントのツリー全体を簡単に取得できます。 where句を追加するだけです:
select id, parent_path, parent_id, comment_text, date_posted
from comments
where parent_path like '/1/%'
order by parent_path, date_posted;
追加されたwhere句は、すでに定義したものと同じインデックスを使用するので、準備は完了です。
parent_id
を使用していないことに注意してください まだ。実際、それは厳密には必要ではありません。ただし、これを含めるのは、参照整合性を適用するための従来の外部キーを定義し、必要に応じてカスケード削除と更新を実装できるためです。外部キー制約とカスケードルールは、INNODBテーブルでのみ使用できます:
ALTER TABLE comments ENGINE=InnoDB;
ALTER TABLE comments
ADD FOREIGN KEY ( parent_id ) REFERENCES comments
ON DELETE CASCADE
ON UPDATE CASCADE;
階層の管理
もちろん、このアプローチを使用するには、必ずparent_path
を設定する必要があります。 各コメントを挿入するときに適切に。また、コメントを移動する場合(これは明らかに奇妙なユースケースです)、移動したコメントに従属する各コメントの各parent_pathを手動で更新する必要があります。 ...しかし、どちらもかなり簡単に追いつくことができます。
本当に凝ったものにしたい場合(そしてデータベースがそれをサポートしている場合)、parent_pathを透過的に管理するトリガーを書くことができます-これは読者に演習を任せますが、基本的な考え方は、挿入と更新のトリガーが起動することです新しい挿入がコミットされる前。彼らは(parent_id
を使用して)ツリーを上っていきます 外部キー関係)、およびparent_path
の値を再構築します それに応じて。
parent_path
を破ることさえ可能です コメントテーブルのトリガーによって完全に管理される別のテーブルに、必要なさまざまなクエリを実装するためのいくつかのビューまたはストアドプロシージャが含まれています。したがって、階層情報を格納するメカニズムを知っている必要や気にする必要から、中間層のコードを完全に分離します。
もちろん、決して凝ったものは必要ありません。通常は、parent_pathをテーブルにドロップし、他のすべてのフィールドと一緒に適切に管理されるように、中間層にコードを記述するだけで十分です。あなたはすでに管理する必要があります。
制限を課す
MySQL(およびその他のデータベース)では、LIMIT
を使用してデータの「ページ」を選択できます。 条項:
SELECT * FROM mytable LIMIT 25 OFFSET 0;
残念ながら、このような階層データを処理する場合、LIMIT句だけでは目的の結果が得られません。
-- the following will NOT work as intended
select id, parent_path, parent_id, comment_text, date_posted
from comments
order by parent_path, date_posted
LIMIT 25 OFFSET 0;
代わりに、制限を課したいレベルで個別に選択する必要があります。次に、それを「サブツリー」クエリと結合して、最終的に望ましい結果を取得します。
このようなもの:
select
a.*
from
comments a join
(select id, parent_path
from comments
where parent_id is null
order by parent_path, post_date DESC
limit 25 offset 0) roots
on a.parent_path like concat(roots.parent_path,roots.id,'/%') or a.id=roots.id)
order by a.parent_path , post_date DESC;
limit 25 offset 0
というステートメントに注意してください。 、インナーセレクトの真ん中に埋もれています。このステートメントは、最新の25個の「ルートレベル」コメントを取得します。
[編集:物事を好きなように注文したり制限したりする機能を得るには、物事を少しいじる必要があることに気付くかもしれません。これには、parent_path
でエンコードされた階層内の情報の追加が含まれる場合があります 。例:/{id}/{id2}/{id3}/
の代わりに 、post_dateをparent_pathの一部として含めることができます:/{id}:{post_date}/{id2}:{post_date2}/{id3}:{post_date3}/
。これにより、フィールドに事前にデータを入力し、データの変更に応じて管理する必要がありますが、必要な順序と階層を非常に簡単に取得できます]
これがお役に立てば幸いです。