応用情報技術者 2022年 春期 午後 問06
クーポン発行サービスに関する次の記述を読んで、設問1~4に答えよ。
K社は、インターネットでホテル、旅館及びレストラン(以下、施設という)の予約を取り扱う施設予約サービスを運営している。施設は複数の利用プランを提供していて、利用者はその中から好みのプランを選んで予約する。会員向けサービスの拡充施策として、現在稼働している施設予約サービスに加え、クーポン発行サービスを開始することにした。
発行するクーポンには割引金額が設定されていて、施設予約の際に料金の割引に利用することができる。K社は、施設、又は都道府県、若しくは市区町村を提携スポンサーとして、提携スポンサーと合意した割引金額、枚数のクーポンを発行する。
クーポン発行に関しては、提携スポンサーによって各種制限が設けられているので、クーポンの獲得、及びクーポンを利用した予約の際に、制限が満たされていることをチェックする仕組みを用意する。
提携スポンサーによって任意に設定可能なチェック仕様の一部を表1に、クーポン発行サービスの概要を表2に示す。


〔クーポン発行サービスと施設予約サービスのE-R図〕
クーポン発行サービスと施設予約サービスで使用するデータベース(以下、予約サイトデータベースという)のE-R図(抜粋)を図1に示す。予約サイトデータベースでは、E-R図のエンティティ名をテーブル名に、属性名を列名にして、適切なデータ型で表定義した関係データベースによってデータを管理する。
クーポン管理テーブルの列名の先頭に“獲得制限”又は“予約制限”が付く列は、クーポンの獲得制限、又はクーポンを利用した予約制限のチェック処理で使用し、チェックが必要ない場合にはNULLを設定する。“獲得制限_1枚限り”には、“何一会員1枚限りの獲得制限”のチェックが必要なときは 'Y' を、不要なときはNULLを設定する。

データベース設計者であるI主任は、“何一会員1枚限りの獲得制限”を制約として実装するために、図2のSQL文によってクーポン明細テーブルに対して、UNIQUE 制約を付けた。なお、予約サイトデータベースにおいては、UNIQUE 制約を構成する複数の列で一つの列でも NULL の場合は、UNIQUE 制約違反とならない。

L主任は、①予約テーブルの“クーポンコード”、“クーポン発行連番”に対しても、UNIQUE 制約を付けた。
予約サイトデータベースでは、更新目的の参照処理と更新処理においてレコード単位にロックを掛け、多重処理を行う設定としている。ロックが掛かるとトランザクションが終了するまでの間、他のトランザクションによる同一レコードに対する処理はロック解除待ちとなる。
〔クーポン獲得処理の連番管理方式〕
クーポン発行サービスと施設予約サービスの CRUD 図〔抜粋〕を図3に示す。
クーポン新規登録処理では、1種類のクーポンにつき1レコードをクーポン管理テーブルに追加する。クーポン獲得サービス取得処理では、獲得可能期間、会員住所による獲得制限、発行上限枚数に関するチェックを行う。チェックの結果、エラーがない場合に表示される同意ボタンを押すことによって、クーポン獲得処理を行う。

クーポン発行サービスでは、上限の定められた発行枚数分のクーポンを抜けや重複なく連番管理する方式が必要になる。特に、提携スポンサーが都道府県、市区町村であるクーポンは割引金額が大きく、クーポンの発行直後にトラフィックが集中することが予想される。発行上限枚数到達後にクーポン獲得処理が動作する場合の考慮も必要である。L主任は、トラフィック集中時のリソース競合によるレスポンス悪化を懸念して、ロック解放待ちを発生させない運営管理方式(以下、ロックなし方式という)のSQL文(図4)を考案した。このSQL文では、ロックを掛けずに参照し、主キー制約によってクーポン発行連番の重複レコード作成を防止する。
ここで、関数COALESCE(A, B)は、AがNULLでないときはAを、AがNULLのときはBを返す。また、“:クーポンコード”、“:会員コード”は、該当の値を格納する埋込変数である。

