Hackerrank SQL 全問解答 | 難易度 Medium【問題翻訳付き】

Hackerrankは、世界中のエンジニアが共通のプログラミング問題に挑戦できるサービスで、問題を解いたスコアを競ったり、コードテストを受験して証明書を発行したりできます。

コードテストの準備用として用意されている問題集(Prepare)は、その場でコードの実行結果を確認できるため「手を動かしながら」各分野の基本を身につけるのに最適です。

本記事では、データ操作言語SQLのPrepareに登録されている難易度Mediumの16問の全問解答例を問題翻訳付きで掲載します。

答え合わせ・問題に詰まった時に活用してください。

本記事のSQLクエリは、Oracleで動作確認(提出)を行なっています。大半のコードは、Oracleに依存しない書き方になっています。

難易度Medium 16問

難易度Mediumは、データ整形の基本テクニックや、複数のテーブルからデータを集める方法を学べるレベルです。

Binary Tree Nodes

いきなりBST(Binary Search Tree, 二分探索木)が出てきて戸惑う人がいるかと思いますが、問題を解くためにはBSTの知識は必要ありませんので、気負わずいきましょう。

問題の趣旨

ある木構造に対応したBSTテーブルが与えられます。

木構造をテーブルとして表すためには、各ノードの番号と、その親ノードの番号の対応を1行ごとに保存しておけば良さそうです。

例えば、以下のテーブルは次のような木構造に対応します。

N(Node, ノードの番号)P(Parent, 親ノードの番号)
1Null
21
31
42
BSTテーブル(入力)
やること

与えられたBSTテーブルから、各ノードが木構造のどの位置にあるのかを復元し、3種類に分類した結果を出力してください。

  • Root : 親ノードを持たない、木構造の一番上のノード
  • Inner : 親ノードと子ノードを両方持つ中間ノード
  • Leaf : 親ノードを持っているが子ノードを持たない、末端(葉)ノード

求められる出力

1 Root
2 Inner
3 Leaf
4 Leaf

解答例

CASE式で以下の分岐を記述します。

  • “P”カラムを参照し、親ノードが無かったらRootに決定する
  • 親ノードを持つノードに関しては、自身を親ノードとする他のノードがあればInner、無ければLeafに決定する
{"filename":"Binary Tree Node","code":"SELECT\n  N,\n  CASE\n    WHEN P is NULL THEN 'Root'\n    WHEN EXISTS(\n      SELECT\n        *\n      FROM\n        BST BST2\n     WHERE\n        BST2.P = BST1.N\n     ) THEN 'Inner'\n    ELSE 'Leaf'\n  END\nFROM\n  BST BST1\nORDER BY\n  BST1.N;","language":"sql","id":18}

The PADS

内容は難しくありませんが、出力の並び替え(ソート)など、細かい要求に気を配る必要があります。

問題の趣旨

名前と職業の対応が保存されたOCCUPATIONSテーブルが1つ与えられます。

職業はProfessor(教授)、 Actor(俳優)、 Doctor(医師)、 Singer(歌手)のいずれかであり、他の値はテーブルにはありません。

NameOccupation
AmandaProfessor
MikeActor
MaryDoctor
OliviaDoctor
JaneActor
SamansaProfessor
AdamSinger
TomSinger
BobActor
OCCUPATIONSテーブル(入力)
やること

OCCUPATIONSテーブルのデータに、以下の2つの操作を行って出力してください。

  • 各職業は頭文字一文字に変換し、Amanda(P)のように「名前(頭文字)」の形式で全員出力する
  • 各職業何人居るか集計し、名前の後に文章として4行出力する

文字列の変換と基本の集計処理なので難しくはありませんが、出力の並び替えに注意しましょう。

  • 「名前(頭文字)」の出力は、名前のアルファベット順に並び替える
  • 集計結果は人数の降順で並び替え、人数が同じ職業が2つ以上あったら、職業のアルファベット順に並び替える
  • 集計結果に表示される職業名は全て小文字で表示し、複数形で表記する

    形式:There are a total of [occupation_count] [occupation]s

求められる出力

Adam(S)
Amanda(P)
Bob(A)
Jane(A)
Mary(D)
Mike(A)
Olivia(D)
Samansa(P)
Tom(S)
There are a total of 3 actors.
There are a total of 2 doctors.
There are a total of 2 professors.
There are a total of 2 singers.

解答例

職業名はSELECT句内でCASE式を使って頭文字1文字に変換し、名前のアルファベット順で並び変えます。

GROUP BY句で行った人数の集計結果を後に続けます。集計時にも、職業名の小文字変換と並び替えが必要で、やや煩雑なので注意を払いましょう。

