2

例として、この階層スキーマについて考えてみます。 ここに画像の説明を入力してください

すべてのidフィールドが自動インクリメントの主キーであり、外部キーは[parent_table_name]_id規則によって名前が付けられていると想定します。

問題

データベースに複数の会社が存在するとすぐに、会社はそれらの間ですべての主キーシーケンスを共有します。

たとえば、会社の行が2つある場合、customer_groupテーブルは次のようになります。

| id | company_id |
-------------------
| 1  |     1      |
| 2  |     1      |
| 3  |     2      |
| 4  |     2      |
| 5  |     1      |
-------------------

しかし、それはこのように見えるはずです

| id | company_id |
-------------------
| 1  |     1      |
| 2  |     1      |
| 1  |     2      |
| 2  |     2      |
| 3  |     1      |
-------------------

この動作は、顧客および直接または間接的に会社を参照するツリー内の他のテーブルにも表示される必要があります。

この目的のために2番目のid列( relative_idのような名前)を作成し、一意のid列をそのままにしておくことに注意してください。これは実際には主に表示目的であり、ユーザーがこれらのデータエンティティを参照する方法です。


これが階層の1つのレベルにすぎない場合、比較的単純なソリューションになります。テーブル(table_name、company_id、current_id)と、任意のテーブルに挿入する前に起動するトリガープロシージャを作成し、現在のIDを1ずつ増やし、行のrelative_idをその値に設定することができます。挿入クエリにcompany_idが含まれている場合は、簡単です。

しかし、会社を直接参照していないテーブルはどうでしょうか。この例の階層の最下位レベルのように、顧客のみを参照するworkorder。 'customer_id'からはしごを登って、最終的に子育てのcompany_idを取得するための、クリーンで再利用可能なソリューションはありますか?

各INSERTでSELECTを使用して階層を再帰的に上に移動することは、パフォーマンスの観点からはあまり魅力的ではありません。

また、これらのテーブルごとに会社に外部キーを追加するというアイデアも好きではありません。スキーマは、テーブルを追加するたびにますます醜くなります。

しかし、これらは私が見ることができる2つの解決策ですが、私は適切な場所を探していない可能性があります。

4

2 に答える 2

1

生成されたキーを使用している場合、会社は主キーが何であるかを気にする必要はありません。それらは無意味であるはずです。平等のために比較され、他には何もありません。私は以前にこれについて不平を言ったので、あなたが書いているのを見て本当にうれしいです:

この目的のために2番目のid列(relative_idのような名前)を作成し、一意のid列をそのままにしておくことに注意してください。これは実際には主に表示目的であり、ユーザーがこれらのデータエンティティを参照する方法です。

あなたはそれを正しくやっています。

ほとんどの場合、IDが何であるかは問題ではないため、シーケンスから出てくるものは何でも与えることができ、穴やギャップは気になりません。会社間のリークが心配な場合は(ありそうもない)、シーケンスを疑似ランダムジェネレーターへの入力として使用することでIDを難読化できます。数年前のこれに関する私の質問に答えてDanielVeritéが書いた関数pseudo_encryptを参照してください。

多くの場合、請求書番号など、完全に連続したギャップレスIDが必要な特定の目的があります。それらについては、カウンターテーブルを使用する必要があります-はい-会社IDを検索します。このようなIDの生成は遅く、とにかく同時実行性がひどいので、インデックス付きキーに1つまたは2つ追加SELECTしてもそれほど問題はありません。JOINただし、sを使用してスキーマを再帰的に上に移動するのではなくSELECT、一連のJOINsを使用するだけです。たとえば、workorderキー生成トリガーへの挿入workorderは、(テストされていない)のようになります。

   CREATE OR REPLACE FUNCTION workorder_id_tgfn() RETURNS trigger AS $$
   BEGIN
       IF tg_op = 'INSERT' THEN
           -- Get a new ID, locking the row so no other transaction can add a
           -- workorder until this one commits or rolls back.
           UPDATE workorder_ids
           SET next_workorder_id = next_workorder_id + 1 
           WHERE company_id = (SELECT company_id
                 FROM customer
                 INNER JOIN customer_group ON (customer.customer_group_id = customer_group.id) 
                 INNER JOIN company ON (customer_group.company_id = company.id)
               WHERE customer.id = NEW.customer_id)
           RETURNING next_workorder_id
           INTO NEW.id;
       END IF;
   END;
   $$ LANGUAGE 'plpgsql';

UPDATE ... RETURNING ... INTO構文については、単一行の結果を使用したクエリの実行を参照してください。

複数の会社に問題がない場合でも、通常のシーケンスにギャップが生じる可能性があります。観察:

CREATE TABLE demo (id serial primary key, blah text);

BEGIN;
INSERT INTO demo(blah) values ('aa');
COMMIT;

BEGIN;
INSERT INTO demo(blah) values ('bb');
ROLLBACK;

BEGIN;
INSERT INTO demo(blah) values ('aa');
COMMIT;

SELECT * FROM demo;

結果:

regress=#     SELECT * FROM demo;
 id | blah 
----+------
  1 | aa
  3 | aa
于 2012-10-08T08:42:39.000 に答える
0

「しかし、それはこのように見えるはずです」

| id | company_id |
-------------------
| 1  |     1      |
| 2  |     1      |
| 1  |     2      |
| 2  |     2      |
| 3  |     1      |
-------------------

私はそうすべきではないと思います、そしてあなたは多対多の関係を望んでいると思います。customer_groupテーブル:

| id | name |
-------------
| 1  |  n1  |
| 2  |  n2  |
| 3  |  n3  |
-------------

そして、customer_group_companyテーブル:

| group_id | company_id |
-------------------------
|    1     |     1      |
|    2     |     1      |
|    1     |     2      |
|    2     |     2      |
|    3     |     1      |
-------------------------
于 2012-10-08T02:18:17.790 に答える