web-dev-qa-db-ja.com

シーケンスとトリガーによる自動インクリメントID:手動オーバーライドの処理方法(一意でないエラーの原因)

自動インクリメントIDを_PROJECT_ID_列に提供するシーケンスとトリガーがあります。

_PROJECTS
+------------+-----+
| PROJECT_ID | ... |
+------------+-----+
| LC000001   |     |
| LC000002   |     |
| LC000003   |     |
| LC000004   |     |
| LC000005   |     |
+------------+-----+

CREATE SEQUENCE SEQ_PROJECT START WITH 1 INCREMENT BY 1

CREATE OR REPLACE TRIGGER "PROJECTS_PROJECT_ID_TRIG" BEFORE INSERT ON PROJECTS
    FOR EACH ROW
    WHEN (NEW.PROJECT_ID IS NULL) 
      BEGIN
       :NEW.PROJECT_ID := 'LC' ||to_char(SEQ_PROJECT.NEXTVAL,'FM000000');  
      END;
_

ソース: 次の接頭辞付きIDを取得するトリガー:より良い方法はありますか?

シーケンスとトリガーは、通常の状況では問題なく機能します。

ただし、 アプリケーション編集環境 は、通常のとは異なります。ユーザーはテーブル全体にアクセスしてデータを編集します。それはまるでテーブルが大きなスプレッドシートか何かのように扱われているようなものです(私が考えるとそれは一種の恐ろしいことです)。とにかく、このシナリオは「属性テーブルウィンドウでの編集」と呼ばれます。ユーザーは任意の行の任意の列を手動で編集できます。つまり、シーケンスとトリガーによって生成されたIDを上書きできます。

例えば:

  1. ユーザーが新しい行を作成します。シーケンスとトリガーは、_LC000006_のIDを提供します。
  2. その後、ユーザーは_LC000001_を_LC000007_に手動で変更します。 (私はユーザーがこれを行う正当な理由を考えることはできませんが、彼らには能力があるので、おそらくそれが起こるでしょう。)
  3. ユーザーが別の新しい行を作成します。シーケンスとトリガーは_LC000007_のIDを提供しようとしますが、エラーをスローします。

    Database Row Change: An unexpected failure occured. Underlying DBMS error [ORA-00001: unique constraint (USER1.PROJECT_ID_IDX) violated][USER1.PROJECTS]

エラーは、フィールドの一意のインデックスが原因です。重複する値はありません。

実際的に言えば、ユーザーがこのようなエラーを受け取るという事実に耐えることができます(チームの誰かが1つまたは2つの_PROJECT_IDs_を手動でオーバーライドするのに十分なほど賢くない場合)。すべてのユーザーが行う必要があるのは、再試行することです。シーケンス/トリガーは、魔法のように次の番号にスキップします。

ただし、もちろん、順序が異なる(1つまたは2つだけではない)10から100のIDが存在する可能性があります。信じられないかもしれませんが、これは実際の可能性です。これは、 フィールド計算機 というツールが存在するためです。これを念頭に置いて、数十または数百のシーケンス外IDのエラーメッセージを取得することは、それほど現実的ではないようです。

だから、私は好奇心が強いのですが、この問題をどうにかして回避できますか?

1
Wilson

誰かがPROJECT_ID列を更新した後に何が起こるかを対処するのではなく、最初にPROJECT_ID列の更新を拒否してみましたか?

おそらく、PROJECT_ID列の更新権限を取り消しますか?または多分... yuck ... PROJECT_ID列を更新しようとする試みをロールバック(およびエラーを生成)するトリガー?

彼らが重複したPROJECT_IDを生成しようとするとエラーが発生することに問題はないので、PROJECT_IDを更新しようとするとエラーが発生することに問題がないと思います...そしておそらく「WTF!?!?!」 DBA(あなた?)に電子メールで送信してください... PROJECT_ID列を更新できないことに慣れます。

または、本当にPROJECT_ID列を更新できるようにしますか?

3
markp-fuso

私が正しくフォローしていると仮定すると、既存のデータが存在するIDフィールドにデータを入力するためにシーケンスを使用する必要があります。生成された新しいID値が既存の値と競合しないことを確認する必要があります。

私の経験では、3つのオプションがあります。

  • スキップされた番号は無視してください。現在の最大数を見つけて、そこでシーケンスを開始します。
  • 現在のすべてのID値が連続するように既存の行の番号を付け直します(たとえば、実線のブロック:IDがLC000001-LC004012の4012レコード)。
  • シーケンスを使用する代わりに、利用可能なID値のテーブルを維持し、そこから新しい番号を割り当てます。