{"filename":"THE PADS","code":"SELECT\n  Name || '(' || CASE\n    WHEN Occupation = 'Professor' THEN 'P'\n    WHEN Occupation = 'Actor' THEN 'A'\n    WHEN Occupation = 'Doctor' THEN 'D'\n    WHEN Occupation = 'Singer' THEN 'S'\n    ELSE NULL\n  END || ')'\nFROM\n  OCCUPATIONS\nORDER BY\n  Name;\nSELECT\n  'There are a total of ' || COUNT() || CASE\n  WHEN Occupation = 'Professor' THEN ' professors.'\n  WHEN Occupation = 'Actor' THEN ' actors.'\n  WHEN Occupation = 'Doctor' THEN ' doctors.'\n  WHEN Occupation = 'Singer' THEN ' singers.'\n  ELSE NULL\n  END\nFROM\n  OCCUPATIONS\nGROUP BY\n  Occupation\nORDER BY\n  COUNT(),\n  Occupation;","language":"sql","id":18}

Occupations

名前と職業についての集計という意味では前問(THE PADS)と同じですが、出力の形式が全く違うので別の問題として考えましょう。

問題の趣旨

名前と職業の対応を表す以下のテーブル1つが与えられます。

NameOccupation
AmandaProfessor
MikeActor
MaryDoctor
OliviaDoctor
JaneActor
SamansaProfessor
AdamSinger
TomSinger
BobActor
OCCUPATIONSテーブル(入力)
やること

OCCUPATIONSテーブルの人物を職業ごとに分類し、同じカラム(列)に同じ職業の人物の名前が並ぶように出力してください。

さらに、出力について以下の要求があります。

  • 列の順番は、左から順に、Doctor→Professor→Singer→Actorにする
  • 全部の職業で出力される行数が同じになるように出力を整形する。名前が埋まらないところはNULLを出力する
  • 同じ職業の人物の出力順は名前のアルファベット順にする

求められる出力

本問に関しては、出力を先に見た方がわかりやすいかも知れません。

Mary Amanda Adam Bob
Olivia Samansa Tom Jane
NULL NULL NULL Mike

解答例

解答例はやや長いです。 ROW_NUMBERで集計することで、「各職業に所属する人の1番目、2番目、…」を取得しています。

{"filename":"Occupations","code":"SELECT\n  MAX(\n    CASE\n      WHEN Occupation = 'Doctor' THEN Name\n      ELSE NULL\n    END\n  ),\n  MAX(\n    CASE\n      WHEN Occupation = 'Professor' THEN Name\n      ELSE NULL\n    END\n  ),\n  MAX(\n    CASE\n      WHEN Occupation = 'Singer' THEN Name\n      ELSE NULL\n    END\n  ),\n  MAX(\n    CASE\n      WHEN Occupation = 'Actor' THEN Name\n      ELSE NULL\n    END\n  )\nFROM(\n  SELECT\n    ROW_NUMBER() OVER(\n      PARTITION BY Occupation\n      ORDER BY\n        Name\n    ) RN,\n    Name,\n    Occupation\n  FROM\n    OCCUPATIONS\n )\nGROUP BY\n  RN\nORDER BY\n  RN;","language":"sql","id":18}

Weather Observation Station 18

SQLで算術演算を使用する問題です。

問題の趣旨

駅名を保存したSTATIONテーブルが1つ与えられます。STATIONテーブルには、各駅の位置が緯度と軽度で与えられています。

IDCITY(都市)STATE(州)LAT_N(北緯)LONG_W(西経)
824Loma MarCA48.69788572130.53935410
478TiptonIN33.5479270197.94286036
619ArlingtonCO75.1799307992.94615894
711TurnerAR50.24380534101.45801630
STATIONテーブル(入力)
やること

STATIONテーブルの中から、「最も大きい北緯と最も小さい北緯の差」と「最も大きい西経と最も小さい西経の差」を足し合わせた値マンハッタン距離)を求めてください。

2点間のマンハッタン距離。wikipediaより引用

出力に対して、問題には以下の要求があります。

  • マンハッタン距離の計算結果は、最後に四捨五入して小数点4桁に丸める

求められる出力

サンプルデータ中で

サンプルデータ中で最も大きい北緯は75.17993079、最も小さい北緯は33.54792701です。

最も大きい西経は130.53935410、最も小さい西経は92.94615894です。

よって、(75.17993079 – 33.54792701)+ (130.53935410 – 92.94615894)を小数点5桁目で四捨五入した結果になります。

79.2252

解答例

素直に実装しましょう。この問題に解答するために、都市名や駅IDは全く不要であることがわかります。

{"filename":"Weather Observation Station 18","code":"SELECT\n  ROUND(\n    ABS(MAX(LAT_N) - MIN(LAT_N)) + ABS(MAX(LONG_W) - MIN(LONG_W)),\n    4\n  )\nFROM\n  STATION;","language":"sql","id":18}

Weather Observation Station 19

