【初心者向け解説】電脳少女プログラミング2088 巨大コーポの最上階（Aランク）解いてみた

# はじめに

今回は、こちらの[プログラミングゲームの解答コードを投稿しよう！](https://qiita.com/official-events/efe957f155af900604b3)の公式イベントの記事になります。

具体的には、[paizaの新作プログラミングゲーム「電脳少女プログラミング2088 ─壊レタ君を再構築─」](https://ppg.paiza.jp/denno2088)の「巨大コーポの最上階」というSQLの問題の解答例とその解説を紹介していきたいと思います。

こちらはpaizaのランクA相当とのことです。

# 問題 (2025年1月25日時点)

```text
あなたは軍の機密情報へアクセスできる端末を見つけました。
そこから重要なデータだけを得ようとしています。

log (ログ), memory (記憶), battle (戦闘), category (カテゴリ), person (人物) のテーブルが以下の図のように与えられます

person (人物) のうち重要度が 5 の人物の
person.deleted_at と同じ battle (戦闘) の battle.created_at (戦闘.作成日) から
memory (記憶) と battle (戦闘) と person (人物) の 
memory.id (記憶.ID), memory.talk (記憶.会話内容), person.name (人物.人物名), battle.created_at (戦闘.作成日) を
出力してください。
```

![image_sql_er5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1655179/71b7ed56-d3cd-96ca-8bff-6d872e9ffa39.png)

# 解答例

私は以下のSQLを作成し、提出しました。
これでOKでした！:tada:

```sql
-- Let's チャレンジ！！
SELECT 
    memory.id
    , memory.talk
    , person.name
    , battle.created_at
FROM person
INNER JOIN battle
ON person.id = battle.person_id
AND person.deleted_at = battle.created_at
INNER JOIN log
ON person.id = log.person_id
INNER JOIN memory
ON log.memory_id = memory.id
WHERE person.importance = 5;
```

# 解説

ここから私がどのように考えたか解説していきます。

## 1.出力対象のテーブルを確認する

問題文の最後に
> memory (記憶) と battle (戦闘) と person (人物) の (中略)を出力してください。

とあります。
つまり、`memory`/`battle`/`person`テーブルを利用することが伺えます。

RDBでは、テーブル間を関連づける設定をする列(外部キー)があります。
今回は、問題にあるテーブルの図から、
**`person`テーブルの`id`と`battle`テーブルの`person_id`が関連づけられている**
ということがすぐにわかるかと思います。

しかし、<font color="red">`memory`テーブルは`person`にも`battle`にも外部キーの設定がありません。</font>

さあ、困った...:sweat:
...
.....

と、焦る必要はありません！


よく図を確認すると、
`person`(もしくは`battle`)と`memory`の間に`log`というテーブルがあり、
`log`を経由すると、`person`と`memory`が関連づけられていることがわかります。

![image_sql_er5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1655179/d25a24b5-f1fd-a6d6-6b32-46bc9609fc64.png)

つまり、
今回は、<font color="red">`memory`/`battle`/`person`テーブルに、関連付けのために`log`テーブルを加えた4テーブルを参照すれば良い</font>ことがわかります。

## 2.personテーブルのSELECT文の作成

さて、もう一度問題文に戻ってみると、

> person (人物) のうち重要度が 5 の人物(略)

> person.deleted_at と同じ(略)

と`person`テーブルについて触れた記述が多いので、**とりあえず`person`テーブルのSELECT文を書いておきましょう。

```sql
SELECT *
FROM person
;
```

これは流石にSQLを触ったことがあれば誰でも書けるかと思います。

## 3.personテーブルに関する条件を加えてみる

次に条件を見ていきます。

[2.personテーブルのSELECT文の作成](#2.personテーブルのSELECT文の作成)でも触れましたが、問題文の中に

> person (人物) のうち重要度が 5 の人物

とあります。
また、`person`テーブルには`importance`という「重要度」を指すint型のカラムが存在することが、テーブルの図からわかります。
これを先ほどのSQLに反映していきます。

```diff_sql
SELECT *
FROM person
+ WHERE person.importance = 5
;
```

## 4.必要なテーブルのJOIN

問題文にはさらに`battle`テーブルに関する条件が記載されていますが、一旦[1.出力対象のテーブルを確認する](#1.出力対象のテーブルを確認する)で判明した必要なテーブルをJOINしていきます。

今回は、`Null`の値に関する記述等がないので、基本的に`Null`の情報を除去できる`INNER JOIN`を利用して良いかと思います。

再掲になりますが、以下の線で示したように

- [`person`テーブルの`id`]と[`battle`テーブルの`person_id`]
- [`person`(もしくは`battle`)テーブルの`id`]と[`log`テーブルの`person_id`]
- [`person`テーブルの`id`]と[`log`テーブルの`person_id`]
- [`log`テーブルの`memory_id`]と[`memory`テーブルの`id`]

が、外部キーの組み合わせとして考えられます。

![image_sql_er5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1655179/d25a24b5-f1fd-a6d6-6b32-46bc9609fc64.png)

一気にお見せすると、おおっ:tired_face:っと圧倒される方もいるかもしれないので、まずは、`person`テーブルと`battle`テーブルを結合してみましょう。

```diff_sql
SELECT *
FROM person
+ INNER JOIN battle
+ ON person.id = battle.person_id
WHERE person.importance = 5
;
```

このようになります。

:::note info

- `INNER JOIN` + `JOINしたいテーブル名`
- `ON`句にJOINする条件

以上でテーブルを結合することが可能です。
:::

さらに他のテーブルを結合すると以下の通りです。

```diff_sql
SELECT *
FROM person
INNER JOIN battle
ON person.id = battle.person_id
+ INNER JOIN log
+ ON person.id = log.person_id -- person.idはbattle.person_idでも可
+ INNER JOIN memory
+ ON log.memory_id = memory.id
WHERE person.importance = 5
;
```

## 5.battleテーブルの条件の追加

問題文にはさらに`battle`テーブルに関する条件が記載されています。

> person.deleted_at と同じ battle (戦闘) の battle.created_at (戦闘.作成日) から

`person.deleted_at`と同じ`battle.created_at`ということなので、これらをイコールで繋いで条件として追加します。

```diff_sql
SELECT *
FROM person
INNER JOIN battle
ON person.id = battle.person_id
+ AND person.deleted_at = battle.created_at
INNER JOIN log
ON person.id = log.person_id -- person.idはbattle.person_idでも可
INNER JOIN memory
ON log.memory_id = memory.id
WHERE person.importance = 5
+ -- AND person.deleted_at = battle.created_at (こちらでもOK)
;
```

## 6.SELECTで出力したい項目だけにする

このままでは、結合したテーブルのすべてのカラムが出力されてしまいます。
一方で、問題文には、

> memory (記憶) と battle (戦闘) と person (人物) の memory.id (記憶.ID), memory.talk (記憶.会話内容), person.name (人物.人物名), battle.created_at (戦闘.作成日) を出力してください。

とあります。これに沿って、
- memory.id
- memory.talk
- person.name
- battle.created_at

を`*`と置き換えます。


```diff_sql
- SELECT *
+ SELECT memory.id, memory.talk, person.name, battle.created_at
FROM person
INNER JOIN battle
ON person.id = battle.person_id
AND person.deleted_at = battle.created_at
INNER JOIN log
ON person.id = log.person_id
INNER JOIN memory
ON log.memory_id = memory.id
WHERE person.importance = 5
;
```

**完成です！！！**

## 終わりに
SQL初心者の方にとっては、複数のテーブルをJOINして条件を追加していく流れは少し複雑に感じられるかもしれません。
しかし、問題を分解しながら進めることで確実に理解が深まります。
また、外部キーの関係や問題文の条件を正確に読み取ることが、正しいクエリを作成するポイントだと実感いただけたのではないでしょうか。

この問題は、SQLの基礎から応用まで幅広く学べる内容が詰まっていますので、ぜひこの記事を参考に他の問題にも挑戦してみてください！
プログラミングスキルの向上に役立つはずです。

他の問題にも取り組んで、そちらも記事にしていこうと思うので、興味があればぜひそちらもみてもらえたらと思います！