〔クーポン獲得処理の連番管理方式の見直し〕
ロックなし方式をレビューしたM課長は、トラフィック集中時に主キー制約違反が発生することによって、会員による再オペレーションが頻発するデメリットを指摘し、ロック解放待ちを発生させることによって更新が順次行われる運営管理方式(以下、ロックあり方式という)の検討と方式の比較、高負荷試験の実施を指示した。L主任は、クーポン管理テーブルに対して初期値が0の“発行済枚数”という列を追加し、このデータ項目のカウントアップによって連番管理をするロックあり方式のSQL文(図5)を考案した。

④ロックあり方式では、図3の CRUD 図の一部に変更が発生する。
L主任は、ロックなし方式とロックあり方式の比較を表3にまとめ、高負荷試験を実施した。

高負荷試験実施の結果、どちらの方式でも最大トラフィック発生時のレスポンス、スループットが規定値以内に収まることが確認できた。そこで、会員による再オペレーションの発生しないロックあり方式を採用することにした。
設問1:〔クーポン発行サービスと施設予約サービスのE-R図〕について、(1)~(3)に答えよ。
(1)図1中のa~cに入れる適切なエンティティ間の関連及び属性名を答え,E-R図を完成させよ。
なお、エンティティ間の関連及び属性名の表記は、図1の凡例及び注記に倣うこと(aとbは順不同)。
模範解答
a:施設コード
b:プランコード
c:↑
解説
解答の論理構成
- 予約は “施設” が提供する “利用プラン” を選択して成立する
【問題文】には「施設は複数の利用プランを提供していて、利用者はその中から好みのプランを選んで予約する。」とあります。したがって予約レコードは、どの “施設” のどの “利用プラン” を選んだかを示す列を必ず持つ必要があります。 - “利用プラン” の主キーは “施設コード” と “プランコード” の複合キー
1件のプランを一意に特定するには “施設コード” と “プランコード” の組が不可欠です。よって予約側には両方を外部キーとして格納するのが自然です。 - 以上より、予約テーブルの [a] 列は “施設コード”, [b] 列は “プランコード”
- 関連の多重度
一つの “利用プラン” に対して予約は多数発生し得ますが、1件の予約が複数のプランを同時に指すことはありません。つまり “利用プラン” 1 - N “予約” です。E-R図の凡例に従い、矢印で「1対多」を表すため [c] には該当する矢印(↑)を記述します。
誤りやすいポイント
- “プランコード” だけを外部キーにしてしまう
“プランコード” は施設横断で一意とは限りません。必ず “施設コード” と組み合わせて複合キーにする必要があります。 - “施設コード” は “利用プラン” に含まれているから予約には不要と考えるミス
“利用プラン” への参照を張るなら、複合キーの両方を外部キーとして持たなければ参照整合性が保てません。 - 多重度を 1対1 と誤解する
プランを予約するたびに新しい予約レコードが追加されるため、多重度は 1対多です。
FAQ
Q: “予約” から “施設” を直接参照してはいけないのですか?
A: “利用プラン” が “施設” を内包しているため、予約→利用プラン→施設 という参照で施設を特定できます。冗長な重複を避け、正規化の観点でも推奨されません。
A: “利用プラン” が “施設” を内包しているため、予約→利用プラン→施設 という参照で施設を特定できます。冗長な重複を避け、正規化の観点でも推奨されません。
Q: 多重度を示す矢印の方向に決まりはありますか?
A: 本問題では凡例で示された記法に従います。1側(親)から N側(子)へ矢印を描くよう指定されています。
A: 本問題では凡例で示された記法に従います。1側(親)から N側(子)へ矢印を描くよう指定されています。
Q: 予約キャンセル時に “利用プラン” 側を更新する必要はありますか?
A: 発生数をカウントするような列を設けていない限り、キャンセルしても “利用プラン” のレコード自体は変更しません。
A: 発生数をカウントするような列を設けていない限り、キャンセルしても “利用プラン” のレコード自体は変更しません。
関連キーワード: E-R図, 外部キー, 複合キー, 1対多
設問1:〔クーポン発行サービスと施設予約サービスのE-R図〕について、(1)~(3)に答えよ。
(2)図2中のdに入れる適切な字句を答えよ。
模範解答
d:ALTER TABLE
解説
解答の論理構成
- 【問題文】には、図2のSQL文が
「d クーポン明細 ADD CONSTRAINT クーポン明細_IX1 UNIQUE(クーポンコード、獲得会員コード、獲得制限_1枚限り)」
と示されています。 - また、同じ段落で
「図2のSQL文によってクーポン明細テーブルに対して、UNIQUE 制約を付けた」
と記述されています。UNIQUE 制約を“既存”テーブルに追加する標準SQL文は
ALTER TABLE <表名> ADD CONSTRAINT <制約名> UNIQUE (列, …)
であることは基本事項です。 - 従って、d に入る字句は ALTER TABLE でなければ文法が成立しません。
➡ 結論:d= ALTER TABLE
誤りやすいポイント
- CREATE TABLE と混同して「CREATE」から書き始めてしまう。今回は既存表への制約追加なので CREATE ではありません。
- 「ADD UNIQUE …」だけで始めるミス。表名の前に ALTER TABLE が必須です。
- UNIQUE 制約名を付与する構文を忘れ、「ADD UNIQUE …」の直後に列名を書いてしまうケース。
FAQ
Q: UNIQUE 制約と PRIMARY KEY の違いは何ですか?
A: 両者とも列値の重複を禁止しますが、PRIMARY KEY は NULL を許容しません。UNIQUE は列のいずれかが NULL なら制約対象外である点が異なります。
A: 両者とも列値の重複を禁止しますが、PRIMARY KEY は NULL を許容しません。UNIQUE は列のいずれかが NULL なら制約対象外である点が異なります。
Q: 既に大量データがある表に ALTER TABLE で制約を追加するとパフォーマンスに影響しますか?
A: 追加時に全件スキャンが行われ、該当列のインデックスも生成されるため一時的に負荷が高まります。運用時間帯を考慮して実行するのが安全です。
A: 追加時に全件スキャンが行われ、該当列のインデックスも生成されるため一時的に負荷が高まります。運用時間帯を考慮して実行するのが安全です。
Q: 制約名「クーポン明細_IX1」のように命名するメリットは?
A: テーブル名+接尾辞(IX1など)で一意に識別でき、障害解析やデータディクショナリ参照時に由来が判別しやすくなります。
A: テーブル名+接尾辞(IX1など)で一意に識別でき、障害解析やデータディクショナリ参照時に由来が判別しやすくなります。
関連キーワード: ALTER TABLE, UNIQUE制約, 制約追加, データベース設計
設問1:〔クーポン発行サービスと施設予約サービスのE-R図〕について、(1)~(3)に答えよ。
(3)本文中の下線①は、どのような業務要件を実現するために行ったものか。30字以内で述べよ。
模範解答
1枚のクーポンは一つの予約だけに利用できる。
解説
解答の論理構成
- 【問題文】には「L 主任は、①予約テーブルの“クーポンコード”、“クーポン発行連番”に対しても、UNIQUE 制約を付けた」とあります。
UNIQUE 制約は、指定した列の組合せが重複する行の登録を禁止します。 - 【問題文】表2「クーポンの利用」欄には、「・1枚のクーポンは一つの予約だけ利用できる。」と明記されています。
つまり、同一の“クーポンコード”と“クーポン発行連番”の組合せは、複数の予約に現れてはなりません。 - 予約テーブル側でこの二つの列に UNIQUE 制約を施せば、あるクーポンが既に使われている状態で別予約へ再利用しようとした際に制約違反が発生し、業務要件をシステム的に担保できます。
- したがって、下線①の目的は「1枚のクーポンは一つの予約だけに利用できる。」という要件の実現です。
誤りやすいポイント
- 「同一会員1枚限りの獲得制限」や「発行上限枚数の管理」と取り違え、予約テーブルに UNIQUE 制約を付ける理由を誤解する。
- UNIQUE を主キーと混同し、NULL 値の扱い(【問題文】では「一つの列でも NULL の場合は、UNIQUE 制約違反とならない」)を見落とす。
- 「キャンセル後は再利用できる」仕様に惑わされ、キャンセル時のクーポン明細更新と予約側 UNIQUE 制約の関係を想像しすぎて本質を外す。
FAQ
Q: キャンセルすると「クーポンを別の予約に利用できる」とありますが、UNIQUE 制約と矛盾しませんか?
A: キャンセル処理では当該予約レコードの“クーポンコード”“クーポン発行連番”を NULL に更新(又は予約行自体を削除)してから再予約します。NULL であれば UNIQUE 制約に抵触しません。
A: キャンセル処理では当該予約レコードの“クーポンコード”“クーポン発行連番”を NULL に更新(又は予約行自体を削除)してから再予約します。NULL であれば UNIQUE 制約に抵触しません。
Q: クーポン明細テーブルにも“クーポンコード”“クーポン発行連番”があります。なぜ予約テーブル側でも UNIQUE にするのですか?
A: クーポン明細は「発行」を管理し、予約テーブルは「利用」を管理します。同一クーポンの多重利用を防ぐ責務は予約テーブルにあるため、そこで一意性を保証します。
A: クーポン明細は「発行」を管理し、予約テーブルは「利用」を管理します。同一クーポンの多重利用を防ぐ責務は予約テーブルにあるため、そこで一意性を保証します。
Q: UNIQUE 制約の代わりにアプリケーションでチェックしても良いですか?
A: 可能ですが、トランザクション境界を跨ぐと競合が起きやすく、DB 制約の方が確実かつ保守性に優れています。
A: 可能ですが、トランザクション境界を跨ぐと競合が起きやすく、DB 制約の方が確実かつ保守性に優れています。
関連キーワード: UNIQUE制約, 一意性制約, ビジネスルール, トランザクション, データ整合性
設問2:
図4中のeに入れる適切な字句を答えよ。
模範解答
e:クーポン発行連番
解説
解答の論理構成
-
副問合せの目的を確認
図4の SQL では、まず
sql WITH 発行済枚数取得 AS ( SELECT COALESCE(MAX(e), 0) AS 発行済枚数 FROM クーポン明細 WHERE クーポンコード = :クーポンコード)として、ある “:クーポンコード” について既に発行されている枚数の最大値を取得し、次の発行連番を算出しています。
ここで必要なのは「そのクーポンで現在付与されている最大の連番」です。 -
どの列が “連番” かを確定
問題文には
・「このSQL文では…主キー制約によってクーポン発行連番の重複レコード作成を防止する。」
・「L 主任は、予約テーブルの“クーポンコード”、“クーポン発行連番”に対しても、UNIQUE 制約を付けた。」
とあり、“クーポン発行連番” が連番を示す列であることが明言されています。 -
MAX 関数の対象列は “クーポン発行連番”
したがって e には、連番を表す列名 “クーポン発行連番” を入れることで、
「該当クーポンの発行連番の最大値 → 発行済枚数」
という処理意図が正しく実装できます。 -
結論
e には “クーポン発行連番” が入るため、模範解答
e:クーポン発行連番
が成立します。
誤りやすいポイント
- 発行済枚数という別名に引きずられ “e = 発行済枚数” としてしまう。列ではなく集計結果の別名なので誤りです。
- COUNT(*) を使うと 0 から始まる連番にならず、途中欠番が出た場合に整合しません。MAX で最新値を取る必要があります。
- “クーポンコード” など主キー列と勘違いして入れるケース。MAX を取っても意味がなく、論理破綻します。
FAQ
Q: COALESCE を使う理由は何ですか?
A: 初回発行時は MAX(クーポン発行連番) が NULL になるため、COALESCE(... , 0) で 0 に置き換え、0 + 1 = 1 から連番を始めるためです。
A: 初回発行時は MAX(クーポン発行連番) が NULL になるため、COALESCE(... , 0) で 0 に置き換え、0 + 1 = 1 から連番を始めるためです。
Q: COUNT では駄目なのですか?
A: COUNT は「発行済枚数」を取得できますが、欠番が生じた場合に次の連番がずれます。MAX は常に「現在の最大値」を返すので連番が一意になります。
A: COUNT は「発行済枚数」を取得できますが、欠番が生じた場合に次の連番がずれます。MAX は常に「現在の最大値」を返すので連番が一意になります。
Q: 主キー制約違反が起きる原因は?
A: 高負荷で同時に挿入が走り、MAX が同じ値を返すと同じ “クーポン発行連番” で INSERT されるためです。ロックなし方式ではこの競合を主キー制約が検知し、エラーになります。
A: 高負荷で同時に挿入が走り、MAX が同じ値を返すと同じ “クーポン発行連番” で INSERT されるためです。ロックなし方式ではこの競合を主キー制約が検知し、エラーになります。
関連キーワード: UNIQUE制約, 主キー, COALESCE, 同時実行制御, 集計関数
設問3:
図5中のf、gに入れる適切な字句を答えよ。
模範解答
f:SET 発行済枚数 = 発行済枚数 + 1
g:発行上限枚数
解説
解答の論理構成
-
まず、図5の1行目のSQLはクーポン管理テーブルで発行枚数をインクリメントする更新処理です。問題文には
“L主任は、クーポン管理テーブルに対して初期値が0の“発行済枚数”という列を追加し、このデータ項目のカウントアップによって連番管理をするロックあり方式のSQL文(図5)を考案した。”
とあります。したがって f には “発行済枚数” を “+1” する処理が入る必要があります。SQLの一般形は
sql UPDATE テーブル名 SET 列名 = 列名 + 1なので f には
“SET 発行済枚数 = 発行済枚数 + 1”
が入ります。 -
次に WHERE 句の比較対象 g です。図5の UPDATE 文は “発行済枚数 < g” のときのみ更新を行います。問題文の表3でもロックあり方式について
“発行上限枚数に到達後の動作” 欄に “クーポン明細テーブルのクーポン発行連番が g のレコードを追加しようとして、主キー制約違反となる。”
と記載されています。ここで “g に図5中の g と同じ字句が入る” と注記されています。
更新の条件は “発行上限枚数” に達していないかどうかの判定なので、 g は “発行上限枚数” であると確定します。 -
以上より
f:SET 発行済枚数 = 発行済枚数 + 1
g:発行上限枚数
が正答となります。
誤りやすいポイント
- 発行済枚数をインクリメントする位置を誤り、INSERT 文側に書いてしまう。
- g を “発行済枚数” と書き換えてしまい、更新条件が常に偽になるため予約が取れなくなる。
- 図4(ロックなし方式)の副問合せロジックと混同し、COALESCE や MAX をそのまま使おうとする。
- 主キー制約違反の原因が INSERT 文ではなく UPDATE 失敗後の INSERT にある点を読み落とす。
FAQ
Q: “SET 発行済枚数 = 発行済枚数 + 1” では同時実行時に値が飛ぶ心配はありませんか?
A: 図5はロックあり方式です。更新対象レコードをロックしたうえで順番にトランザクションが実行されるので、値飛びは発生しません。
A: 図5はロックあり方式です。更新対象レコードをロックしたうえで順番にトランザクションが実行されるので、値飛びは発生しません。
Q: “発行上限枚数” を超えた場合はどの段階でエラーになりますか?
A: UPDATE 文が条件を満たさず 0 件更新となり、その後の INSERT が主キー重複で失敗します。これにより発行済枚数は超過せず整合性が保たれます。
A: UPDATE 文が条件を満たさず 0 件更新となり、その後の INSERT が主キー重複で失敗します。これにより発行済枚数は超過せず整合性が保たれます。
Q: UNIQUE 制約とロックのどちらを優先して設計すべきですか?
A: 高トラフィック時の再試行コストやユーザ体験を重視するならロックあり方式、スループット最重視ならロックなし方式など、目的に合わせて選択します。今回の問題では再オペレーション防止を理由にロックあり方式が採用されています。
A: 高トラフィック時の再試行コストやユーザ体験を重視するならロックあり方式、スループット最重視ならロックなし方式など、目的に合わせて選択します。今回の問題では再オペレーション防止を理由にロックあり方式が採用されています。
関連キーワード: UNIQUE 制約, レコードロック, カウンタ更新, 楽観制御, トランザクション
設問4:
本文中の下線②について、図3中の下線②、下線③の変更後のレコード操作内容を、注記に従いそれぞれ答えよ。
模範解答
下線②:RU
下線③:C
解説
解答の論理構成
-
「④ロックあり方式では、図 3 の CRUD 図の一部に変更が発生する」と記載されています。
-
ロックあり方式の具体的な SQL は図5です。
sql UPDATE クーポン管理 f WHERE クーポンコード = :クーポンコード AND 発行済枚数 < g ; INSERT INTO クーポン明細 (クーポンコード, クーポン発行連番, 獲得会員コード, 獲得制限_1枚限り) SELECT :クーポンコード, 発行済枚数, :会員コード, 獲得制限_1枚限り FROM クーポン管理 WHERE クーポンコード = :クーポンコード; -
第1文で《UPDATE クーポン管理》を実行しているため、クーポン管理テーブルには
・SELECT で値を取得(Read)
・UPDATE で値を更新(Update)
の両方が必要です。従って CRUD 区分は “RU” となります。 -
第2文は《INSERT INTO クーポン明細》のみで、クーポン明細テーブルに対して Read は行っていません。従って CRUD 区分は “C” です。
-
以上より、図3の下線②は「RU」、下線③は「C」となります。
誤りやすいポイント
- 「SELECT … FROM クーポン管理」を INSERT 文の中で使っているため、クーポン明細でも Read があると誤認しがちですが、実際に Read しているのはクーポン管理です。
- UPDATE が追加されると “U” だけと思い込み、“R” を落とすケースが多いです。SQL が現在値を参照してから更新している点を見落とさないようにしましょう。
- CRUD の記号は並び順に意味はありませんが、回答例では “RU” の順で示されています。複数操作があるときは両方書く必要があります。
FAQ
Q: 「INSERT … SELECT …」に Read は含めなくて良いのですか?
A: Read は SELECT 先のテーブルに対してカウントします。今回 SELECT しているのはクーポン管理テーブルなので、クーポン明細には Read は発生しません。
A: Read は SELECT 先のテーブルに対してカウントします。今回 SELECT しているのはクーポン管理テーブルなので、クーポン明細には Read は発生しません。
Q: UPDATE だけなら “U” では?
A: 発行済枚数を取得するために同じ UPDATE 文の中で現在値を参照しているため Read が伴います。結果として “R” と “U” の両方が必要になります。
A: 発行済枚数を取得するために同じ UPDATE 文の中で現在値を参照しているため Read が伴います。結果として “R” と “U” の両方が必要になります。
Q: もとのロックなし方式では CRUD が変わらないのですか?
A: ロックなし方式(図4)は クーポン管理 を Read のみ、クーポン明細 を Create + Read(CR)としていました。ロックあり方式では UPDATE が追加されたため CRUD が変わります。
A: ロックなし方式(図4)は クーポン管理 を Read のみ、クーポン明細 を Create + Read(CR)としていました。ロックあり方式では UPDATE が追加されたため CRUD が変わります。
関連キーワード: 一意性制約, レコードロック, CRUD, 競合制御, INSERT-SELECT