Weather Observation Station 18に引き続き、SQLで算術演算を使用する問題です。

問題の趣旨

駅名を保存したSTATIONテーブルが1つ与えられます。STATIONテーブルには、各駅の位置が緯度と軽度で与えられています。

IDCITY(都市)STATE(州)LAT_N(北緯)LONG_W(西経)
824Loma MarCA48.69788572130.53935410
478TiptonIN33.5479270197.94286036
619ArlingtonCO75.1799307992.94615894
711TurnerAR50.24380534101.45801630
STATIONテーブル(入力)
やること

STATIONテーブルの中の最も大きい北緯X1, 最も大きい西経Y1、最も小さい北緯X2、最も小さい西経Y2とします。

地点A(X1, Y1)地点B(X2, Y2)とした時に、地点Aと地点Bの距離を求めてください。

出力に対する要求として、以下の条件があります。

  • 距離の計算結果は、最後に四捨五入して小数点4桁に丸める

求められる出力

最も大きい北緯が75.17993079、最も小さい北緯は33.54792701です。

最も大きい西経が130.53935410、最も小さい西経は92.94615894です。

地点A(75.17993079, 130.53935410)で、地点B(33.54792701, 92.94615894)になり、A, B間の距離が答えになります。

56.0934

解答例

素直に実装しましょう。この問題に解答するために、都市名や駅IDは全く不要であることがわかります。

{"filename":"Weather Observation Station 19","code":"SELECT\n  ROUND(\n    SQRT(\n      POWER(MAX(LAT_N) - MIN(LAT_N), 2) + POWER(MAX(LONG_W) - MIN(LONG_W), 2)\n    ),\n    4\n  )\nFROM\n  STATION;","language":"sql","id":18}

Weather Observation Station 20

SQLに用意されている、統計値を出力する関数を使用する問題です。

問題の趣旨

駅に関する情報を保存したSTATIONテーブルが1つ与えられます。

STATIONテーブルには、各駅がある都市・州・駅の緯度と軽度が与えられています。

IDCITY(都市)STATE(州)LAT_N(北緯)LONG_W(西経)
824Loma MarCA48.69788572130.53935410
478TiptonIN33.5479270197.94286036
619ArlingtonCO75.1799307992.94615894
711TurnerAR50.24380534101.45801630
STATIONテーブル(入力)
やること

STATIONテーブルに含まれる北緯の中央値(メディアン)を小数点4桁に四捨五入して出力してください。

求められる出力

上記のSTATIONテーブルのデータは4個ですから、北緯の2番目に大きい値と3番目に大きい値の平均値が中央値(メディアン)になります。

49.4708

解答例

素直に実装しましょう。STATIONテーブルのうち使用するデータはLAT_Nのみです。

{"filename":"Weather Observation Station 20\uff08Oracle\uff09","code":"SELECT\n  ROUND(MEDIAN(LAT_N), 5)\nFROM\n  STATION;","language":"sql","id":18}

Oracleでは中央値を直接求めるMEDIANがありますので、これを利用します。

The Report

2つのテーブルを使用した集計を行う問題です。

問題の趣旨

2つのテーブルが与えられます。

  • Studentsテーブル:ある試験に関する生徒の点数を記録したテーブル
  • Gradesテーブル:点数と成績(等級)の対応を定めているテーブル
IDNameMarks
1Julia88
2Samansa68
3Maria99
4Scarlet78
Studentsテーブル(入力)
Grade(等級)Min_MarkMax_Mark
109
21019
32029
43039
54049
65059
76069
87079
98089
1090100
Gradesテーブル(入力)
やること

Gradesテーブルに定められた基準に従って生徒の成績を決定し、出力してください。

出力に対する要求は以下です。

  • 出力は3列で、左から順にName(名前)→ Grade(等級)→Score(点数)にする
  • Gradeが8より小さい生徒については、名前の代わりにNULLと表示する
  • 出力はGradeが高い方から出力し、同じGradeの生徒は名前のアルファベット順にソートする

求められる出力

Maria 10 99
Julia 9 88
Scarlet 8 78
NULL 7 68

解答例

問題への解答に限っては、Gradesテーブルを使用しなくても正答できますが、Gradesテーブルが変更になるとクエリが使用できなくなってしまいます。

SQLクエリの中でGradesテーブルを利用しましょう。

{"filename":"The Report","code":"SELECT\n  CASE\n    WHEN Marks >= 70 THEN Name ELSE NULL\n  END,\n  (\n    SELECT\n      Grade\n    FROM\n      Grades\n    WHERE\n      Marks >= Grades.Min_Mark\n      AND Marks = Grades.Max_Mark\n  ) AS G,\n    Marks\n  FROM\n    Students\n  ORDER BY\n    G DESC,\n    Name,\n    Marks;","language":"sql","id":18}

Top Competitors

