応用情報技術者 2020年 秋期 午後 問06
宿泊施設の予約を行うシステムに関する次の記述を読んで、設問1~3に答えよ。
U社は、旅館や民宿などの宿泊施設の宿泊予約を行うWebシステム(以下、予約システムという)を開発している。予約システムの主な要件を図1に示す。

〔データベースの設計〕
予約システムを開発するに当たり、データベースの設計を行った。データベースのE-R図を図2に示す。

このデータベースでは、E-R図のエンティティ名を表名にし、属性名を列名にして、適切なデータ型で表定義した関係データベースによって、データを管理する。部屋IDは、全施設を通して一意な値である。また、予約ID、予約明細IDは、レコードを挿入した順に値が大きくなる。
〔部屋の予約の流れ〕
部屋の予約は、部屋の空き状況の確認と、予約確定の二つの処理から成る。部屋を予約する際には、希望した施設、部屋の種別、チェックイン日付、チェックアウト日付、部屋数について、空き状況の照会を行う。照会の結果、部屋に空きがあった場合は、予約手続の画面を表示する。部屋に空きがなかった場合は、部屋が空いていない旨を画面に表示し、空き部屋照会のための条件入力の画面に戻って条件を変更するよう促す。
部屋の空き状況の確認を行うためのSQL文を図3に示す。予約する部屋の施設ID、部屋種別ID、チェックイン日付、チェックアウト日付及び部屋数は、埋込み変数“:施設ID”、“:部屋種別ID”、“:チェックイン日付”、“:チェックアウト日付”及び“:部屋数”に設定されている。

〔部屋の空き状況の確認の処理〕
予約システムは、図3のSQL文の検索結果として、レコードが返された場合に予約可能であると判定し、予約手続の画面を表示する。レコードが返されなかった場合は、部屋が空いていない旨を画面に表示する。空き状況確認の処理の流れを図4に示す。

〔予約確定の処理〕
予約手続の画面が表示された後、利用者は予約の確定の操作を行うことで部屋の予約を確定させる。予約の確定の処理では、予約のレコードを挿入した後、各宿泊日について、予約明細に必要な部屋数分のレコードを挿入する。
予約手続の画面が表示されてから、利用者が予約の確定の操作を行うまでの間に、他の利用者が先に予約を確定してしまうこともある。そこで、予約確定の処理では、レコードの挿入の前に図3のSQL文を再度実行し、まだ予約可能な状態であるかを確認してから挿入を行う。予約確定の処理の流れを図5に示す。

〔不具合の報告と対応〕
予約システムのテスト中に、同じ宿泊日の同じ部屋について、予約明細のレコードが重複して挿入されてしまう不具合が報告された。報告された事象について確認すると、別々の利用者が同じ時刻に予約確定の操作を行った際に発生していた。
そこで、今後同じ宿泊日の同じ部屋の予約が重複して入らないようにするために、予約明細テーブルのe列とf列の複合キーに対して制約を追加することにした。このような制約のことを、gという。
gを追加するためには、既に重複して挿入されてしまったレコードを削除する必要がある。削除に当たっては、同じ宿泊日の同じ部屋の予約が重複した予約明細のレコードについて、最初に挿入された予約のレコードと、それに紐づく予約明細のレコードを残し、それ以外の予約明細、予約のレコードを削除することにした。
予約明細について、削除するレコードを抽出するSQL文を図6に示す。図6で得られた該当の予約明細のレコードを削除するとともに、それらに紐づく予約のレコードを削除してから、テストの作業を再開することにした。
予約明細テーブルへの制約の追加後、当該の不具合について再度テストを行ったところ、追加した制約によって、重複が発生しなくなったことが確認できた。

