2024年4月11日木曜日

複合主キーを持つ表のメンテナンスを行なう

複合主キーが設定されている表のメンテナンスを行なう画面を作成してみます。以下のスキーマ定義を例に取ります。

TEST_CATEGORIESにカテゴリーのコード(列CATEGORY_CD)と名前(列CATEGORY_NAME)を定義し、表TEST_PARTSの主キーは、表TEST_CATEGORIESに定義されているCATEGORY_CDPART_IDの複合主キーとして定義します。

create table test_categories (
    category_cd      varchar2(2 char) not null,
    category_name    varchar2(80 char) not null,
    constraint test_categories_pk primary key(category_cd)
);

create table test_parts (
    category_cd    varchar2(2 char)
                   constraint test_parts_category_cd_fk references test_categories,
    part_id        varchar2(2 char) not null,
    part_name      varchar2(80 char) not null,
    constraint test_parts_pk primary key(category_cd, part_id)
);
空のAPEXアプリケーションを作成します。名前複合主キーとします。


アプリケーションが作成されます。

最初に表TEST_CATEGORIESのメンテナンスを行なう対話モード・レポートとフォームのページを作成します。

ページの作成をクリックします。


対話モード・レポートを選択します。


対話モード・レポートのページの名前Categoriesとします。フォーム・ページを含めるオンにします。フォーム・ページ名Categoryとします。

データ・ソースは表TEST_CATEGORIESですが、主キー列CATEGORY_CDには、データを登録する人が意味のあるコードを割り当てます(いわゆる自然キーまたはナチュラルキー)。

Oracle APEXのページ生成ウィザードは主キーは自動生成される(いわゆる代理キーまたはサロゲートキー)ことを前提としてるため、データ・ソースに表TEST_CATEGORIESを指定し、主キーとしてCATEGORY_CDを選択すると、対話モード・レポートでは列CATEGORY_CDは非表示列となり、また、フォームのページでは列CATEGORY_CDに対応するページ・アイテムが非表示かつ変更不可になります。

ここでは、主キー列CATEGORY_CDを通常の列と同様に扱えるように、ROWIDを主キーとして扱うように対話モード・レポートとフォームを構成します。

データ・ソースソース・タイプSQL問合せを選択し、SQL SELECT文を入力に以下を記述します。

select rowid, category_cd, category_name from test_categories

主キー列CATEGORY_CDは通常の列と同じ扱いになるため、作成されたフォームでは一度割り当てたコードを変更することも可能です。一度割り当てたコードを変更させたくない場合などは、フォームのページが作成された後にページ・アイテムのタイプ表示のみにするか、読取り専用の条件を適用します。

へ進みます。


主キー列1ROWID(Rowid)を選択します。

ページの作成をクリックします。


TEST_CATEGORIESを編集するページが作成されました。


同様に、表TEST_PARTSのメンテナンスを行なう対話モード・レポートとフォームのページを作成します。

データ・ソースのSELECT文は以下になります。

select rowid, category_cd, part_id, part_name from test_parts


主キー列1は表TEST_CATEGORIESと同様にROWID(Rowid)を選択し、ページの作成を実行すると、表TEST_PARTSを編集するページが作成されます。


カテゴリーのコードと名前のLOVを、共有コンポーネントとして作成します。

共有コンポーネントLOVを開きます。


作成をクリックします。


LOVの作成最初から行います。そのままへ進みます。


作成するLOVの名前CATEGORIES_LOVとします。タイプDynamicを選択します。

へ進みます。


LOVソース表/ビューの名前としてTEST_CATEGORIESを選択します。

へ進みます。


列マッピング戻り列としてCATEGORY_CD表示列としてCATEGORY_NAMEを選択します。

LOVを作成します。


LOVとしてCATEGORIES_LOVが作成されました。


カテゴリのコードと名前を入れ替るLOVが作成されたので、表TEST_PARTSの対話モード・レポートとフォームのページに使用します。

TEST_PARTS対話モード・レポートのページを開き、列CATEGORY_CDを選択します。

識別タイププレーン・テキスト(LOVに基づく)に変更します。LOVに先ほど作成したCATEGORIES_LOVを選択します。ヘッダーはCategory CdだったのでCdを除いてCategoryのみに変更します。


TEST_PARTSフォームのページを開き、ページ・アイテムP5_CATEGORY_CDを選択します。

識別タイプポップアップLOVに変更します。ラベルCategoryとします。LOVタイプ共有コンポーネントを選択し、LOVとしてCATEGORIES_LOVを設定します。

追加値の表示オフNULL値の表示オンにしてNULL表示値として- Select Category -を記述します。


以上で複合主キーを持つ表TEST_PARTSのメンテナンス画面が作成できました。

アプリケーションを実行し、実際にデータの入力を行います。

Categoriesのページを開き、以下のデータを作成します。

コード CL - 名前 Cloud
コード DB - 名前 Database


表TEST_PARTSにデータを作成します。

対話モード・レポートの列Categoryには、CATEGORIES_LOVよりCATEGORY_NAMEの値が表示されています。


一般的には、ソースのSELET文に複合主キーをそのまま表示する列を追加することが多いように思います。