多くのテーブルのデータを合わせて、条件を満たすデータの絞り込みを行う問題です。

問題の趣旨

コーディングコンテストに関する、以下の4つのテーブルが与えられます。

  • Hackersテーブル:コンテスト参加者のIDと名前を保存しているテーブル
  • Difficultyテーブル:どの難易度の問題に正答したら何点もらえるかを定めているテーブル
  • Challengesテーブル:出題された問題のID、問題作成者のID、問題の難易度の対応を保存した問題リスト
  • Submissionsテーブル:解答の提出ID、提出者のID、提出対象の問題ID、得点を保存した提出履歴
ツカレパンダ
ツカレパンダ

問題文が複雑すぎて何したらいいかよく分からないよ。疲れた

ダテネコ
ダテネコ

注意したいところをまとめたから、そこに集中すると見通しが良くなるぜ。

注目ポイント
  • コンテスト作成者は問題を解く(提出する)側にも、問題作成者にもなり得る
  • 問題に正答したら、Difficultyテーブルで定められている満点がもらえる
  • 正答で無い場合にも部分点が存在し、得点が0になるとは限らない
hacker_idname
11111Mary
22222Bob
33333Tom
Hackersテーブル(入力)
difficulty_level(難易度)score(得点)
120
230
340
Difficultyテーブル(入力)
challenge_idhacker_id(作成者のID)difficulty_level
100222221
200111112
300222223
400333333
Challengesテーブル(入力)
submission_idhacker_id(回答者のID)challenge_idscore
55551111110020
66661111130040
77772222220030
8888333331004
Submissionsテーブル(入力)
やること

2つ以上の異なる問題に正答したユーザーを検索して、hacker_idとname(名前)を出力してください。

求められる出力

かなり煩雑ですので、提出履歴が記録されているSubmissionsテーブルから愚直に分析してみましょう。

submission_idhacker_id(回答者のID)challenge_idscore
55551111110020
66661111130040
77772222220030
8888333331004
Submissionsテーブル(再掲)

提出は4回あるので、順に見てみます。

  • haker11111(Mary)は問題100に挑戦し、20点貰った。
    問題リスト(Challengesテーブル)を見ると問題100は難易度1である。
    Diffycultyテーブルを見ると難易度1の問題の得点は20だから、Maryの得点はこの問題のフルスコアだった。
    Maryは問題100に挑戦し、フルスコアをもらっている
  • 同様に考えると、Maryは問題300にも挑戦し、フルスコアをもらっている
  • 同様に考えると、hacker22222(Bob)は問題200に挑戦し、フルスコアをもらっている
  • 同様に考えると、hacker33333(Tom)は問題100に挑戦したが、得点は4であり、フルスコアではない

以上より、2つ以上の異なる問題に正答したユーザーはMaryだけなの、Maryのidと名前を出力すれば正解です。

11111 Mary

解答例

各ユーザーの正答数(満点を獲得した問題数)を調べるためには、4つ全てのテーブルのデータが必要です。

下記の実装では、一旦必要な情報を全部集めたビューを作成し、ビューからデータを抽出するという2段階を踏んでいます。

{"filename":"Top Competitors","code":"CREATE VIEW PerfectScorer(hacker_id, name) AS\nSELECT\nCASE WHEN SUB1.score =\n  (SELECT score\n  FROM Challenges INNER JOIN Difficulty\n  ON Challenges.difficulty_level = Difficulty.difficulty_level\n  WHERE SUB1.challenge_id = challenge_id)\nTHEN SUB1.hacker_id ELSE NULL END AS full_score_hacker_id,\n  (SELECT name FROM Hackers WHERE Hackers.hacker_id = SUB1.hacker_id)\nFROM Submissions SUB1;\nSELECT hacker_id, MAX(name) FROM PerfectScorer\nWHERE (hacker_id IS NOT NULL)\nGROUP BY hacker_id\nHAVING COUNT(hacker_id) > 1\nORDER BY COUNT(hacker_id) DESC, hacker_id;","language":"sql","id":18}

Contest Leaderboard

Top Competitorと同じくプログラミングコンテストを題材とした問題で、複数のテーブルから集計処理を行う問題です。

問題の趣旨

以下の2つのテーブルが与えられます。

  • Hackersテーブル:コンテスト参加者のID、名前を保存したテーブル
  • Submissionsテーブル:解答の提出ID、提出者のID、提出した問題ID、得点を保存した提出履歴テーブル
hacker_idname
11111Mary
22222Bob
33333Tom
44444John
Hackersテーブル(入力)
submission_idhacker_id(提出者のID)challenge_idscore
55551111110020
66661111130040
77772222220030
8888333331004
99993333310020
12345444442000
54321444443000
Submissionsテーブル(入力)
やること

各参加者がプログラミングコンテストで得た総得点を集計し、参加者のID、名前、総得点を出力して下さい。

