SQLのMAX()関数は、単にグループ内の最大値(最も大きい数値)を求めるだけではありません。GROUP BY句と組み合わせることで、グループの 「代表」の値を選ぶ という、より実践的な使い方ができます。
GROUP BYでデータを集約する際、集約キー以外の列(例:ユーザー名やメールアドレス)をSELECT句に含めると、通常はエラーになります。
エラーになる理由は、「グループ化した後、どの行の値を表示すればいいかデータベースが判断できないから」 です。
GROUP BYでまとめたグループ内の情報は、本来一つに定まるはずです(例:顧客IDが同じなら、顧客名は同じ)。このような場合に、その情報を「代表」として一つだけ表示させたい、というニーズが生まれます。
ここでMAX()関数が役立ちます。MAX()は数値だけでなく文字列にも使用でき、文字列の場合は辞書順(アルファベット順)で最も後ろに来るものを選びます。
例えば、顧客IDでグループ化した際に顧客名も表示したい場合、MAX(username)と記述します。これにより、SQLはエラーを出すことなく、グループ内の一つのユーザー名を「代表」として選択し、結果を表示してくれます。
この方法は、
MIN()関数(辞書順で最も前に来るものを選択)を使っても同様に実現できます。グループ内の値が全て同じであれば、MAX()とMIN()の結果は当然同じになります。これは、エラー回避のための方便としてよく使われる定番のテクニックです。
CASE WHENでSQLの表現力を高める — 条件分岐からデータの"翻訳"までSQLはデータをただ抽出するだけのツールではありません。CASE WHEN構文を使いこなすことで、データの表現方法をクエリ内で自在にコントロールできます。
この構文の本質は 「条件分岐」 ですが、これを応用することで、SQLクエリ自体を データの"翻訳機 のように機能させることが可能になります。
データベースに保存されているコードや数値を、人間が読んで分かる言葉にクエリの実行と同時に変換するテクニックです。
例えば、user_roleというカラムに'1'や'2'といったコードが保存されているとします。このままではレポートを見ても意味が分かりません。
CASE WHENを使えば、このコードをSQLクエリの中で直接、分かりやすい日本語ラベルに"翻訳"できます。
SELECT
user_id,
CASE user_role
WHEN '1' THEN '管理者'
WHEN '2' THEN '一般ユーザー'
FROM
users;
ポイント: 上記は特定の値との一致を見るシンプルな例ですが、
CASE WHENは本来、より複雑で多様な条件を指定できる強力な構文です。
この手法が強力なのは、アプリケーション側で変換処理を行ったり、別途「コード対応表」をExcelなどで用意したりする必要がなくなる点です。
SQLクエリを実行した時点で、すでにビジネスで使える言葉に"翻訳"された、完成形のデータが出力されます。これにより、データ抽出とレポート用のデータ整形を一度に完結させることができます。
NULLはエラーじゃない、活用できる「状態」であるNULLはデータが存在しないことを示す「状態」です。エラーとして恐れるのではなく、この状態を積極的に活用することで、より洗練されたデータハンドリングが可能になります。
ここでは、NULLを巧みに扱う2つの代表的なテクニックを紹介します。
IFNULL()で見栄えを整えるレポートや分析結果にNULLという文字がそのまま表示されると、無機質で分かりにくいことがあります。IFNULL()関数を使えば、NULLを任意の文字に置き換えることができます。
IFNULL(チェックしたい値, NULLだった場合の表示)supervisor_name(上司名)がNULLの場合に、「該当なし」と表示させる例です。
IFNULL(MAX(A.supervisor_name), '該当なし') AS supervisor
これにより、出力結果からNULLという文字が消え、レポートが格段に見やすくなります。
IS NULLで有無をフラグにするデータの「有無」そのものを、true / falseのようなフラグ情報に変換するテクニックです。後のデータ処理やアプリケーションでの利用が非常に簡単になります。
IF(条件, trueの場合の値, falseの場合の値)supervisor_id(上司ID)がNULLかどうかを判定し、上司の有無をfalse / trueのフラグとして新しい列を作成します。
IF(MAX(A.supervisor_id) IS NULL, false, true) AS has_supervisor
このhas_supervisor列を使えば、「上司がいる従業員だけを絞り込む」といった後続の処理を、このtrue/falseフラグだけでシンプルに実行できます。
JSON_VALUE()で"箱"の中身を取り出す最近のデータベースでは、一つのカラムに複数の情報をまとめてJSON形式で格納することがよくあります。
このような、情報が入れ子になった"箱"のようなデータから、特定の情報だけをピンポイントで取り出すための必須関数がJSON_VALUE()です。
この関数の働きを、「プロフィールボックス」に例えると直感的に理解できます。
user_dataというカラムが、プロフィール情報が入った大きな箱だとします。{'user_id': 'AB123', 'status': 'active'} のように、キーと値がペアになった情報(札)が入っています。この箱から'user_id'の値だけを取り出したい場合、SQLは次のようになります。
JSON_VALUE(user_data, '$.user_id') AS user_id
これは、「user_dataという箱 (user_data) の中から、user_idという札 ($.user_id) を探し、その値 'AB123' を取り出してください」という命令です。
$はJSONデータの「一番外側(根元)」を指す記号です。
JSON_VALUE()を使いこなせれば、一見複雑なJSONデータも、まるで通常のテーブルのシンプルなカラムのように扱えます。これにより、WHERE句での絞り込みやGROUP BYでの集計など、SQLのパワフルな機能をJSONデータに対してもフル活用できるようになります。
CAST()はJOINを成功させる「握手」の作法「見た目は同じ値なのにJOINがうまくいかない」という不可解なエラーの多くは、目には見えないデータ型の違いが原因です。この問題を解決する関数がCAST()です。
テーブル間でキーとなる列のデータ型が異なると(例:片方は数値型、もう片方は文字列型)、JOINは失敗します。CAST()は、この異なるテーブル同士を正しく繋ぐための **「握手の作法」**となります。
table_Aのuser_idが数値型、table_Bのuser_idが文字列型の場合、ON句で片方の型を一時的に変換して合わせます。
ON CAST(table_A.user_id AS STRING) = table_B.user_id
これにより、データ型が原因のJOIN失敗を防げます。これは実務で頻繁に遭遇する典型的な解決策です。
データベースにポイント数が'100'のように文字列として保存されていると、SUM()での合計やAVG()での平均計算ができません。
SELECT句でCAST()を使い、出力データの型をあらかじめ変換しておくことで、この問題を解決します。
firestoreで数字型として保存していても, JSON_VALUE()で抽出した値はデフォルトで文字列として扱われる ため、後続の計算(合計など)で正しく利用できるよう、数値型に変換します。
points列を文字列型から数値型(INT64)に変換して出力します。
SELECT
CAST(A.points AS INT64) AS points
FROM
table_A AS A
これは、クエリが返すデータを後続の分析や計算がしやすくするための、非常に重要なデータ整形術です。
SELECT: どの列のデータを取得するかを指定する句。クエリの心臓部です。FROM: データを取得する対象のテーブルを指定する句。AS: 列やテーブルに別名(エイリアス)を付けるキーワード。クエリを簡潔にし、結果の列名を分かりやすくします。LEFT JOIN: 左側のテーブルを基準に、右側のテーブルを結合する命令。左テーブルのデータは常に表示され、右側に一致するデータがない場合はNULLとなります。ON: JOINでテーブルを結合する際の、結合条件(どの列同士を紐付けるか)を指定する句。GROUP BY: 指定した列の値が同じ行を一つのグループにまとめる命令。集計処理の基本となります。ORDER BY: 取得した結果全体を、指定した列を基準に並べ替える命令。DESC: "descending"の略で、降順(大きいものから小さいものへ)に並べ替える際に使用します。GROUP BYでまとめられたグループに対して、合計値(SUM)、最大値(MAX)などを計算する関数の総称。MAX(): グループ内の最大値を選ぶ集約関数。数値だけでなく文字列にも使用でき、その場合は辞書順で最も後ろに来る値を選びます。SUM(): グループ内の数値データの合計を計算する集約関数。IF(): IF(条件, 真の場合の値, 偽の場合の値)という構造で、シンプルな2択の条件分岐を行う関数。CASE WHEN: 複数の条件分岐を記述するための構文。WHEN '条件' THEN '値'の形式で複数の条件を指定できます。IFNULL(): IFNULL(A, B)の形で使用し、「もしAがNULLでなければAを、NULLならば代わりにBを使う」という処理を行う関数。データの欠損を補うのに役立ちます。is null: ある列の値がNULL(空)であるかどうかを判定するための条件式。CAST(): ある列のデータ型を、別の型(例:文字列型を数値型)に一時的に変換する関数。INT64: 64ビットの整数を表すデータ型。STRING: 文字列を表すデータ型。JSON_VALUE(): JSON_VALUE(JSON列, 'パス')の形で使用し、JSON形式のデータの中から指定したキーの値を取り出す関数。JSON_VALUE()関数で、JSONデータ内のどの値を取り出すかを指定する文字列(例:'$.user.id')。$はJSONデータの根元を意味します。TIMESTAMP_MILLIS(): ミリ秒単位のUNIXタイムスタンプ(数値)を、SQLが日時として認識できるタイムスタンプ形式に変換する関数。DATETIME(): タイムスタンプ形式のデータを、人間が読める「YYYY-MM-DD hh:mm:ss」という形式の日時に変換する関数。