web-dev-qa-db-ja.com

脆弱なエンティティの一意の複数列キーを生成するためのベストプラクティス?

弱いエンティティに対して一意でなく自然な識別子をどのように生成すべきですか?

たとえば、order_idorderテーブルの主キーであり、(order_id, item_number)order_itemの外部キーを含むorder_idテーブルの主キーである場合、item_numberを生成する最善の方法は?

いくつかの可能性が頭に浮かびますが、理想的なものはありません。

  1. item_numberの自動インクリメント:order_itemエンティティは脆弱ではなくなり、複合キーは冗長になります。

  2. トリガーを使用して、特定のitem_numberの現在の最大order_idを検索し、次にインクリメントします。行が削除されると、PKが別のレコードに再割り当てされる可能性があります。これは良い考えではないでしょうか。 (編集:これは joanoloの答えで説明されているようにトリガーを使用せずに行うこともできます

  3. トリガーを使用して、order_idごとに新しいシーケンスを作成し、適切なシーケンスから何らかの方法でitem_numbersを描画します。これは、機能的に望ましい動作ですが、実装するのは面倒なようです。 order_idでシーケンスを参照することもできますか?

編集-密接に関連(重複していない場合):

2
Gord Stephen

1.は、エラーが発生しにくく、最も単純で高速です。
2.または3.のようなトリガーソリューションは、微妙な競合状態の影響を受けます同時書き込みアクセス。

この場合、_item_number_をserial列にし、_order_item_のPKも作成します。基になるシーケンスから描画されたデフォルト値をそのまま使用し、never列を更新します。

一般的なクエリのパフォーマンスのために、_(order_id, item_number)_に複数列のインデックスを作成します。 (UNIQUEでもかまいませんが、そうである必要はありません。)通常の設定(_order_id_と_item_number_はどちらもプレーンintegerにすることができます)では、マルチカラムインデックスは、ちょうど_order_id_のインデックスと同じくらい小さくて高速です:

(私がコメントしたように:)通常、アイテム番号の唯一の重要な役割は、unique(およびimmutable)になることです。アイテム間でstable並べ替え順序が必要な場合は、_item_number_のシリアル値に依存するだけかもしれません。これらの数値は必ずしもトランザクションコミットの順序ではないことに注意してください。トランザクションのタイムスタンプ _current_timestamp_(またはstatement_timestamp()またはclock_timestamp() を行に追加すると便利な場合があります。要件とアクセスパターンによって異なります。

人間の目のためにVIEWを追加できます。1から始まる_order_id_ごとのアイテム番号は、 row_number() 、上記の基準で並べ替えられます。ただし、内部では、一意で不変の_item_number_で動作します。

4

データのINSERTの際にデータベースがitemNumbersを処理する、定義と一致する4番目の選択肢を使用できます。トリガーは必要ありません。 [使用することもできますが。]

これがあなたのスキーマであると仮定します:

_CREATE TABLE "Order"
(
    "orderId" integer primary key,  -- this should probably be serial
    "order" text not null
) ;

CREATE TABLE "OrderItem"
(
    "orderId" integer not null REFERENCES "Order"("orderId") 
         ON DELETE CASCADE ON UPDATE CASCADE,
    "itemNumber" integer not null,
    "orderItem" text,

    PRIMARY KEY ("orderId", "itemNumber")
) ;
_

3つの注文があることを確認します。

_INSERT INTO "Order" ("orderId", "order") VALUES(1, 'Order number 1') ;
INSERT INTO "Order" ("orderId", "order") VALUES(2, 'Order number 2') ;
INSERT INTO "Order" ("orderId", "order") VALUES(3, 'Order number 3') ;
_

注文3に1つのアイテムを追加する必要がある場合は、次のようにします。

_INSERT INTO 
    "OrderItem" 
    ("orderId", "orderItem", "itemNumber")
VALUES 
    (3, 'whichever item you need', 
     (SELECT coalesce(max("itemNumber"),0) + 1 
        FROM "OrderItem" 
       WHERE "orderId"=3)
    ) ;
_

つまり、INSERTを実行するときは、max("itemNumber")for your specific "order_Id"を探して1を追加するだけです。これが注文にアイテムを挿入する最初のアイテムである場合、COALESCE(___、0)+ 1はNULL + 1ではなく0 + 1になります(これは機能しません)。

複数のアイテムを複数の注文に追加する必要がある場合(ほとんどのアプリケーションでは一般的ではありません)、少し複雑な方法で追加できますが、同じ原則を使用します。

_-- Assume you want to add items to orders 1 and 2
WITH "orderItemsToAdd" ("orderId", "orderItem") AS
(
    VALUES 
    (1, 'first item, 1st order'), 
    (1, 'second item, 1st order'),
    (2, 'first item, 2nd order'),
    (2, 'second item, 2nd order'),
    (2, 'third item, 2nd order')
)

-- Get the max item numbers (or zero, via coalesce) for each order

, "maxItemNumbers" AS
(
SELECT
    "orderId", coalesce(max("itemNumber"), 0) AS "baseItemNumber"
FROM
    "orderItemsToAdd"
    LEFT JOIN "OrderItem" USING("orderId")
GROUP BY
    "orderId"                    
)

-- We insert new items, using row_number() + baseItemNumber as the new item numbers
-- (NOTE: orderItemsToAdd should, in practice be ORDERed BY something!)
INSERT INTO 
    "OrderItem" ("orderId", "itemNumber", "orderItem")
SELECT
    "orderId", "baseItemNumber" + (row_number() over (partition by "orderId")), "orderItem"
FROM
    "orderItemsToAdd" 
    JOIN "maxItemNumbers" USING("orderId")
RETURNING
    * ;
_

これはあなたが得るものです:

_    +--------------------------------+
    ¦ 1 ¦ 1 ¦ first item, 1st order  ¦
    ¦---+---+------------------------¦
    ¦ 1 ¦ 2 ¦ second item, 1st order ¦
    ¦---+---+------------------------¦
    ¦ 2 ¦ 1 ¦ first item, 2nd order  ¦
    ¦---+---+------------------------¦
    ¦ 2 ¦ 2 ¦ second item, 2nd order ¦
    ¦---+---+------------------------¦
    ¦ 2 ¦ 3 ¦ third item, 2nd order  ¦
    +--------------------------------+
_

rextester で確認できます。

次のことに注意する必要があります。


補足として、PostgreSQLを使用している場合は、camelCaseOnesではなく_underscored_lowercase_identifiers_を使用することをお勧めします。 _"_を入力する必要がないので、多くの時間を節約できます(そして、それらを忘れてしまうリスクもあります);-)


2番目の付記:many人々がsame order同時。 @Erwinによるコメントを参照してください。

0
joanolo