集計の際に、以下のことに注意しましょう。

  • 同じ問題に何回も提出できる
  • 同じ問題に複数回提出があった場合、一番高い得点で評価する

同じ問題に対しての得点を重複して計算すると、総得点が実際のスコアより大きくなってしまいます。

また、問題の出力に対しては以下の要求があります。

  • 総得点が0の参加者は、出力から除外する
  • 参加者は総得点の大きい順に並べて出力する。
  • 総得点が同じ参加者が複数いたら、IDが小さい順(昇順)に出力する。

求められる出力

例に挙げているSubmissionsテーブルを再掲します。

submission_idhacker_id(回答者のID)challenge_idscore
55551111110020
66661111130040
77772222220030
8888333331004
99993333310020
12345444442000
54321444443000
Submissionsテーブル(入力)
  • hacker11111(Mary)の総得点は、20+40 = 60
  • haker22222(Bob)の総得点は、30
  • hacker33333Tom)は問題100に2回挑戦しており、得点は高い方の20点。
    他の問題には提出していないので総得点は20
  • hacker44444(John)は問題200と問題300の2問に挑戦しているが、どちらも得点が0のため、出力から除外する

出力は以下のようになります。

11111 Mary 60
22222 Bob 30
33333 Tom 20

解答例

Hackersテーブルは、hakcker_idからname(名前)を引っ張ってくるためだけに利用します。

集計処理に関してはSubmissionsテーブルのみ考えれば良いことが分かると、見通しが良くなります。

{"filename":"Contest Leaderboard","code":"CREATE VIEW Max_Scores(hacker_id, name, max_score)\nAS\nSELECT\n  MAX(hacker_id),\n  MAX((SELECT\n      name\n    FROM Hackers\n    WHERE Hackers.hacker_id = Submissions.hacker_id)),\n  MAX(score)\nFROM Submissions\nWHERE score > 0\nGROUP BY hacker_id, challenge_id;\nSELECT\n  hacker_id,\n  MAX(name),\n  SUM(max_score)\nFROM Max_Scores\nGROUP BY hacker_id\nORDER BY SUM(max_score) DESC, hacker_id;","language":"sql","id":18}

Challenges

Top Competitors, Contest Leaderboardと同じく、プログラミングコンテストを題材にした問題です。

複数テーブルからの集計を行います。

問題の趣旨

プログラミングコンテストに出題される問題の作成に関して、以下の2つのテーブルが与えられます。

  • Hackersテーブル:コンテスト出題者のIDと名前を保存しているテーブル
  • Challengesテーブル:問題のIDと問題作成者のIDの対応を保存した、問題の作成履歴
hacker_idname
11111Mary
22222Bob
33333Tom
44444John
Hackersテーブル(入力)
challenge_idhacker_id(作成者のID)
10011111
20011111
30022222
40022222
50033333
60044444
Challengesテーブル(入力)
やること

出題者が何問問題を作成したかを集計し、作成者のID、名前、作成数を出力してください。

問題の出力に対しての要求が少しトリッキーなので注意しましょう。

  • 出力は問題作成数が多い人から順に並べる。(ルール1)
  • 問題作成数が同じ出題者が複数居たら、全員出力から除外する。(ルール2)
  • ルール2で除外される出題者の問題作成数が、集計結果の中で最も多い場合に限り、出力に全員表示する。(ルール3)
  • ルール3により出力される出題者たちは、IDの若い順(IDの昇順)にソートする

求められる出力

Challengesテーブルを再掲します。

challenge_idhacker_id(作成者のID)
10011111
20011111
30022222
40022222
50033333
60044444
Challengesテーブル(入力)
  • hacker11111(Mary)とhacker22222(Bob)はともに2問づつ作成している。
  • hacker33333Tom)とhacker44444(John)はともに1問ずつ作成している。
  • TomとJohnは作問数が同じため出力から除外する。
  • MaryとBobは作問数が同じだが、2問は集計結果の中で最も多いため、2人とも出力する
11111 Mary 2
22222 Bob 2

解答例

煩雑なルールのために、分岐が必要になってきます。

{"filename":"Challenges","code":"CREATE VIEW Count_Each_Student_Posts(hacker_id, name, Cnt) AS\nSELECT Hackers.hacker_id,\n  MAX(name),\n  COUNT(challenge_id)\nFROM\n  Hackers INNER JOIN Challenges\n  ON Hackers.hacker_id = Challenges.hacker_id\nGROUP BY Hackers.hacker_id\nORDER BY COUNT(challenge_id) DESC, Hackers.hacker_id;\nSELECT *\nFROM\n  (SELECT\n    CASE\n      WHEN (SELECT COUNT(*)\n        FROM Count_Each_Student_Posts C2\n        WHERE C1.Cnt = C2.Cnt\n        GROUP BY Cnt) = 1\n     THEN hacker_id\n     ELSE\n      CASE\n        WHEN Cnt = (SELECT MAX(Cnt) FROM Count_Each_Student_Posts)\n        THEN hacker_id\n        ELSE NULL END\n      END hacker_id_2,\n     name,\n     Cnt\nFROM Count_Each_Student_Posts C1)\nWHERE hacker_id_2 IS NOT NULL\nORDER BY Cnt DESC, hacker_id_2;","language":"sql","id":18}

