PostgreSQLでCASCADE DELETE just onceを安全に利用するためのヒント

2024-04-05

PostgreSQLにおける「CASCADE DELETE just once」

PostgreSQLでは、CASCADE DELETE オプションを使用することで、親テーブルのレコードを削除するときに、関連する子テーブルのレコードも自動的に削除することができます。しかし、場合によっては、最初のレベルの子テーブルのみを削除し、さらに深いレベルの子テーブルには影響を与えたくない場合があります。

「CASCADE DELETE just once」とは

「CASCADE DELETE just once」は、最初のレベルの子テーブルのみを削除し、それ以降の子テーブルには影響を与えない方法です。これは、DELETE ステートメントに LIMIT 1 を追加することで実現できます。

以下の例では、products テーブルと orders テーブルの関係は、product_id 列で 1 対多 となっています。

-- テーブル定義
CREATE TABLE products (
  product_id SERIAL PRIMARY KEY,
  name VARCHAR(255)
);

CREATE TABLE orders (
  order_id SERIAL PRIMARY KEY,
  product_id INTEGER REFERENCES products(product_id),
  quantity INTEGER
);

-- データ挿入
INSERT INTO products (name) VALUES ('商品1'), ('商品2');
INSERT INTO orders (product_id, quantity) VALUES (1, 10), (1, 20), (2, 30);

通常の CASCADE DELETE

DELETE FROM products
WHERE product_id = 1;

このクエリを実行すると、products テーブルから product_id=1 のレコードが削除されます。同時に、orders テーブルから product_id=1 のレコードも すべて 削除されます。

DELETE FROM products
WHERE product_id = 1
LIMIT 1;

注意事項

  • LIMIT 1 を使用すると、最初のレベルの子テーブルのみが削除されます。
  • 複数のレベルの子テーブルを削除したい場合は、サブクエリを使用する必要があります。
  • LIMIT を使用すると、パフォーマンスが低下する可能性があります。



環境

  • PostgreSQL 14.2
  • psql コマンドラインツール

テーブル定義

-- テーブル定義
CREATE TABLE products (
  product_id SERIAL PRIMARY KEY,
  name VARCHAR(255)
);

CREATE TABLE orders (
  order_id SERIAL PRIMARY KEY,
  product_id INTEGER REFERENCES products(product_id),
  quantity INTEGER
);

-- データ挿入
INSERT INTO products (name) VALUES ('商品1'), ('商品2'), ('商品3');
INSERT INTO orders (product_id, quantity) VALUES (1, 10), (1, 20), (2, 30), (3, 40);
-- 親テーブルのレコードを削除
DELETE FROM products
WHERE product_id = 1;

-- 結果確認
SELECT * FROM products;
-- product_id | name
-- ----------+---------
-- 2         | 商品2
-- 3         | 商品3

SELECT * FROM orders;
-- order_id | product_id | quantity
-- ---------+-----------+---------
-- 3        | 3         | 40
-- 親テーブルのレコードを削除 (LIMIT 1 使用)
DELETE FROM products
WHERE product_id = 1
LIMIT 1;

-- 結果確認
SELECT * FROM products;
-- product_id | name
-- ----------+---------
-- 2         | 商品2
-- 3         | 商品3

SELECT * FROM orders;
-- order_id | product_id | quantity
-- ---------+-----------+---------
-- 1        | 1         | 10
-- 2        | 1         | 20
-- 3        | 3         | 40

サブクエリを使用した多階層の CASCADE DELETE

-- 親テーブルのレコードを削除
DELETE FROM products
WHERE product_id IN (
  SELECT product_id
  FROM orders
  WHERE quantity > 10
);

-- 結果確認
SELECT * FROM products;
-- product_id | name
-- ----------+---------
-- 3         | 商品3

SELECT * FROM orders;
-- order_id | product_id | quantity
-- ---------+-----------+---------
-- 3        | 3         | 40