まず、既存の値の状態を評価する必要があります。 IDには、1から999,999までの数字を使用できます。現在、使用中の最大数はいくつですか、そこには行がいくつありますか、そしてそれはどれだけのアクティビティを表しますか?

いくつかの異なるシナリオを考えてみましょう:

  • 最大数= LC010203;行数= 8192

    推奨:シーケンスを10204で始まるように設定します。スキップした〜2000の可能な数を心配する必要はありません。それらは、利用可能なIDの合計の0.2%に相当します。これは、ほとんどの場合、少なすぎて操作する価値がありません。

  • 最大数= LC200001;行数= 5,120;データは5年間の事業を表す

    **推奨事項**:繰り返しになりますが、おそらく未使用の値をスキップして200002から始めることができます。はい、潜在的な数値のほぼ20%を失っていますが、平均して5倍のビジネスを進めているとしても過去5年間に見られるように、残っているものは30年以上あなたを保持するはずです。

    注:「スキップされた」値の損失を無関係にする特別なケースがある場合があります。たとえば、毎年新しい2文字のプレフィックスを使用する場合、IDシーケンスを毎年の初めに再起動するオプションがある場合があります(実際にLC200001を格納しているだけでなく、200001を格納し、 LCは、アプリケーションとレポートツールの前にあります。

  • 最大数= 'LC909090'、行数= 5,000、1年間の営業期間

    推奨:OK、問題が発生しました。おそらく909091から始めることはできません。 2年以内に数が足りなくなる可能性があります。

そこに行ったことがある。

その時点で、最も簡単なオプションは、すべての既存のレコードに番号を付け直すことです。そのため、IDがLC000123からLC909090までの5,000行は、ID値がLC000001からLC005000までの5,000行になります。 ID値を外部キーとして使用する場合は、外部キーを識別する必要があります(DB構造でそのように宣言されていない場合があります)。データの整合性を強制するメカニズムを少なくとも一時的に無効にします。次に、すべての値を(ローカルテーブルとリモートテーブルの両方で)更新し、コードのみでその整合性を維持します。

しかし、あなたはさらに悪い状態にあるかもしれません。 ID値が人々によって使用される場合(組織の全員が「もの」を識別する方法であるかどうか、または顧客が自分の支払いをアカウントに正しく一致させるために顧客が支払い伝票に書き込む番号であるかどうか)、それを変更することができます非常に悪い考え。

それらを無視できず、リセットできない場合、残りのオプションの1つは、使用可能なすべての番号を判別し、それらをテーブルに格納し、作成したプロセスを使用して、使用可能な番号を新しいレコードに割り当てることです。 。

私は以前の立場で、このようなことをしなければなりませんでした。 「数値」テーブルを使用して、可能なすべての値をリストしました。使用中のものを削除しました。残りをテーブルに保存しました。テーブルには、IDが使用されたかどうかを示す列が含まれていました(最初はすべて未使用でした)。

新しい番号を割り当てる必要がある場合は、UPDATEクエリを実行し、未使用の最小番号を使用済みとしてマークし、RETURNING句を使用してマークされた番号を取得します(1つのアトミックアクションこれは、実際に新しい顧客をサインアップするためにWebアプリケーションで使用されたため、問題を回避しました。

これは設定が大変なので、既存のIDをすべて変更するよりも良いオプションです。 1日に数百または数千の顧客の支払いを受け取り、それらの支払いを顧客のアカウントに手動で適用しなければならないことを想像してください。小切手の顧客番号と//または送金伝票がシステムの内容と一致しなくなった...

2
RDFozz

私が理解したことから、それはそれほど複雑ではありません。

i)1つのunqiue列Project_IDがあり、これもユーザーが編集できます。

ii)その結果、順番にギャップが生じます。そうしよう、ユーザー要件を変更することはできないので、エラー番号をキャッチし、代わりにユーザーフレンドリーなメッセージを表示します。 "このIDは使用できません"

iii)まだない場合は、2列追加します。

iv)1つの列には、ActualProjectIDが保持されます。これは適切な順序で一意であり、誰も編集できません。この列は、必要な場合にプロジェクトIDを順番に表示するのに役立ちます。

v)2番目の列は単純なint auto increamentです。この列はPKにし、FK目的としても使用します。

私はiv)カラムだと確信していますが、v)カラムについてはあまり詳しくないので、さらに分析を行う必要があります。

1
KumarHarsh