Olivander’s Inventory

ハリーポッターに関する問題です。題意がやや読み取りづらい問題ですが、見た目より簡単です。

問題の趣旨

魔法の杖に関する、以下の2つのテーブルが与えられます。

  • Wandsテーブル:杖のID、コード(杖のカテゴリ)、値段、魔力の強さが保存された杖の販売リスト
  • Wands_Propertyテーブル:杖のコード、杖の年齢、杖が邪悪かどうか(0 or 1)の対応を保存したテーブル

杖のコードは、杖の年齢と、杖が邪悪かどうかの2つを決めるカテゴリのようなものです。

例えば杖A, 杖Bが同じコードであれば、杖A,杖Bは年齢が同じであり、一方が邪悪なら他方も邪悪ということになります。

idcodecoins_neededpower
1430008
2390003
33700010
417008
5260002
Wandsテーブル(入力)
codeageis_evil(1だと邪悪)
1450
2400
341
4200
Wands_Propertyテーブル(入力)
やること

ハーマイオニーが決めた以下のルールに基づいて、購入の優先順位の高い杖から順に杖のID、年齢、値段、魔力の強さを出力してください。

  • 邪悪(is_evil = 1)な杖は購入できない
  • 邪悪でない杖のうち、魔力の高い杖を優先する
  • 魔力が同じなら、年齢が高い杖を優先する
  • 魔力と年齢が同じ杖が複数あったら、値段が一番安い杖のみ購入リストに加える

求められる出力

4 45 700 8
1 20 3000 8
5 40 6000 2

解答例

魔力と年齢が同じでf値段が高い杖を出力から削除するために、相関サブクエリを用いています。

{"filename":"Olivander's Inventory","code":"CREATE VIEW W(id, age, coins_needed, power, is_evil) AS\nSELECT\n  id,\n  age,\n  coins_needed,\n  power,\n  is_evil\nFROM\n  Wands INNER JOIN Wands_property\n  ON Wands.code = Wands_property.code;\nSELECT\n  id,\n  age,\n  coins_needed,\n  power\nFROM\n  W\nWHERE is_evil = 0\n  AND coins_needed =\n  (\n    SELECT\n      MIN(coins_needed)\n    FROM W W2\n    WHERE W.age = W2.age\n      AND W.power = W2.power\n  )\nORDER BY\n  power DESC,\n  age DESC;","language":"sql","id":18}

Placement

ここまでの問題が解けていれば簡単な問題です。

問題の趣旨

以下の3つのテーブルが与えられます。

  • Studentsテーブル:人物のIDと名前の対応が保存されたテーブル
  • Friendsテーブル:それぞれの人物と、友達のIDが保存されたテーブル
  • Packagesテーブル:人物のIDと、その人の給与が保存されたテーブル
IDName
1Ashley
2Samantha
3Julia
4Scarlet
Studentsテーブル(入力)
IDFriend_ID
12
23
34
41
Studentsテーブル(入力)
IDSalary
115.20
210.06
311.55
412.12
Packagesテーブル(入力)
やること

自身より友達の方が給与が高いような人物の名前を、友達の給与が低い順(昇順)にソートして出力してください。

ただし、データ中に同じ給与の人物が2人以上含まれないことが保証されています。

求められる出力

Samantha(Friend_ID = 3), Julia(Friend_ID = 4), Scarlet(Friend_ID = 1)の順に出力されます。

Samantha
Julia
Scarlet

解答例

サブクエリを活用して解答すれば比較的容易です。

{"filename":"Placements","code":"SELECT Name FROM Students Std1\nWHERE\n  (SELECT Salary\n    FROM Packages P1\n    WHERE P1.ID = Std1.ID)\n    <\n  (SELECT Salary\n    FROM Packages P2\n    WHERE P2.ID\n    = (SELECT Friend_ID\n  FROM Friends\n  WHERE Std1.ID = Friends.ID))\nORDER BY\n  (SELECT Salary\n    FROM Packages P2\n    WHERE P2.ID\n  = (SELECT Friend_ID\n       FROM Friends\n      WHERE Std1.ID = Friends.ID));","language":"sql","id":18}

New Companies

複数のテーブルの集計作業を行う問題です。見た目より簡単な問題です。

問題の趣旨

会社組織に関するテーブルが与えられます。