対話モード・レポートのソースSQL問合せを以下に変更します。

select rowid, category_cd || part_id part_cd, category_cd, part_id, part_name from test_parts


ソースのSELECT文に後から追加した列は非表示列になるため、アクションから表示列に追加する必要があります。追加した列Part Cdは以下のように表示されます。

フォームを開くリンクはROWIDを引数としているため、対話モード・レポートの列の追加や修正の影響は受けません。


編集フォームのCategoryはポップアップLOVに変わっています。


複合主キーであってもフォームとしての扱いは通常の列なので、CategoryおよびPart Idの項目は変更可能です。ただし、同じ主キーが登録済みであればORA-00001の一意制約違反のエラーが発生します。


主キーとして設定されている値を変更することはあまり無いため、これらは値が設定済みであれば変更不可とすることが望ましいでしょう。

例えばページ・アイテムP5_CATEGORY_CDであれば、読取り専用タイプアイテムはNULLではないを選択し、アイテムとしてP5_CATEGORY_CDを指定します。


このように設定することにより、一度登録した部品についてはカテゴリを変更不可にできます。


Oracle APEXのページ作成ウィザードを使って、表TEST_PARTSの対話モード・レポートとフォームを作ると、どのようになるか確認してみます。

ROWIDを使ったページと見分けるために、ページ名は大文字にします。対話モード・レポートのページはPARTS、フォームのページはPARTです。

データ・ソース表/ビューの名前としてTEST_PARTSを指定します。


主キー列1CATEGORY_CD(Varchar2)主キー列2PART_ID(Varchar2)となります。


作成された対話モード・レポートのページを確認します。

CATEGORY_CDについては、タイププレーン・テキスト(LOVに基づく)LOVとしてCATEGORIES_LOVが設定されています。主キー列のタイプはデフォルトで非表示列ですが、複合種キーの場合は異なるのかもしれません。


PART_IDについてはタイプ非表示列となっていたため、表示されるようにプレーン・テキストに変更します。


フォームを開く際に、一意検索をするために主キー列である列CATEGORY_CDPART_IDの値が必要です。このとき、列CATEGORY_CDの表示値がCATEGORY_CDの値として渡されます。表示値ではなく戻り値を引数として渡すために、レポートのソースに列CATEGORY_CDの値をそのまま参照するための列を追加します。

対話モード・レポートのソースタイプSQL問合せに変更し、SQL問合せとして以下を記述します。列CATEGORY_CD_Vを追加しています。
select CATEGORY_CD,
       PART_ID,
       PART_NAME,
       CATEGORY_CD CATEGORY_CD_V
  from TEST_PARTS

追加した列CATEGORY_CD_Vタイプ非表示列にします。


対話モード・レポートの属性を開き、リンクターゲットを修正します。


ページ・アイテムP7_CATEGORY_CDに渡す値を\#CATEGORY_CD_V#\に変更します。


以上で対話モード・レポートについては、複合主キーの列もレポートに表示されるようになりました。


編集アイコンをクリックしてフォームを開くと、表示されるのはPart Nameのみです。主キーのページ・アイテムのタイプはデフォルトで非表示になります。


ページ・アイテムP7_CATEGORY_CDP5_CATEGORY_CDと同じく、ポップアップLOVとして設定します。


ページ・アイテムP7_CATEGORY_CDは主キー項目なので、ソース主キーオンセキュリティセッション・ステート保護チェックサムが必要 - セッション・レベルが設定されています。この設定はページ生成ウィザードによって行われます。ページ・アイテムP7_PART_IDも同様の設定になっています。


初期化フォームのプロセスは主キーがオンになっているページ・アイテムの値を、一意検索の条件とします。そのため複合主キーを構成する列に対応するページ・アイテムは、主キーオンである必要があります。


ページ・アイテムP7_PART_IDも表示されるよう、タイプテキスト・フィールドに変更します。ラベルPart Idとし、検証必須の値オンにします。


以上の変更で、ROWIDを使ったメンテナンス画面とほぼ同様な画面が作成できました。

ただし、主キー列の指定が異なるため、ROWIDを使った場合と複合主キーを使った場合では、特に更新処理で発生するエラーが異なります。主キーをオンにしたページ・アイテムの値は変更できません。ページ・アイテムとして変更ができても、更新時にエラーが発生するか更新処理が空振りします。

すでに主キーが登録済みの行がある場合は、エラーが発生します。


これは行の自動処理のプロセスの失われた更新の防止によって保護されています。逆に言うと、これをオフにすると既存の行が上書きできてしまいます。


主キーが存在しない場合は、更新処理が空振りします。これはUPDATE文に以下のような条件句がついているためです。対象行が存在しないため、更新処理は発生しません。

WHERE CATEGORY_CD = :P7_CATEGORY_CD and PART_ID = :P7_PART_ID

主キーがROWIDのときは、ROWIDが条件句に現れるため、列CATEGORY_CDおよびPART_IDの更新は可能です。

今回の記事は以上です。

説明に使用したAPEXアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/sample-composite-key-form.zip

Oracle APEXのアプリケーション作成の参考になれば幸いです。