設問1:
図2中のa、bに入れる適切なエンティティ間の関連及び属性名を答え、E-R図を完成させよ。
なお、エンティティ間の関連及び属性名の表記は、図2の凡例及び注記に倣うこと。
模範解答
a:→
b:施設ID
解説
解答の論理構成
-
図2におけるa
- エンティティ間の関連は記号で表します。
- 予約と予約明細は「1つの予約に対し、複数行の予約明細が存在し得る」という要件から「1 対 多」です。
- 図2の凡例で「1対多」は “—→” を用いると示されています。
- よって a には “→” を入れ、「予約 —→ 予約明細」となります。
-
図2におけるb
- 部屋は必ずいずれかの施設に属します。
- 施設を識別する主キーは “施設ID” です。
- 部屋から施設を参照するための外部キーが必要であり、図2の注記で「外部キーは破線下線で表記する」とあるため、b には “施設ID” が入ります。
-
以上より模範解答と一致します。
誤りやすいポイント
- 「1対多」の向きを逆に書く
予約明細から予約へ “←—” としてしまうミスが頻発します。 - 外部キー表記の線種
実線下線は主キー、破線下線は外部キーです。“施設ID” を実線下線で書くと減点対象です。 - エンティティ間の多対多と誤認
「部屋 ←→ 部屋種別マスタ」の多対多と混同して、予約と予約明細も “←→” としてしまうケースがあります。
FAQ
Q: “予約 —→ 予約明細” がなぜ「1対多」になるのですか?
A: 1つの予約操作で泊数分の明細が発生します。例として2泊3日の予約なら「予約」は1行ですが、「予約明細」は2行(宿泊日ごと)です。これが「1対多」の根拠になります。
A: 1つの予約操作で泊数分の明細が発生します。例として2泊3日の予約なら「予約」は1行ですが、「予約明細」は2行(宿泊日ごと)です。これが「1対多」の根拠になります。
Q: “施設ID” が部屋に無くても、部屋と施設は部屋IDだけで結び付けられませんか?
A: 部屋IDは全施設で一意ですが、施設単位の検索・集計を高速に行うために部屋側にも “施設ID” を保持する正規化パターンが採用されています。
A: 部屋IDは全施設で一意ですが、施設単位の検索・集計を高速に行うために部屋側にも “施設ID” を保持する正規化パターンが採用されています。
Q: 外部キーは必ず破線下線で書かないといけませんか?
A: 図2の注記に「外部キーの破線を付ける」と明記されているため、試験では記号ルールに従わないと減点対象になります。
A: 図2の注記に「外部キーの破線を付ける」と明記されているため、試験では記号ルールに従わないと減点対象になります。
関連キーワード: 外部キー、主キー、1対多、正規化、エンティティ間関連
設問2:
図3中のc、dに入れる適切な字句を答えよ。
模範解答
c:NOT EXISTS
d:HAVING COUNT(*)
解説
解答の論理構成
-
部屋の空きを判定する要件
- 本文に「部屋を予約する際には、…チェックイン日付、チェックアウト日付、部屋数について、空き状況の照会を行う。」とあります。
- また「図3のSQL文の検索結果として、レコードが返された場合に予約可能であると判定」とあり、返るレコードは “空いている部屋種別” を示す必要があります。
-
c の導出 ―― 既存予約と重複しない部屋を抽出
- 図3の SQL では部屋表を外側に置き、「予約明細」表をサブクエリで参照しています。
- 重複がある部屋は除外するために、サブクエリに該当レコードが“存在しない”ことを条件にします。
- SQL で存在しないことを表す代表的な述語は NOT EXISTS です。
- よって c には NOT EXISTS が入ります。
-
d の導出 ―― 希望部屋数を満たすグループのみ返却
- 外側の SELECT では「施設ID, 部屋種別ID, COUNT(*)」を取得し、その直後に「GROUP BY 施設ID, 部屋種別ID」が置かれています。
- グループ化の結果に対して行数(=空いている部屋数)が “:部屋数” 以上かどうかを判定する必要があります。
- 集計後の行数に条件を付けるには HAVING 句を使います。
- したがって d に入るのは HAVING COUNT(*) です。
-
以上より
- c:NOT EXISTS
- d:HAVING COUNT(*)
誤りやすいポイント
- サブクエリを NOT IN で書こうとして NULL を含むケースを見落とす。NOT EXISTS を使うと NULL 問題を回避できます。
- HAVING を WHERE に置き換えてしまう。WHERE はグループ化前に評価されるため、COUNT(*) などの集計結果を直接使えません。
- HAVING COUNT() >= :部屋数 の COUNT() を別名で受け取り、その別名を HAVING で参照しようとしてエラーになるケース。集計関数そのものを HAVING 句に書く方が安全です。
FAQ
Q: NOT IN と NOT EXISTS のどちらを使っても良いですか?
A: NULL を含む可能性がある列を比較すると NOT IN は全行を除外してしまう危険があります。本設問では安全策として NOT EXISTS を採用します。
A: NULL を含む可能性がある列を比較すると NOT IN は全行を除外してしまう危険があります。本設問では安全策として NOT EXISTS を採用します。
Q: HAVING を使わず WHERE COUNT(*) と書くとどうなりますか?
A: WHERE 句では集計関数を直接扱えないため SQL エラーになります。集計結果に条件を付ける場合は必ず HAVING を使用してください。
A: WHERE 句では集計関数を直接扱えないため SQL エラーになります。集計結果に条件を付ける場合は必ず HAVING を使用してください。
Q: COUNT() ではなく COUNT(部屋ID) にしても良いですか?
A: 主キーである 部屋ID は NULL になり得ないので結果は同じですが、可読性と汎用性を考慮し全行を数える COUNT() が一般的です。
A: 主キーである 部屋ID は NULL になり得ないので結果は同じですが、可読性と汎用性を考慮し全行を数える COUNT() が一般的です。
関連キーワード: NOT EXISTS, HAVING句、集計関数、サブクエリ、NULL比較
設問3:〔不具合の報告と対応〕について、(1)〜(3)に答えよ。
(1)本文中のe、fに入れる適切な列名を答えよ(eとfは順不同)。
模範解答
e:宿泊日
f:部屋ID
解説
解答の論理構成
- 【問題文】に「同じ宿泊日の同じ部屋について、予約明細のレコードが重複して挿入されてしまう不具合が報告された」とあります。
└ 重複の判定条件は “同じ宿泊日” かつ “同じ部屋” であることが明示されています。 - 同じ箇所で「予約明細テーブルのe列とf列の複合キーに対して制約を追加することにした」と記述されています。
└ つまり、重複防止用のユニーク制約(複合キー)を掛ける対象列が e と f です。 - 予約明細テーブルに存在し、かつ重複判定で使われる列は “宿泊日” と “部屋ID” の二つしかありません。
└ “予約ID” や “予約明細ID” は予約ごと・明細ごとの識別子であり、同一部屋・同一宿泊日の重複を検知できません。 - 以上より、e と f には
・宿泊日
・部屋ID
を設定すれば、同一部屋・同一宿泊日に同じレコードが二度入ることをユニーク制約で防止できます。
誤りやすいポイント
- 予約IDや予約明細IDをキーに含めてしまう
→ これらはそもそも重複してはいけない主キー属性であり、今回の「部屋×宿泊日」の重複を防げません。 - チェックイン日付/チェックアウト日付を選んでしまう
→ 予約明細は宿泊日ごとに1レコードを持つ設計なので、期間情報ではなく “宿泊日” が必要です。 - キーの順序を気にし過ぎる
→ SQLレベルのユニーク制約では複合キー内の列順は一意性判定に影響せず、部屋ID→宿泊日でも宿泊日→部屋IDでも同じ効果です。
FAQ
Q: 予約確定時の排他制御は不要になりますか?
A: ユニーク制約で重複挿入は防げますが、業務的な「必要室数の確保」は依然として同時実行制御(再チェック+コミット)が必要です。
A: ユニーク制約で重複挿入は防げますが、業務的な「必要室数の確保」は依然として同時実行制御(再チェック+コミット)が必要です。
Q: 複合キーと主キーの違いは?
A: 主キーはテーブルの行を一意に識別する列(群)で NULL 不許可。複合キーに付けるユニーク制約は「重複禁止」を保証するだけで、主キーとは別に設定できます。
A: 主キーはテーブルの行を一意に識別する列(群)で NULL 不許可。複合キーに付けるユニーク制約は「重複禁止」を保証するだけで、主キーとは別に設定できます。
Q: 後からユニーク制約を追加する際の注意点は?
A: 既存データに重複があると制約追加に失敗するため、問題文のようにまず重複行を抽出・削除してから ALTER TABLE … ADD CONSTRAINT を行います。
A: 既存データに重複があると制約追加に失敗するため、問題文のようにまず重複行を抽出・削除してから ALTER TABLE … ADD CONSTRAINT を行います。
関連キーワード: ユニーク制約、複合キー、排他制御、トランザクション
設問3:〔不具合の報告と対応〕について、(1)〜(3)に答えよ。
(2)本文中のgに入れる適切な字句を答えよ。
模範解答
g:UNIQUE制約
解説
解答の論理構成
- まず問題文中に
“予約明細テーブルのe列とf列の複合キーに対して制約を追加することにした。このような制約のことを、gという。”
と明記されています。 - 目的は “同じ宿泊日の同じ部屋について、予約明細のレコードが重複して挿入されてしまう不具合” を防ぐことです。
- すなわち “e列とf列” (=部屋IDと宿泊日) の組み合わせがテーブル内で重複しないようにする必要があります。
- RDB で列または列集合の重複を禁止する制約は “UNIQUE制約” で、主キー以外の列にも設定できます。
- よって “g” に入る適切な語句は “UNIQUE制約” となります。
誤りやすいポイント
- 主キーと混同する
UNIQUE制約は重複禁止ですが NULL を許容でき、主キーとは異なるという点を忘れがちです。 - CHECK制約との取り違え
CHECK は値の範囲や論理式を検証するだけで、行間の重複は検出できません。 - インデックスと制約の違い
UNIQUEインデックスと結果的に同じ動作になる DB もありますが、問題では “制約” と明言されているため UNIQUE制約と答える必要があります。
FAQ
Q: 部屋IDと宿泊日を主キーにすれば良いのでは?
A: 主キーには既に “予約明細ID” が設定されているため重複防止だけを目的に主キーを変更すると設計全体に影響します。そこで既存の主キーはそのままに、重複禁止のための UNIQUE制約を追加するのが妥当です。
A: 主キーには既に “予約明細ID” が設定されているため重複防止だけを目的に主キーを変更すると設計全体に影響します。そこで既存の主キーはそのままに、重複禁止のための UNIQUE制約を追加するのが妥当です。
Q: UNIQUE制約を追加する前に重複行を削除しないといけないのはなぜ?
A: 既に重複が存在する状態で UNIQUE制約を張ると整合性が取れずエラーになるため、先に重複行を削除しておく必要があります。
A: 既に重複が存在する状態で UNIQUE制約を張ると整合性が取れずエラーになるため、先に重複行を削除しておく必要があります。
Q: UNIQUE制約と一緒に排他ロックを使うべき?
A: 挿入競合をトランザクション制御で防御する方法もありますが、制約でDBレベルの整合性を担保しておけばアプリ側でロックを細かく制御しなくても重複は発生しません。
A: 挿入競合をトランザクション制御で防御する方法もありますが、制約でDBレベルの整合性を担保しておけばアプリ側でロックを細かく制御しなくても重複は発生しません。
関連キーワード: UNIQUE制約、重複禁止、複合キー、データベース制約、整合性保持
設問3:〔不具合の報告と対応〕について、(1)〜(3)に答えよ。
(3)図6中のh〜jに入れる適切な字句を答えよ(iとjは順不同)。
模範解答
h:MIN(t2.予約ID)
i:t1.部屋ID = t2.部屋ID
j:t1.宿泊日 = t2.宿泊日
解説
解答の論理構成
-
目的の整理
不具合修正では、 「同じ宿泊日の同じ部屋の予約明細のレコードについて、最初に挿入された予約のレコードと、それに紐づく予約明細のレコードを残し、それ以外の予約明細、予約のレコードを削除する」
ことが求められています。 -
“最初に挿入された” かどうかの判定軸
問題文には「予約ID、予約明細IDは、レコードを挿入した順に値が大きくなる。」とあります。
したがって、同一の部屋・宿泊日で最小の 予約ID が “最初の予約” になります。 -
取得したいレコード
・外側の t1 は “削除候補” を示します。
・内側の副問い合わせ t2 は “同じ部屋・同じ宿泊日の中で最小(=先頭)の予約ID” を返します。
・t1.予約ID > (SELECT …) という比較で、先頭レコードより後に挿入された重複分だけを抽出します。 -
字句の対応
(1) h は、同じグループ内で最小の 予約ID を取得する集約関数 ― MIN(t2.予約ID)
(2) i と j は “同じ部屋” と “同じ宿泊日” を示す等価条件
・t1.部屋ID = t2.部屋ID
・t1.宿泊日 = t2.宿泊日
順不同なので i・j いずれに置いても成立します。 -
結論
h:MIN(t2.予約ID)
i:t1.部屋ID = t2.部屋ID
j:t1.宿泊日 = t2.宿泊日
誤りやすいポイント
- “最初に挿入” を 予約明細ID で比較してしまう
→ 要件では “予約IDで判断する” と明示されています。 - 集約関数を MIN(*) や MIN(予約明細ID) にしてしまう
→ 副問い合わせは先頭の予約を探すので、対象列は必ず 予約ID です。 - 等価条件を >= や > に書き換えてしまう
→ グループ化のキーは完全一致でなければ同一レコードとみなせません。
FAQ
Q: MIN ではなく MAX を使ってはいけないのですか?
A: 先に挿入されたレコードを残し、それ以外を削除するため “最小” の 予約ID を求める必要があります。MAX にすると最後に挿入された予約が残ってしまいます。
A: 先に挿入されたレコードを残し、それ以外を削除するため “最小” の 予約ID を求める必要があります。MAX にすると最後に挿入された予約が残ってしまいます。
Q: 副問い合わせの t2 を EXISTS で書き換えることはできますか?
A: 重複行を1行だけ残す要件上、MIN(t2.予約ID) のような集約が必要です。EXISTS 単独では “最小値” を取得できないため、本問の目的を満たせません。
A: 重複行を1行だけ残す要件上、MIN(t2.予約ID) のような集約が必要です。EXISTS 単独では “最小値” を取得できないため、本問の目的を満たせません。
Q: 宿泊日 と 部屋ID のどちらをキーにすべきか迷います。
A: 不具合の原因は「同じ宿泊日の同じ部屋」に対して重複が入ることです。したがって両方を等価条件に含めて重複グループを正しく特定します。
A: 不具合の原因は「同じ宿泊日の同じ部屋」に対して重複が入ることです。したがって両方を等価条件に含めて重複グループを正しく特定します。
関連キーワード: 集約関数、サブクエリ、複合一意制約、データ整合性、重複排除