テーブルには複数の会社組織の情報が混在して与えられますが、役職名と役職同士の関係は全ての会社に共通で、以下の通りになっています。

組織図。役職名・関係は全ての会社に共通。

5つの役職それぞれについての情報が、5つのテーブルで与えられます。

  • Companyテーブル:会社IDと、その会社のFounder(設立者)の名前の対応が保存されたテーブル
  • Lead_Managerテーブル:会社IDと、その会社に所属するLead ManagerのIDの対応が保存されたテーブル
  • Senior_Managerテーブル:会社ID、Senior_ManagerのID、上司であるLead ManagerのIDが保存されたテーブル
  • Managerテーブル:会社ID、ManagerのID、上司のSenior Manager・Lead ManagerのIDが保存されたテーブル
  • Employeeテーブル:会社ID、EmployeeのID、上司のManager・Senior Manager・Lead ManagerのIDが保存されたテーブル

それぞれのテーブルは、直属の上司のIDだけでなく、組織図上で上にある全ての上司のIDを保持しています。

company_codefounder
C1Monika
C2Samantha
Companyテーブル(入力)
lead_manager_codecompany_code
LM1C1
LM2C2
Lead_Managerテーブル(入力)
senior_manager_codelead_manager_codecompany_code
SM1LM1C1
SM2LM1C1
SM3LM2C2
Senior_Managerテーブル(入力)
manager_codesenior_manager_codelead_manager_codecompany_code
M1SM1LM1C1
M2SM3LM2C2
M3SM3LM2C2
Managerテーブル(入力)
employee_codemanager_codesenior_manager_codelead_manager_codecompany_code
E1M1SM1LM1C1
E2M1SM1LM1C1
E3M2SM3LM2C2
E4M3SM3LM2C2
Employeeテーブル(入力)
やること

各会社ごとに、各役職がそれぞれ何人勤務しているかを集計して出力してください。

出力の形式は以下のようにするよう要求されています。

  • 会社ID 設立者の名前 Lead Managerの数 Senior Managerの数 Managerの数 Employeeの数
  • 出力は会社IDの辞書順に並び替える

また、テーブルの中に重複レコードが存在しますので、集計時に取り除きましょう。

求められる出力

C1 Monika 1 2 1 2
C2 Samantha 1 1 2 2

解答例

各テーブルが、組織図上で上にある全ての上司の情報を含んでいるため、表を結合する必要がありません。

集計は問題の見た目より簡単です。

重複レコードをDISTINCTで取り除くことを忘れずに。

{"filename":"New Company","code":"SELECT\n  company_code,\n  founder,\n  (SELECT\n    COUNT(DISTINCT lead_manager_code)\n  FROM Lead_Manager\n  WHERE Lead_Manager.company_code = Company.company_code\n  GROUP BY company_code),\n  (SELECT\n    COUNT(DISTINCT senior_manager_code)\n  FROM Senior_Manager\n  WHERE Senior_Manager.company_code = Company.company_code\n  GROUP BY company_code),\n  (SELECT\n    COUNT(DISTINCT manager_code)\n  FROM Manager\n  WHERE Manager.company_code = Company.company_code\n  GROUP BY company_code),\n  (SELECT\n    COUNT(DISTINCT employee_code)\n  FROM Employee\n  WHERE Employee.company_code = Company.company_code\nGROUP BY company_code)\nFROM Company\nORDER BY company_code;","language":"sql","id":18}

Symmetric Pairs

1つのテーブルに含まれるレコード同士を比較する問題です。

問題の趣旨

X, Yの2つの数字の対応が保存されたFunctionsテーブルが1つ与えられます。

XY
1020
2010
1530
1520
3015
Functionsテーブル(入力)
やること

Functionsテーブルの中から、XとYを入れ替えたら同じになるようなレコードのペアを見つけ出力してください。

例えば、上のテーブルでは、1行目と2行目、3行目と5行目の2組のペアがあります。

出力形式に対する要求として、

  • ペア1組につき1行出力する。2つのうちX <= Yのレコードの方を出力する
  • 出力は、Xが若い順に行を並び替える

求められる出力

(X, Y)= (10, 20)と、(X, Y)=(15, 30)を出力します。

10 20
15 30

解答例

同じテーブルに含まれるレコード同士を比較するために、EXSISTS句を使用した相関サブクエリの形になっています。

同じレコード同士を比較してペアと判定しないように、予め連番を振っておく実装です。

{"filename":"Symmetric Pairs (Oracle)","code":"CREATE VIEW Functions_with_Rownumber(R, X, Y)\nAS\nSELECT\n  ROW_NUMBER() OVER(ORDER BY X),\n  X,\n  Y\nFROM Functions;\nSELECT\n  X,\n  Y\nFROM Functions_with_Rownumber F1\nWHERE\n  EXISTS(\n    SELECT\n      *\n    FROM\n      Functions_with_Rownumber F2\n    WHERE\n      F1.X = F2.Y AND F1.Y = F2.X\n    AND\n      F1.R < F2.R\n  )\nORDER BY R;","language":"sql","id":18}