上記のクエリを実行すると、orders テーブルで quantity が 10 を超えるレコードの product_id を取得し、その product_id を持つ products テーブルのレコードを削除します。orders テーブルの quantity が 10 以下のレコードは影響を受け ません

LIMIT によるパフォーマンスへの影響

LIMIT を使用すると、パフォーマンスが低下する可能性があります。大量のデータを削除する場合は、サブクエリを使用するなど、他の方法を検討する必要があります。




CASCADE DELETE just once の代替方法

サブクエリを使用することで、削除したい子テーブルのレコードを特定することができます。

DELETE FROM products
WHERE product_id IN (
  SELECT product_id
  FROM orders
  WHERE product_id = 1
);

上記のクエリは、orders テーブルで product_id=1 のレコードの product_id を取得し、その product_id を持つ products テーブルのレコードを削除します。

UPDATEJOIN を使用することで、子テーブルのレコードを削除フラグで更新し、その後削除フラグが立っているレコードのみを削除することができます。

UPDATE orders
SET is_deleted = TRUE
WHERE product_id = 1;

DELETE FROM orders
WHERE is_deleted = TRUE;

上記のクエリは、orders テーブルで product_id=1 のレコードの is_deleted 列を TRUE に更新し、その後 is_deleted 列が TRUE のレコードのみを削除します。

トリガーを使用することで、親テーブルのレコードが削除されたときに、自動的に子テーブルのレコードを削除することができます。

CREATE TRIGGER delete_orders
BEFORE DELETE ON products
FOR EACH ROW
BEGIN
  DELETE FROM orders
  WHERE product_id = OLD.product_id;
END;

上記のトリガーは、products テーブルのレコードが削除される前に、orders テーブルから product_id が一致するレコードを削除します。

  • データ量が少ない場合は、LIMIT 1 を使用するのが最も簡単です。
  • データ量が多い場合は、サブクエリや UPDATE と JOIN を使用した方がパフォーマンスが良い場合があります。
  • 複雑なロジックが必要な場合は、トリガーを使用するのが良い場合があります。

postgresql sql-delete cascade


PostgreSQLにおけるAUTO_INCREMENTに相当するデータ型

MySQLのAUTO_INCREMENTは、PostgreSQLではいくつかのデータ型で実現できます。それぞれのデータ型には、わずかな違いと利点・欠点があります。データ型SERIAL - 最も一般的で、自動的に1から始まる整数値を生成します。...


PostgreSQLでメールアドレスの有効性を検証する制約の作り方

要件このチュートリアルを完了するには、以下のものが必要です。PostgreSQLデータベースサーバーがインストールおよび実行されているPostgreSQLデータベースにアクセスできるユーザーアカウント手順電子メールアドレス列を作成するまず、users テーブルなどの既存のテーブルに電子メールアドレス列を作成する必要があります。 次の例では、email という名前の列を作成します。...


コマンドラインから PostgreSQL データベースを削除する際のトラブルシューティング

ターミナルを開きます。以下のコマンドを実行します。例:このコマンドは、my_databaseという名前のデータベースを削除します。オプション-e または --echo: dropdb が生成してサーバーに送信するコマンドをエコー表示します。...


PostgreSQL ビューの CREATE VIEW コードを表示する方法

\d コマンドpsql の \dd コマンド情報スキーマの views ビューそれぞれの方法について、詳細と例を説明します。\d コマンドは、データベースオブジェクトに関する情報を表示します。ビューの CREATE VIEW コードを表示するには、以下の構文を使用します。...


ダウンタイムなしでアップグレード!PostgreSQL 9.6から10.1への移行

アップグレード前の準備バックアップを取る: データベース全体とWAL(Write-Ahead Log)のバックアップを取る。互換性確認: PostgreSQL 10. 1 と互換性がない拡張機能やカスタム設定がないことを確認する。ダウンタイムの計画: アップグレード中はデータベースが使用できないため、ダウンタイムを計画する。...