弱いエンティティに対して一意でなく自然な識別子をどのように生成すべきですか?
たとえば、order_id
がorder
テーブルの主キーであり、(order_id, item_number)
がorder_item
の外部キーを含むorder_id
テーブルの主キーである場合、item_number
を生成する最善の方法は?
いくつかの可能性が頭に浮かびますが、理想的なものはありません。
item_number
の自動インクリメント:order_item
エンティティは脆弱ではなくなり、複合キーは冗長になります。
トリガーを使用して、特定のitem_number
の現在の最大order_id
を検索し、次にインクリメントします。行が削除されると、PKが別のレコードに再割り当てされる可能性があります。これは良い考えではないでしょうか。 (編集:これは joanoloの答え )で説明されているようにトリガーを使用せずに行うこともできます
トリガーを使用して、order_id
ごとに新しいシーケンスを作成し、適切なシーケンスから何らかの方法でitem_number
sを描画します。これは、機能的に望ましい動作ですが、実装するのは面倒なようです。 order_id
でシーケンスを参照することもできますか?
編集-密接に関連(重複していない場合):
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
_で動作します。
データの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によるコメントを参照してください。