SQL Project Planning

Symmetric Pairsに引き続き、レコード間比較が必要になる問題です。時系列データを扱います。

問題の趣旨

タスクを管理する1つのprojectsテーブルが与えられます。projectsテーブルには、タスクのID、開始日、終了日が保存されています。

Task_IDStart_DateEnd_Date
12024-10-012024-10-02
22024-10-022024-10-04
32024-10-042024-10-05
42024-10-062024-10-08
52024-10-082024-10-09
projectsテーブル(入力)

projectsテーブルの中で、タスクの終了日(End_Date)とタスクの開始日(Start_Date)が一致する、連続した複数のタスクがあったとき、それらはともに同じプロジェクトの一部です。

例えば、上記のprojectsテーブルには2つのプロジェクトが存在し、スケジュールは以下のようになります。


projectsテーブルに保存されたタスクのスケジュール
やること

projectsテーブルのタスクを、プロジェクト毎にまとめ、各プロジェクトの開始日と終了日を出力してください。

出力に対する要求は以下の2つです。

  • プロジェクトごとに1行出力する。出力は、プロジェクトの日数が短い方から順に並べる
  • 同じ日数のプロジェクトが複数あったら、開始日が早い方から並べる

求められる出力

1つ目のプロジェクトは4日間、2つ目は3日間のスケジュールですから、2つ目を先に出力します。

2024-10-06 2024-10-09
2024-10-01 2024-10-05

解答例

時系列データという意味でも、レコード間比較という意味でも、ウィンドウ関数を使用する解法が連想されますね。

{"filename":"SQL Project Planning (Oracle)","code":"CREATE VIEW V(Start_Date, End_Date, period) AS\nSELECT\n  Start_Date,  \n  End_Date,\n  SUM(not_consecutive_flg) OVER(ORDER BY Start_Date RANGE UNBOUNDED PRECEDING)\nFROM\n(SELECT\n  Start_Date,\n  End_Date,\n  CASE WHEN\n    MIN(End_Date)\n    OVER(ORDER BY Start_Date\n      ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING) IS NULL\n  THEN 1\n  ELSE\n    CASE WHEN\n      Start_Date -\n      MIN(End_Date)\n      OVER(ORDER BY Start_Date\n        ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING) > 0\n    THEN 1 ELSE 0 END\n  END not_consecutive_flg\nFROM Projects);\nSELECT\n  MIN(Start_Date),\n  MAX(End_Date)\nFROM V\nGROUP BY period\nORDER BY COUNT(*), MIN(Start_Date);","language":"sql","id":18}

Print Prime Numbers

通常のプログラミング言語なら簡単な問題ですが、SQLではどうしたらいいでしょうか。

問題の趣旨

入力となるテーブルはありません。

やること

1000以下の素数を列挙し、アンパサンド”&”で結合して出力してください。

求められる出力

1000以下を列挙すると書ききれませんので、例えば10以下の素数を列挙する場合の出力を記載します。

2&3&5&7

解答例

一旦0 ~ 9の数字をもつDigitsテーブルを作成し、Digitsテーブルの数字を使って、1000以下の数字を全て保持するSequenceテーブルを作成しました。

その後、Sequenceテーブルの中から素数を抽出しています。

SQLでは、使用するデータは全てテーブルに持たなければなりません。この問題はSQLに明らかに「向いていない」操作を要求していますが、なんとかできなくはないというところです。

{"filename":"Print Prime Numbers (Oracle)","code":"CREATE TABLE Digits(\n  digit INTEGER NOT NULL\n);\nINSERT INTO Digits VALUES\n(0),(1),(2),(3),(4),(5),(6),(7),(8),(9);\nCREATE VIEW Sequence(seq)\nAS\nSELECT D1.digit + 10(D2.digit) + 100\/em>(D3.digit)\nFROM Digits D1\n  CROSS JOIN Digits D2\n  CROSS JOIN Digits D3;\nCREATE VIEW PrimeNums(R,p)\nAS\nSELECT\n  ROW_NUMBER() OVER(ORDER BY seq),\n  seq\nFROM Sequence S1\nWHERE (\n  NOT EXISTS(\n    SELECT\n      *\n    FROM Sequence S2\n    WHERE S1.seq > S2.seq\n      AND S1.seq % S2.seq = 0\n      AND S2.seq > 1\n  )\n  AND S1.seq BETWEEN 2 AND 1000\n)\nOR S1.seq = 2;\nSELECT\n  GROUP_CONCAT(p separator '&')\nFROM\n  PrimeNums;","language":"sql","id":18}

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA


error: Content is protected !!