TreeとTreeNodeを組み合わせたデータツリー構造を作成しています。ツリーには、データに対するルートアクションとトップレベルアクションが含まれます。 UIライブラリを使用して、ツリーをTreeViewにバインドできるウィンドウフォームでツリーを表示しています。
このツリーとノードをDBに保存する必要があります。ツリーを保存し、次の機能を取得する最良の方法は何ですか:
2つのアイデアがありました。 1つ目は、データをテーブル内の1つのライナーにシリアル化することです。 2番目はテーブルに保存することですが、データエンティティに移動すると、変更されたノード上のテーブルの行の状態が失われます。
何か案は?
最も簡単な実装は、隣接リスト構造です:
id parent_id data
ただし、一部のデータベース、特にMySQL
には、MySQL
にない再帰クエリを実行する機能が必要なため、このモデルの処理に問題があります。
別のモデルは、入れ子集合です:
id lft rgt data
ここで、lft
およびrgt
は、階層を定義する任意の値です(子のlft
、rgt
は、親のlft
、rgt
内にある必要があります)
これは再帰的なクエリを必要としませんが、保守が遅くなり難しくなります。
ただし、MySQL
では、SPATIAL
abitiliesを使用してこれを改善できます。
私のブログでこれらの記事を参照してください。
より詳細な説明については。
SQL-Antipatternsについてこのslidshareをブックマークしました。いくつかの代替案について説明しています。 http://www.slideshare.net/billkarwin/sql-antipatterns-strike-back?src=embed
そこからの推奨事項は、クロージャーテーブルを使用することです(スライドで説明されています)。
概要は次のとおりです(スライド77)。
| Query Child | Query Subtree | Modify Tree | Ref. Integrity
Adjacency List | Easy | Hard | Easy | Yes
Path Enumeration | Easy | Easy | Hard | No
Nested Sets | Hard | Easy | Hard | No
Closure Table | Easy | Easy | Easy | Yes
マテリアライズドパスソリューションについて言及している人がいないことに驚いています。これはおそらく、標準SQLでツリーを操作する最も速い方法です。
このアプローチでは、ツリー内のすべてのノードに列pathがあり、ルートからノードへのフルパスが格納されます。これには、非常にシンプルで高速なクエリが含まれます。
サンプルテーブルnodeをご覧ください:
+---------+-------+
| node_id | path |
+---------+-------+
| 0 | |
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
| 4 | 1.4 |
| 5 | 2.5 |
| 6 | 2.6 |
| 7 | 2.6.7 |
| 8 | 2.6.8 |
| 9 | 2.6.9 |
+---------+-------+
ノードxの子を取得するには、次のクエリを記述できます。
SELECT * FROM node WHERE path LIKE CONCAT((SELECT path FROM node WHERE node_id = x), '.%')
列pathは、[〜#〜 ] like [〜#〜]句。
PostgreSQLを使用している場合、ltree
を使用できます。これは、ツリーデータ構造を実装するcontrib拡張機能のパッケージ(デフォルトで付属)です。
docs から:
CREATE TABLE test (path ltree);
INSERT INTO test VALUES ('Top');
INSERT INTO test VALUES ('Top.Science');
INSERT INTO test VALUES ('Top.Science.Astronomy');
INSERT INTO test VALUES ('Top.Science.Astronomy.Astrophysics');
INSERT INTO test VALUES ('Top.Science.Astronomy.Cosmology');
INSERT INTO test VALUES ('Top.Hobbies');
INSERT INTO test VALUES ('Top.Hobbies.Amateurs_Astronomy');
INSERT INTO test VALUES ('Top.Collections');
INSERT INTO test VALUES ('Top.Collections.Pictures');
INSERT INTO test VALUES ('Top.Collections.Pictures.Astronomy');
INSERT INTO test VALUES ('Top.Collections.Pictures.Astronomy.Stars');
INSERT INTO test VALUES ('Top.Collections.Pictures.Astronomy.Galaxies');
INSERT INTO test VALUES ('Top.Collections.Pictures.Astronomy.Astronauts');
CREATE INDEX path_Gist_idx ON test USING Gist (path);
CREATE INDEX path_idx ON test USING BTREE (path);
次のようなクエリを実行できます。
ltreetest=> SELECT path FROM test WHERE path <@ 'Top.Science';
path
------------------------------------
Top.Science
Top.Science.Astronomy
Top.Science.Astronomy.Astrophysics
Top.Science.Astronomy.Cosmology
(4 rows)
データのクエリおよび更新方法によって異なります。すべてのデータを1つの行に格納する場合、基本的に単一のユニットであり、すべてのデータを書き換えずにクエリしたり、部分的に更新したりすることはできません。
各要素を行として保存する場合、最初に MySQLでの階層データの管理 (MySQL固有ですが、他の多くのデータベースにも当てはまります)を読む必要があります。
ツリー全体にのみアクセスする場合、隣接リストモデルにより、再帰クエリを使用せずにルートの下にあるすべてのノードを取得することが難しくなります。先頭にリンクする列を追加すると、SELECT * WHERE head_id = @id
と1つの非再帰クエリでツリー全体を取得しますが、データベースを非正規化します。
一部のデータベースには、階層データの保存と取得を容易にするカスタム拡張機能があります。たとえば、Oracleには CONNECT BY があります。
これはグーグル検索で「SQLツリー」を尋ねるときの一番の答えなので、今日(2018年12月)の観点からこれを更新しようとします。
ほとんどの答えは、隣接リストの使用が単純で遅いことを示唆しているため、他の方法を推奨します。
バージョン8(2018年4月に公開)以降、MySQLは 再帰共通テーブル式(CTE) をサポートします。 MySQLはショーに少し遅れていますが、これは新しいオプションを開きます。
チュートリアル here があり、再帰クエリを使用して隣接リストを管理する方法を説明しています。
再帰はデータベースエンジン内で完全に実行されるようになったため、過去(スクリプトエンジンで実行する必要があったとき)よりもはるかに高速になりました。
ブログ here は、いくつかの測定値(バイアスがあり、MySQLではなくpostgresの場合)を提供しますが、それでも、隣接リストを遅くする必要はないことを示しています。
したがって、今日の私の結論は次のとおりです。
最良の方法は、実際に各ノードにidとparent_idを与えることだと思います。ここで、親idは親ノードのidです。これにはいくつかの利点があります
各ノード行に(通常のノードデータに加えて)親IDが含まれるテーブル「ノード」のようなもの。ルートの場合、親はNULLです。
もちろん、これにより子供を見つけるのに少し時間がかかりますが、この方法では実際のデータベースは非常にシンプルになります。