「MySQL - 集約関数」の版間の差分

提供: MochiuWiki : SUSE, EC, PCB

ページの作成:「== 概要 == MySQLでは、豊富な文字列関数が提供されており、文字列の結合、分割、検索、置換、フォーマット等の処理を行うことができる。<br> これらの関数は、SELECT文、UPDATE文、WHERE句等で使用でき、データ操作において重要な役割を果たす。<br> <br> MySQL 5.7とMySQL 8.0では、文字列関数の動作に一部差異がある。<br> 特に、文字コードと照合順序の扱い…」
 
 
(同じ利用者による、間の3版が非表示)
1行目: 1行目:
== 概要 ==
== 概要 ==
MySQLでは、豊富な文字列関数が提供されており、文字列の結合、分割、検索、置換、フォーマット等の処理を行うことができる。<br>
MySQLの集約関数 (Aggregate Functions) は、複数の行から単一の値を計算する関数である。<br>
これらの関数は、SELECT文、UPDATE文、WHERE句等で使用でき、データ操作において重要な役割を果たす。<br>
集約関数は、<code>GROUP BY</code> 句と組み合わせて使用することで、データをグループ化して集計を行うことができる。<br>
<br>
<br>
MySQL 5.7とMySQL 8.0では、文字列関数の動作に一部差異がある。<br>
主要な集約関数には、<code>COUNT</code>、<code>SUM</code>、<code>AVG</code>、<code>MAX</code><code>MIN</code><code>GROUP_CONCAT</code> が含まれる。<br>
特に、文字コードと照合順序の扱いに変更がある。<br>
また、MySQL 5.7.22以降では、<code>JSON_ARRAYAGG</code> および <code>JSON_OBJECTAGG</code> が追加されている。<br>
MySQL 8.0では、デフォルト文字セットが <code>utf8mb4</code>、デフォルト照合順序が <code>utf8mb4_0900_ai_ci</code> に変更された。<br>
MySQL 5.7では、デフォルト文字セットは <code>latin1</code>、デフォルト照合順序は <code>latin1_swedish_ci</code> であった。<br>
<br>
<br>
文字列関数は、マルチバイト文字セット (utf8mb4) を考慮して使用する必要がある。<br>
集約関数は、<code>NULL</code> 値を自動的に無視する特性を持つ (一部例外を除く)。<br>
<code>LENGTH</code> 関数 と <code>CHAR_LENGTH</code> 関数は、マルチバイト文字での動作が異なる。<br>
全ての値が <code>NULL</code> の場合、集約関数は <code>NULL</code> を返す。(<code>COUNT(*)</code> は、0を返す)<br>
<code>LENGTH</code> 関数はバイト長を返し、<code>CHAR_LENGTH</code> 関数は文字数を返す。<br>
<br>
<br>
UTF-8エンコーディングでは、日本語は1文字あたり3バイトで表現されるため、<code>LENGTH</code>関数の戻り値は文字数の3倍となる。<br>
MySQL 8.0以降では、集約関数を <code>OVER()</code> 句と組み合わせてウインドウ関数として使用できる。<br>
ウインドウ関数として使用した場合、行を削減せず各行が保持される。<br>
<br>
<br>
文字列関数をWHERE句で使用すると、インデックスが使用されない場合が多い。<br>
<code>GROUP BY</code> 句を使用する際は、MySQL 8.0のデフォルトSQLモードである <code>ONLY_FULL_GROUP_BY</code> に注意が必要である。<br>
パフォーマンスを考慮する場合は、関数を使用しないクエリ設計を検討する必要がある。<br>
このモードでは、<code>SELECT</code> リストの非集約列は全て <code>GROUP BY</code> 句に含まれるか、関数従属である必要がある。<br>
<br>
<br>
<u>TEXT型およびBLOB型での文字列関数使用は、処理速度が低下する傾向がある。</u><br>
集約関数のパフォーマンスは、インデックスの有無、データ量、<code>DISTINCT</code> の使用などに影響される。<br>
<u>大量のテキストデータを処理する場合は、VARCHAR型の使用を推奨する。</u><br>
<code>EXPLAIN</code> で実行計画を確認し、<code>Using temporary</code>、<code>Using filesort</code> をチェックすることが推奨される。<br>
<br><br>
<br><br>


== 文字列結合 ==
== 基本構文 ==
==== CONCAT ====
MySQLで提供される主要な集約関数を以下に示す。<br>
<code>CONCAT</code> 関数は、複数の文字列を結合する。<br>
<br>
<br>
基本構文を以下に示す。<br>
<center>
{| class="wikitable"
|+ 主要な集約関数一覧
! 関数名 !! 説明 !! 戻り値型
|-
| COUNT(*) || 全ての行数をカウント (NULL含む) || BIGINT
|-
| COUNT(column) || 指定列のNULL以外の値をカウント || BIGINT
|-
| COUNT(DISTINCT column) || 指定列の重複を除いた値をカウント || BIGINT
|-
| SUM(column) || 指定列の合計値を計算 (NULL除外) || DECIMAL または DOUBLE
|-
| SUM(DISTINCT column) || 指定列の重複を除いた合計値を計算 || DECIMAL または DOUBLE
|-
| AVG(column) || 指定列の平均値を計算 (NULL除外) || DECIMAL または DOUBLE
|-
| AVG(DISTINCT column) || 指定列の重複を除いた平均値を計算 || DECIMAL または DOUBLE
|-
| MAX(column) || 指定列の最大値を取得 || 列の型に依存
|-
| MIN(column) || 指定列の最小値を取得 || 列の型に依存
|-
| GROUP_CONCAT(column) || 指定列の値を連結した文字列を生成 || TEXT
|-
| JSON_ARRAYAGG(column) || 指定列の値をJSON配列として集約 (MySQL 5.7.22+) || JSON
|-
| JSON_OBJECTAGG(key, value) || キーと値のペアをJSONオブジェクトとして集約 (MySQL 5.7.22+) || JSON
|}
</center>
<br>
<br>
<syntaxhighlight lang="sql">
==== COUNT関数 ====
CONCAT(str1, str2, ...)
<code>COUNT</code> 関数は、行数をカウントする集約関数である。<br>
</syntaxhighlight>
<br>
<br>
引数のいずれかが <code>NULL</code> の場合、結果は <code>NULL</code> となる。<br>
基本的な使用例を以下に示す。<br>
これは、<code>CONCAT_WS</code> 関数との重要な違いである。<br>
  <syntaxhighlight lang="mysql">
<br>
-- 全ての行数をカウント (NULL含む)
使用例を以下に示す。<br>
  SELECT COUNT(*) FROM employees;
<br>
  <syntaxhighlight lang="sql">
  SELECT CONCAT('Hello', ' ', 'World');
-- 結果: 'Hello World'
   
   
  SELECT CONCAT('ID: ', id, ' Name: ', name) FROM users;
-- 指定列のNULL以外の値をカウント
  SELECT COUNT(department_id) FROM employees;
   
   
SELECT CONCAT('MySQL', NULL, 'Database');
  -- 重複を除いたカウント
  -- 結果: NULL
  SELECT COUNT(DISTINCT department_id) FROM employees;
</syntaxhighlight>
<br>
<code>CONCAT</code> 関数は、数値型の引数を文字列に自動変換する。<br>
<br>
==== CONCAT_WS ====
<code>CONCAT_WS</code> 関数は、セパレータを使用して複数の文字列を結合する。<br>
<br>
基本構文を以下に示す。<br>
<br>
<syntaxhighlight lang="sql">
CONCAT_WS(separator, str1, str2, ...)
</syntaxhighlight>
<br>
<code>CONCAT</code> 関数と異なり、<code>CONCAT_WS</code> 関数は、<code>NULL</code> の引数をスキップする。<br>
セパレータが <code>NULL</code> の場合のみ、結果は <code>NULL</code> となる。<br>
<br>
使用例を以下に示す。<br>
<br>
<syntaxhighlight lang="sql">
  SELECT CONCAT_WS(', ', 'Apple', 'Banana', 'Cherry');
-- 結果: 'Apple, Banana, Cherry'
   
   
  SELECT CONCAT_WS('-', '2025', '02', '15');
  -- 複数列の組み合わせで重複を除いたカウント
-- 結果: '2025-02-15'
  SELECT COUNT(DISTINCT department_id, job_id) FROM employees;
  SELECT CONCAT_WS(', ', 'Alice', NULL, 'Bob', 'Charlie');
-- 結果: 'Alice, Bob, Charlie' (NULLはスキップされる)
SELECT CONCAT_WS(NULL, 'Alice', 'Bob', 'Charlie');
-- 結果: NULL (セパレータがNULLのため)
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br>
<code>CONCAT_WS</code> 関数は、CSV形式のデータ生成に便利である。<br>
<code>COUNT(*)</code> <code>COUNT(column)</code> の違いを以下に示す。<br>
<br><br>
 
== 部分文字列の取得 ==
==== SUBSTRING / SUBSTR / MID ====
<code>SUBSTRING</code> 関数、<code>SUBSTR</code> 関数、<code>MID</code> 関数は、文字列から部分文字列を取得する。<br>
これらの関数は、同一の動作をする別名である。<br>
<br>
<br>
基本構文を以下に示す。<br>
<center>
<br>
{| class="wikitable"
<syntaxhighlight lang="sql">
|+ COUNT(*) と COUNT(column) の違い
SUBSTRING(str, pos)
|-
SUBSTRING(str, pos, len)
! 関数 !! 説明
SUBSTR(str, pos, len)
|-
MID(str, pos, len)
| COUNT(*) || 全ての行数をカウントする。<br><code>NULL</code> 値を含む全ての行を対象とする。
</syntaxhighlight>
|-
| COUNT(column) || 指定列の <code>NULL</code> 以外の値のみをカウントする。<br><code>NULL</code> 値は自動的に除外される。
|}
</center>
<br>
<br>
<center>
<center>
{| class="wikitable"
{| class="wikitable"
|+ 引数の意味
|+ パフォーマンス特性
|-
|-
! 引数 !! 説明
! ストレージエンジン !! 説明
|-
|-
| 第1引数 || 対象文字列
| InnoDB || <code>COUNT(*)</code> は、最小の利用可能なセカンダリインデックスをスキャンする。<br>プライマリキーより小さいセカンダリインデックスが存在する場合、そちらを優先的に使用する。
|-
|-
| 第2引数 || 開始位置 (1始まり、負の値は末尾から)
| MyISAM || 内部的に行数を保持しているため、<code>WHERE</code> 句なしの <code>COUNT(*)</code> は高速に実行される。
|-
| 第3引数 || 取得する文字数 (省略時は末尾まで)
|}
|}
</center>
</center>
<br>
<br>
使用例を以下に示す。<br>
戻り値の型は、MySQL 8.0以降では常に <code>BIGINT</code> である。<br>
<br>
<br>
  <syntaxhighlight lang="sql">
<code>COUNT(DISTINCT column1, column2)</code> のように複数列を指定した場合、列の組み合わせでユニークな行をカウントする。<br>
  SELECT SUBSTRING('HelloWorld', 6);
<br>
-- 結果: 'World'
==== SUM関数 ====
<code>SUM</code> 関数は、指定列の合計値を計算する集約関数である。<br>
<br>
基本的な使用例を以下に示す。<br>
<br>
  <syntaxhighlight lang="mysql">
-- 給与の合計
  SELECT SUM(salary) FROM employees;
   
   
  SELECT SUBSTRING('HelloWorld', 1, 5);
-- 部署ごとの給与合計
-- 結果: 'Hello'
  SELECT department_id, SUM(salary) FROM employees GROUP BY department_id;
   
   
SELECT SUBSTRING('HelloWorld', -5);
  -- 重複を除いた合計
  -- 結果: 'World' (末尾から5文字)
  SELECT SUM(DISTINCT salary) FROM employees;
  SELECT SUBSTRING('HelloWorld', -5, 3);
-- 結果: 'Wor' (末尾から5文字目から3文字)
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br>
負の位置指定により、末尾からの取得が可能である。<br>
<code>NULL</code> 値の処理を以下に示す。<br>
* <code>NULL</code> 値は自動的に除外される。
* 全ての値が <code>NULL</code> の場合、<code>SUM</code> は <code>NULL</code> を返す。
* <code>COUNT(column)</code> が0の場合、<code>SUM</code> は <code>NULL</code> を返す。
<br>
<br>
==== LEFT / RIGHT ====
戻り値の型を以下に示す。<br>
<code>LEFT</code> 関数は、文字列の左端から指定文字数を取得する。<br>
* 整数型または DECIMAL型の引数
<code>RIGHT</code> 関数は、文字列の右端から指定文字数を取得する。<br>
*: <code>DECIMAL</code> 型を返す。
* FLOAT型または DOUBLE型の引数
*: <code>DOUBLE</code> 型を返す。
<br>
<br>
基本構文を以下に示す。<br>
<u>※注意</u><br>
* <u>数値型の列にのみ適用可能</u>
*: 時間型 (TIME、DATE、DATETIME) に直接適用できない。
*: <code>TIME_TO_SEC()</code> 等で事前に数値に変換する必要がある。
* <u>オーバーフロー</u>
*: 整数型の合計が型の範囲を超える場合、オーバーフローが発生する可能性がある。
*: <code>DECIMAL</code> 型または <code>BIGINT</code> 型を使用することを推奨する。
<br>
<br>
  <syntaxhighlight lang="sql">
<code>SUM(DISTINCT column)</code> を使用すると、重複する値を除いて合計を計算する。<br>
  LEFT(str, len)
<br>
  RIGHT(str, len)
時間型の合計を計算する例を以下に示す。<br>
<br>
  <syntaxhighlight lang="mysql">
  -- TIME型の合計 (秒に変換)
  SELECT SEC_TO_TIME(SUM(TIME_TO_SEC(work_time))) FROM daily_records;
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br>
使用例を以下に示す。<br>
==== AVG関数 ====
<code>AVG</code> 関数は、指定列の平均値を計算する集約関数である。<br>
<br>
<br>
  <syntaxhighlight lang="sql">
基本的な使用例を以下に示す。<br>
SELECT LEFT('HelloWorld', 5);
  <syntaxhighlight lang="mysql">
  -- 結果: 'Hello'
  -- 給与の平均
  SELECT AVG(salary) FROM employees;
  SELECT RIGHT('HelloWorld', 5);
-- 結果: 'World'
   
   
  SELECT LEFT('2025-02-15', 4);
  -- 部署ごとの給与平均
  -- 結果: '2025' (年を取得)
  SELECT department_id, AVG(salary) FROM employees GROUP BY department_id;
   
   
  SELECT RIGHT('2025-02-15', 2);
  -- 重複を除いた平均
  -- 結果: '15' (日を取得)
  SELECT AVG(DISTINCT salary) FROM employees;
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br>
これらの関数は、<code>SUBSTRING</code> 関数の簡易版である。<br>
内部的な計算方法を以下に示す。<br>
* <code>AVG(column)</code> は、<code>SUM(column) / COUNT(column)</code> と同等である。
* <code>NULL</code> 値は自動的に除外される。
<br>
<br>
==== SUBSTRING_INDEX ====
戻り値の型を以下に示す。<br>
<code>SUBSTRING_INDEX</code> 関数は、デリミタを基準に文字列を分割し、指定された部分を取得する。<br>
* 整数型または DECIMAL型の引数
*: <code>DECIMAL(14, 4)</code> 型を返す。
* FLOAT型または DOUBLE型の引数
*: <code>DOUBLE</code> 型を返す。
<br>
<br>
基本構文を以下に示す。<br>
精度に関する注意を以下に示す。<br>
* 整数型の平均を計算する場合、小数点以下が切り捨てられない
*: <code>DECIMAL(14, 4)</code> で正確な小数値が返される。
* 丸め処理
*: <code>ROUND(AVG(column), 2)</code> で任意の桁数に丸めることができる。
<br>
<br>
<syntaxhighlight lang="sql">
<code>AVG(DISTINCT column)</code> を使用すると、重複する値を除いて平均を計算する。<br>
SUBSTRING_INDEX(str, delim, count)
</syntaxhighlight>
<br>
<br>
<center>
丸め処理の例を以下に示す。<br>
{| class="wikitable"
|+ 引数の意味
|-
! 引数 !! 説明
|-
| 第1引数 || 対象文字列
|-
| 第2引数 || デリミタ文字列
|-
| 第3引数 || デリミタの出現回数 (正数は左から、負数は右から)
|}
</center>
<br>
使用例を以下に示す。<br>
<br>
<br>
  <syntaxhighlight lang="sql">
  <syntaxhighlight lang="mysql">
  SELECT SUBSTRING_INDEX('192.168.1.100', '.', 2);
-- 小数点以下2桁に丸める
-- 結果: '192.168' (左から2番目のピリオドまで)
  SELECT ROUND(AVG(salary), 2) FROM employees;
   
   
  SELECT SUBSTRING_INDEX('192.168.1.100', '.', -2);
  -- 整数に切り捨て
  -- 結果: '1.100' (右から2番目のピリオドまで)
  SELECT FLOOR(AVG(salary)) FROM employees;
   
   
  SELECT SUBSTRING_INDEX('user@example.com', '@', 1);
-- 整数に切り上げ
  -- 結果: 'user' (ユーザ名部分を取得)
  SELECT CEIL(AVG(salary)) FROM employees;
</syntaxhighlight>
<br>
==== MAX / MIN関数 ====
<code>MAX</code> 関数 および <code>MIN</code> 関数は、指定列の最大値および最小値を取得する集約関数である。<br>
<br>
基本的な使用例を以下に示す。<br>
<syntaxhighlight lang="mysql">
  -- 最大給与
SELECT MAX(salary) FROM employees;
   
   
  SELECT SUBSTRING_INDEX('user@example.com', '@', -1);
  -- 最小給与
  -- 結果: 'example.com' (ドメイン部分を取得)
  SELECT MIN(salary) FROM employees;
   
   
  SELECT SUBSTRING_INDEX(SUBSTRING_INDEX('192.168.1.100', '.', 3), '.', -1);
-- 部署ごとの最大給与・最小給与
  -- 結果: '1' (3番目のオクテットを取得)
  SELECT department_id, MAX(salary), MIN(salary)
  FROM employees
GROUP BY department_id;
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br>
ネストして使用することで、複雑な分割が可能である。<br>
数値型以外への適用を以下に示す。<br>
<br><br>
* 文字列型
 
*: 辞書順 (照合順序に依存) で比較される。
== 文字列置換・挿入 ==
*: 照合順序 (COLLATION) によって結果が異なる場合がある。
==== REPLACE ====
* 日付型 (DATE、DATETIME、TIMESTAMP)
<code>REPLACE</code> 関数は、文字列内のすべての出現を置換する。<br>
*: 時系列順で比較される。
*: 最も古い日付 (<code>MIN</code>)、最も新しい日付 (<code>MAX</code>) を取得できる。
<br>
インデックスとの関係を以下に示す。<br>
* インデックスが存在する列に対して <code>MAX</code> / <code>MIN</code> を使用する場合、インデックスを活用した高速な検索が可能である。
* これは、<code>ORDER BY column DESC LIMIT 1</code> (<code>MAX</code>) や <code>ORDER BY column ASC LIMIT 1</code> (<code>MIN</code>) と同等の最適化が行われるためである。
<br>
<code>NULL</code> 値の処理を以下に示す。<br>
* <code>NULL</code> 値は常に無視される。
* 全ての値が <code>NULL</code> の場合、<code>MAX</code> / <code>MIN</code> は <code>NULL</code> を返す。
<br>
文字列型への適用例を以下に示す。<br>
<br>
<syntaxhighlight lang="mysql">
-- 辞書順で最初の名前
SELECT MIN(first_name) FROM employees;
-- 辞書順で最後の名前
SELECT MAX(first_name) FROM employees;
</syntaxhighlight>
<br>
<br>
基本構文を以下に示す。<br>
日付型への適用例を以下に示す。<br>
<br>
<br>
  <syntaxhighlight lang="sql">
  <syntaxhighlight lang="mysql">
  REPLACE(str, from_str, to_str)
  -- 最も古い入社日
SELECT MIN(hire_date) FROM employees;
-- 最も新しい入社日
SELECT MAX(hire_date) FROM employees;
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br>
<code>REPLACE</code> 関数は、大文字小文字を区別する。<br>
==== GROUP_CONCAT関数 ====
照合順序が大文字小文字を区別しない場合でも、<code>REPLACE</code> 関数は区別する。<br>
<code>GROUP_CONCAT</code> 関数は、グループ内の値を連結した文字列を生成する集約関数である。<br>
<br>
<br>
使用例を以下に示す。<br>
基本的な使用例を以下に示す。<br>
<br>
<br>
  <syntaxhighlight lang="sql">
  <syntaxhighlight lang="mysql">
  SELECT REPLACE('Hello World', 'World', 'MySQL');
-- 部署ごとの従業員名を連結
  -- 結果: 'Hello MySQL'
  SELECT department_id, GROUP_CONCAT(first_name)
  FROM employees
GROUP BY department_id;
   
   
  SELECT REPLACE('192.168.1.100', '.', '-');
-- セパレータを指定
  -- 結果: '192-168-1-100'
  SELECT department_id, GROUP_CONCAT(first_name SEPARATOR '; ')
  FROM employees
GROUP BY department_id;
   
   
  SELECT REPLACE('apple,apple,orange', 'apple', 'banana');
-- ORDER BY句でソート
  -- 結果: 'banana,banana,orange' (すべての出現を置換)
  SELECT department_id, GROUP_CONCAT(first_name ORDER BY first_name ASC)
  FROM employees
GROUP BY department_id;
   
   
  UPDATE products SET description = REPLACE(description, 'old_term', 'new_term');
  -- 重複を除外
SELECT department_id, GROUP_CONCAT(DISTINCT job_id)
FROM employees
GROUP BY department_id;
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br>
全ての出現が置換される点に注意する必要がある。<br>
オプション句を以下に示す。<br>
* SEPARATOR句
*: 連結時のセパレータを指定する。
*: デフォルトはカンマ (<code>,</code>) である。
*: <code>SEPARATOR ''</code> でセパレータなしの連結も可能である。
* ORDER BY句
*: 連結前に値をソートする。
*: <code>ORDER BY column ASC</code> または <code>ORDER BY column DESC</code> を指定できる。
* DISTINCT
*: 重複する値を除外してから連結する。
<br>
<br>
==== INSERT ====
出力制限に関する注意を以下に示す。<br>
<code>INSERT</code> 関数は、指定位置の文字列を新しい文字列で置換する。<br>
* group_concat_max_len システム変数
*: <code>GROUP_CONCAT</code> の出力最大長を制御する (デフォルト1024バイト)。
*: この制限を超える場合、無警告で切り詰められる。
*: <code>SET SESSION group_concat_max_len = 1000000;</code> で拡張可能である。
* max_allowed_packet
*: 最大パケットサイズも制限要因となる。
<br>
<br>
基本構文を以下に示す。<br>
<code>NULL</code> 値の処理を以下に示す。<br>
* <code>NULL</code> 値は自動的に除外される。
* 全ての値が <code>NULL</code> の場合、<code>GROUP_CONCAT</code> は <code>NULL</code> を返す。
<br>
<br>
  <syntaxhighlight lang="sql">
複数列の連結例を以下に示す。<br>
  INSERT(str, pos, len, newstr)
<br>
  <syntaxhighlight lang="mysql">
  -- 名前と役職を組み合わせて連結
SELECT department_id,
        GROUP_CONCAT(CONCAT(first_name, ' (', job_id, ')') ORDER BY first_name)
FROM employees
GROUP BY department_id;
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br>
<center>
出力制限の設定例を以下に示す。<br>
{| class="wikitable"
|+ 引数の意味
|-
! 引数 !! 説明
|-
| 第1引数 || 対象文字列
|-
| 第2引数 || 開始位置 (1始まり)
|-
| 第3引数 || 置換する文字数
|-
| 第4引数 || 挿入する文字列
|}
</center>
<br>
<br>
使用例を以下に示す。<br>
  <syntaxhighlight lang="mysql">
<br>
  -- セッション単位で制限を拡張
  <syntaxhighlight lang="sql">
  SET SESSION group_concat_max_len = 1000000;
  SELECT INSERT('HelloWorld', 6, 5, 'MySQL');
  -- 結果: 'HelloMySQL'
   
   
SELECT INSERT('12345', 3, 0, 'XX');
  -- グローバルに制限を拡張
  -- 結果: '12XX345' (文字を挿入、置換なし)
  SET GLOBAL group_concat_max_len = 1000000;
   
SELECT INSERT('abcdefg', 2, 3, 'XX');
-- 結果: 'aXXefg' (2文字目から3文字を'XX'で置換)
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br>
第3引数に0を指定した場合は、文字列の挿入のみが行われる。<br>
==== JSON_ARRAYAGG / JSON_OBJECTAGG ====
<br><br>
<code>JSON_ARRAYAGG</code> 関数 および <code>JSON_OBJECTAGG</code> 関数は、MySQL 5.7.22以降で使用可能なJSON集約関数である。<br>
 
== 空白除去・パディング ==
==== TRIM / LTRIM / RTRIM ====
<code>TRIM</code> 関数は、文字列の両端から指定文字を除去する。<br>
<code>LTRIM</code> 関数は、文字列の左端から空白を除去する。<br>
<code>RTRIM</code> 関数は、文字列の右端から空白を除去する。<br>
<br>
<br>
基本構文を以下に示す。<br>
===== JSON_ARRAYAGG =====
<code>JSON_ARRAYAGG</code> 関数は、グループ内の値をJSON配列として集約する。<br>
<br>
<br>
  <syntaxhighlight lang="sql">
基本的な使用例を以下に示す。<br>
  TRIM([{BOTH | LEADING | TRAILING} [remstr] FROM] str)
  <syntaxhighlight lang="mysql">
  LTRIM(str)
  -- 部署ごとの従業員名をJSON配列として集約
  RTRIM(str)
SELECT department_id, JSON_ARRAYAGG(first_name)
FROM employees
GROUP BY department_id;
  -- 結果例
  -- {"department_id": 10, "names": ["John", "Jane", "Bob"]}
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br>
<center>
<code>GROUP_CONCAT</code> との違いを以下に示す。<br>
{| class="wikitable"
* NULL値の扱い
|+ <code>TRIM</code>関数のオプション
*: <code>JSON_ARRAYAGG</code> は、<code>NULL</code> 値も配列要素として含める。
|-
*: <code>GROUP_CONCAT</code> は、<code>NULL</code> 値を無視する。
! オプション !! 説明
* 戻り値の型
|-
*: <code>JSON_ARRAYAGG</code> は、JSON配列型を返す。
| <code>BOTH</code> || 両端から除去 (デフォルト)
*: <code>GROUP_CONCAT</code> は、文字列 (TEXT型) を返す。
|-
| <code>LEADING</code> || 左端から除去
|-
| <code>TRAILING</code> || 右端から除去
|-
| <code>remstr</code> || 除去する文字列 (デフォルトは空白)
|}
</center>
<br>
<br>
使用例を以下に示す。<br>
NULL値を含む例を以下に示す。<br>
<br>
<br>
  <syntaxhighlight lang="sql">
  <syntaxhighlight lang="mysql">
SELECT TRIM('  Hello World  ');
  -- NULL値も配列要素として含まれる
-- 結果: 'Hello World'
  SELECT JSON_ARRAYAGG(commission_pct) FROM employees;
SELECT LTRIM('  Hello World  ');
  -- 結果: 'Hello World  '
  SELECT RTRIM('  Hello World  ');
-- 結果: '  Hello World'
SELECT TRIM(BOTH 'x' FROM 'xxxHelloxxx');
-- 結果: 'Hello'
   
   
SELECT TRIM(LEADING '0' FROM '000123');
  -- 結果例: [0.2, 0.3, null, null, 0.15]
  -- 結果: '123'
SELECT TRIM(TRAILING '.' FROM 'example.com...');
-- 結果: 'example.com'
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br>
<code>TRIM</code> 関数は、ユーザ入力データのクリーニングに有用である。<br>
===== JSON_OBJECTAGG =====
<code>JSON_OBJECTAGG</code> 関数は、キーと値のペアをJSONオブジェクトとして集約する。<br>
<br>
<br>
==== LPAD / RPAD ====
基本的な使用例を以下に示す。<br>
<code>LPAD</code> 関数は、文字列の左側を指定文字でパディングする。<br>
<code>RPAD</code> 関数は、文字列の右側を指定文字でパディングする。<br>
<br>
<br>
基本構文を以下に示す。<br>
  <syntaxhighlight lang="mysql">
<br>
  -- 従業員IDと名前のペアをJSONオブジェクトとして集約
  <syntaxhighlight lang="sql">
SELECT department_id, JSON_OBJECTAGG(employee_id, first_name)
  LPAD(str, len, padstr)
  FROM employees
  RPAD(str, len, padstr)
GROUP BY department_id;
-- 結果例
-- {"department_id": 10, "employees": {"100": "John", "101": "Jane"}}
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br>
<center>
キー重複時の動作を以下に示す。<br>
{| class="wikitable"
* 同じキーが複数回現れる場合、最後の値で上書きされる。
|+ 引数の意味
* どの値が最後になるかは、行の順序に依存する。
|-
* 明示的に <code>ORDER BY</code> 句を使用することで、順序を制御できる。
! 引数 !! 説明
|-
| 第1引数 || 対象文字列
|-
| 第2引数 || 結果の文字列長
|-
| 第3引数 || パディング文字列
|}
</center>
<br>
<br>
使用例を以下に示す。<br>
キー重複の例を以下に示す。<br>
<br>
<br>
  <syntaxhighlight lang="sql">
  <syntaxhighlight lang="mysql">
SELECT LPAD('123', 6, '0');
  -- 同じキーが複数回現れる場合、最後の値で上書き
  -- 結果: '000123'
  SELECT JSON_OBJECTAGG(job_id, first_name)
  FROM employees;
  SELECT RPAD('Hello', 10, '.');
-- 結果: 'Hello.....'
  SELECT LPAD('ID', 10, '-');
-- 結果: '--------ID'
   
   
  SELECT RPAD('Name', 20, ' ');
  -- 結果例: {"CLERK": "Sarah", "MANAGER": "Bob"}
  -- 結果: 'Name                ' (空白でパディング)
  -- (CLERKやMANAGERが複数いる場合、最後の行の値が使用される)
  </syntaxhighlight>
  </syntaxhighlight>
<br>
元の文字列が指定長より長い場合、切り詰められる。<br>
<br><br>
<br><br>


== 大文字・小文字変換 ==
== GROUP BYとの組み合わせ ==
<code>UPPER</code> 関数 および <code>UCASE</code> 関数は、文字列を大文字に変換する。<br>
==== 基本的な使い方 ====
<code>LOWER</code> 関数 および <code>LCASE</code> 関数は、文字列を小文字に変換する。<br>
<code>GROUP BY</code> 句は、集約関数と組み合わせて使用することにより、データをグループ化して集計を行う。<br>
<br>
基本構文を以下に示す。<br>
<br>
<syntaxhighlight lang="sql">
UPPER(str)
UCASE(str)
LOWER(str)
LCASE(str)
</syntaxhighlight>
<br>
<br>
<code>UPPER</code> 関数 と <code>UCASE</code> 関数は同一である。<br>
基本的な使用例を以下に示す。<br>
<code>LOWER</code> 関数 と <code>LCASE</code> 関数は同一である。<br>
<br>
<br>
使用例を以下に示す。<br>
  <syntaxhighlight lang="mysql">
<br>
-- 部署ごとの従業員数
  <syntaxhighlight lang="sql">
  SELECT department_id, COUNT(*)
  SELECT UPPER('Hello World');
  FROM employees
  -- 結果: 'HELLO WORLD'
GROUP BY department_id;
   
   
  SELECT LOWER('Hello World');
-- 部署ごとの平均給与
  -- 結果: 'hello world'
  SELECT department_id, AVG(salary)
  FROM employees
GROUP BY department_id;
   
   
SELECT UCASE('mysql database');
  -- 役職ごとの最大給与・最小給与
  -- 結果: 'MYSQL DATABASE'
  SELECT job_id, MAX(salary), MIN(salary)
  FROM employees
  SELECT LCASE('MYSQL DATABASE');
GROUP BY job_id;
  -- 結果: 'mysql database'
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br>
バイナリ文字列 (BINARY, VARBINARY, BLOB) に対しては効果がない。<br>
複数列でのグループ化を以下に示す。<br>
<br>
<br>
照合順序によっては、大文字小文字変換が期待通りに動作しない場合がある。<br>
<syntaxhighlight lang="mysql">
<code>utf8mb4_bin</code> 等のバイナリ照合順序では、ASCIIアルファベット以外の変換が行われない場合がある。<br>
-- 部署と役職の組み合わせでグループ化
<br><br>
SELECT department_id, job_id, COUNT(*), AVG(salary)
 
FROM employees
== 文字列検索 ==
GROUP BY department_id, job_id;
==== LOCATE / POSITION ====
</syntaxhighlight>
<code>LOCATE</code> 関数 および <code>POSITION</code> 関数は、部分文字列の位置を検索する。<br>
<br>
<br>
基本構文を以下に示す。<br>
<code>WHERE</code> 句との組み合わせを以下に示す。<br>
<br>
<br>
  <syntaxhighlight lang="sql">
  <syntaxhighlight lang="mysql">
  LOCATE(substr, str)
  -- 給与が5000以上の従業員を対象に部署ごとの平均給与を計算
  LOCATE(substr, str, pos)
SELECT department_id, AVG(salary)
  POSITION(substr IN str)
  FROM employees
WHERE salary >= 5000
  GROUP BY department_id;
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br>
<center>
実行順序を以下に示す。<br>
{| class="wikitable"
# WHERE句でフィルタリング (グループ化前)
|+ 引数の意味
# GROUP BY句でグループ化
|-
# 集約関数で集計
! 引数 !! 説明
# HAVING句でフィルタリング (グループ化後)
|-
# ORDER BY句でソート
| 第1引数 || 検索する部分文字列
# LIMIT句で件数制限
|-
| 第2引数 || 対象文字列
|-
| 第3引数 || 検索開始位置 (省略時は1)
|}
</center>
<br>
<br>
戻り値は、部分文字列が最初に出現する位置 (1始まり) である。<br>
==== WITH ROLLUP ====
見つからない場合は0を返す。<br>
<code>WITH ROLLUP</code> 修飾子は、小計と総計を含む追加行を生成する。<br>
<br>
<br>
使用例を以下に示す。<br>
基本的な使用例を以下に示す。<br>
<br>
<br>
  <syntaxhighlight lang="sql">
  <syntaxhighlight lang="mysql">
SELECT LOCATE('World', 'Hello World');
  -- 部署ごとの従業員数と総計
  -- 結果: 7
  SELECT department_id, COUNT(*)
  FROM employees
  SELECT POSITION('MySQL' IN 'Hello MySQL Database');
  GROUP BY department_id WITH ROLLUP;
-- 結果: 7
SELECT LOCATE('o', 'Hello World');
-- 結果: 5 (最初の'o'の位置)
   
  SELECT LOCATE('o', 'Hello World', 6);
-- 結果: 8 (6文字目以降で検索)
   
   
  SELECT LOCATE('xyz', 'Hello World');
  -- 結果例:
  -- 結果: 0 (見つからない)
-- +---------------+----------+
-- | department_id | COUNT(*) |
-- +---------------+----------+
-- |            10 |        5 |
-- |            20 |        8 |
-- |            30 |      12 |
-- |          NULL |      25 |  -- 総計行
  -- +---------------+----------+
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br>
<code>POSITION</code> 関数は、SQL標準構文である。<br>
複数列でのロールアップを以下に示す。<br>
<br>
<br>
==== INSTR ====
<syntaxhighlight lang="mysql">
<code>INSTR</code> 関数は、<code>LOCATE</code> 関数と同様に部分文字列の位置を検索する。<br>
-- 部署と役職の組み合わせでロールアップ
引数の順序が <code>LOCATE</code> 関数と逆である点が異なる。<br>
SELECT department_id, job_id, COUNT(*)
FROM employees
GROUP BY department_id, job_id WITH ROLLUP;
-- 結果例:
-- +---------------+---------+----------+
-- | department_id | job_id  | COUNT(*) |
-- +---------------+---------+----------+
-- |            10 | CLERK  |        3 |
-- |            10 | MANAGER |        2 |
-- |            10 | NULL    |        5 |  -- 部署10の小計
-- |            20 | CLERK  |        5 |
-- |            20 | ANALYST |        3 |
-- |            20 | NULL    |        8 |  -- 部署20の小計
-- |          NULL | NULL    |      13 |  -- 総計
-- +---------------+---------+----------+
</syntaxhighlight>
<br>
<br>
基本構文を以下に示す。<br>
<code>GROUPING</code> 関数を以下に示す。<br>
<code>GROUPING</code> 関数は、<code>NULL</code> 値が通常のグループ値か、小計行かを区別するために使用する。<br>
<br>
<br>
  <syntaxhighlight lang="sql">
  <syntaxhighlight lang="mysql">
  INSTR(str, substr)
  -- GROUPING関数で小計行を識別
SELECT department_id,
        job_id,
        COUNT(*),
        GROUPING(department_id) AS dept_grouping,
        GROUPING(job_id) AS job_grouping
FROM employees
GROUP BY department_id, job_id WITH ROLLUP;
-- GROUPING関数の戻り値:
-- 0 = 通常のグループ値
-- 1 = 小計行 (ROLLUPによって生成されたNULL)
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br>
使用例を以下に示す。<br>
小計行のラベル付け例を以下に示す。<br>
<br>
<br>
  <syntaxhighlight lang="sql">
  <syntaxhighlight lang="mysql">
  SELECT INSTR('Hello World', 'World');
-- 小計行にラベルを付ける
-- 結果: 7
  SELECT IF(GROUPING(department_id), 'Total', department_id) AS department,
        IF(GROUPING(job_id), 'Subtotal', job_id) AS job,
SELECT INSTR('Hello World', 'o');
        COUNT(*)
-- 結果: 5
  FROM employees
   
  GROUP BY department_id, job_id WITH ROLLUP;
  SELECT INSTR('Hello World', 'xyz');
-- 結果: 0
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br>
<code>INSTR</code> 関数は、検索開始位置を指定できない。<br>
==== ONLY_FULL_GROUP_BY モード ====
検索開始位置を指定する場合は、<code>LOCATE</code> 関数を使用する。<br>
<code>ONLY_FULL_GROUP_BY</code> は、MySQL 8.0のデフォルトSQLモードに含まれる厳格なグループ化ルールである。<br>
<br>
<br>
==== FIND_IN_SET ====
基本的なルールを以下に示す。<br>
<code>FIND_IN_SET</code> 関数は、カンマ区切りリスト内での文字列の位置を検索する。<br>
* <code>SELECT</code> リストの非集約列は、全て <code>GROUP BY</code> 句に含まれる必要がある。
* または、非集約列が <code>GROUP BY</code> 列に関数従属である必要がある。
<br>
<br>
基本構文を以下に示す。<br>
エラーの例を以下に示す。<br>
<br>
<br>
  <syntaxhighlight lang="sql">
  <syntaxhighlight lang="mysql">
  FIND_IN_SET(str, strlist)
  -- エラー: first_name が GROUP BY に含まれていない
SELECT department_id, first_name, COUNT(*)
FROM employees
GROUP BY department_id;
-- エラーメッセージ:
-- ERROR 1055 (42000): Expression #2 of SELECT list is not in GROUP BY clause
-- and contains nonaggregated column 'employees.first_name' which is not
-- functionally dependent on columns in GROUP BY clause
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br>
<center>
正しい書き方を以下に示す。<br>
{| class="wikitable"
|+ 引数の意味
|-
! 引数 !! 説明
|-
| 第1引数 || 検索する文字列
|-
| 第2引数 || カンマ区切りの文字列リスト
|}
</center>
<br>
戻り値は、リスト内での位置 (1始まり) である。<br>
見つからない場合は0を返す。<br>
<br>
<br>
使用例を以下に示す。<br>
  <syntaxhighlight lang="mysql">
<br>
-- 正: first_name を GROUP BY に含める
  <syntaxhighlight lang="sql">
  SELECT department_id, first_name, COUNT(*)
  SELECT FIND_IN_SET('b', 'a,b,c,d');
  FROM employees
  -- 結果: 2
GROUP BY department_id, first_name;
   
   
SELECT FIND_IN_SET('MySQL', 'Oracle,MySQL,PostgreSQL');
  -- : first_name を集約関数に含める
  -- 結果: 2
  SELECT department_id, GROUP_CONCAT(first_name), COUNT(*)
  FROM employees
  SELECT FIND_IN_SET('x', 'a,b,c,d');
  GROUP BY department_id;
-- 結果: 0
   
  SELECT * FROM users WHERE FIND_IN_SET('admin', roles) > 0;
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br>
<code>FIND_IN_SET</code> 関数は、SET型カラムの検索に有用である。<br>
<code>ANY_VALUE</code> 関数を以下に示す。<br>
<code>ANY_VALUE</code> 関数は、グループ内の任意の値を返す関数である。<br>
<code>ONLY_FULL_GROUP_BY</code> モードでも、<code>ANY_VALUE</code> を使用することで非集約列を <code>SELECT</code> リストに含めることができる。<br>
<br>
<br>
==== FIELD / ELT ====
<syntaxhighlight lang="mysql">
<code>FIELD</code> 関数は、引数リスト内での文字列の位置を返す。<br>
-- ANY_VALUE関数で回避
<code>ELT</code> 関数は、指定位置の要素を返す。<br>
SELECT department_id, ANY_VALUE(first_name), COUNT(*)
これらは、互いに補関数の関係にある。<br>
FROM employees
GROUP BY department_id;
-- 注意: どの値が返されるかは不定である
</syntaxhighlight>
<br>
<br>
基本構文を以下に示す。<br>
<code>ONLY_FULL_GROUP_BY</code> モードの無効化を以下に示す。<br>
<br>
<br>
  <syntaxhighlight lang="sql">
  <syntaxhighlight lang="mysql">
  FIELD(str, str1, str2, str3, ...)
  -- セッション単位で無効化
  ELT(N, str1, str2, str3, ...)
SET SESSION sql_mode = (SELECT REPLACE(@@sql_mode, 'ONLY_FULL_GROUP_BY', ''));
-- グローバルに無効化
  SET GLOBAL sql_mode = (SELECT REPLACE(@@sql_mode, 'ONLY_FULL_GROUP_BY', ''));
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br>
使用例を以下に示す。<br>
関数従属の例を以下に示す。<br>
<br>
<br>
  <syntaxhighlight lang="sql">
  <syntaxhighlight lang="mysql">
SELECT FIELD('MySQL', 'Oracle', 'MySQL', 'PostgreSQL');
  -- employee_id がプライマリキーの場合、他の列は関数従属
  -- 結果: 2
  SELECT employee_id, first_name, last_name, COUNT(*)
  FROM employees
  SELECT ELT(2, 'Oracle', 'MySQL', 'PostgreSQL');
GROUP BY employee_id;
  -- 結果: 'MySQL'
   
   
SELECT FIELD('xyz', 'a', 'b', 'c');
  -- employee_id がプライマリキーであるため、first_name と last_name は
-- 結果: 0 (見つからない)
  -- employee_id に関数従属しており、エラーにならない
SELECT ELT(0, 'a', 'b', 'c');
  -- 結果: NULL (位置0は無効)
SELECT ELT(FIELD('b', 'a', 'b', 'c'), 'a', 'b', 'c');
  -- 結果: 'b' (FIELD/ELTの組み合わせ)
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<code>FIELD</code> 関数は、カスタムソート順序の実装に使用できる。<br>
<br><br>
<br><br>


== 文字列長 ==
== HAVING句との組み合わせ ==
<code>LENGTH</code> 関数、<code>CHAR_LENGTH</code> 関数、<code>BIT_LENGTH</code> 関数は、文字列の長さを取得する。<br>
<code>HAVING</code> 句は、グループ化後の結果に対してフィルタリングを行う。<br>
これらの関数は、異なる単位で長さを返す。<br>
<br>
<br>
基本構文を以下に示す。<br>
<code>WHERE</code> 句 と <code>HAVING</code> 句の違いを以下に示す。<br>
<br>
<syntaxhighlight lang="sql">
LENGTH(str)
CHAR_LENGTH(str)
CHARACTER_LENGTH(str)
BIT_LENGTH(str)
OCTET_LENGTH(str)
</syntaxhighlight>
<br>
下表に、各関数の戻り値の単位を示す。<br>
<br>
<br>
<center>
<center>
{| class="wikitable"
{| class="wikitable"
|+ 文字列長関数の比較
|+ WHERE句とHAVING句の違い
! 関数 !! 単位 !! 説明
|-
|-
| LENGTH || バイト || バイト長 (マルチバイト文字は複数バイト)
! 項目 !! WHERE句 !! HAVING句
|-
|-
| CHAR_LENGTH || 文字 || 文字数
| フィルタリングのタイミング || グループ化前 || グループ化後
|-
|-
| CHARACTER_LENGTH || 文字 || CHAR_LENGTHの別名
| 条件の対象 || 個々の行 || グループ
|-
|-
| BIT_LENGTH || ビット || ビット長 (LENGTH * 8)
| 集約関数の使用 || 使用できない || 使用できる
|-
| OCTET_LENGTH || バイト || LENGTHの別名
|}
|}
</center>
</center>
<br>
<br>
マルチバイト文字セットでの動作の違いを以下に示す。<br>
基本的な使用例を以下に示す。<br>
<br>
<br>
  <syntaxhighlight lang="sql">
  <syntaxhighlight lang="mysql">
SELECT LENGTH('Hello');
  -- 従業員数が5人以上の部署のみを抽出
  -- 結果: 5 (ASCII文字は1バイト/文字)
  SELECT department_id, COUNT(*)
  FROM employees
  SELECT CHAR_LENGTH('Hello');
  GROUP BY department_id
-- 結果: 5
  HAVING COUNT(*) >= 5;
SELECT LENGTH('こんにちは');
  -- 結果: 15 (UTF-8では日本語は3バイト/文字)
   
  SELECT CHAR_LENGTH('こんにちは');
-- 結果: 5 (文字数)
SELECT BIT_LENGTH('Hello');
-- 結果: 40 (5バイト * 8ビット)
SELECT LENGTH('MySQL') = CHAR_LENGTH('MySQL');
-- 結果: 1 (ASCIIのみの場合は一致)
   
   
  SELECT LENGTH('データベース') = CHAR_LENGTH('データベース');
-- 平均給与が7000以上の部署のみを抽出
  -- 結果: 0 (マルチバイト文字の場合は不一致)
  SELECT department_id, AVG(salary)
  FROM employees
GROUP BY department_id
HAVING AVG(salary) >= 7000;
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br>
<u>VARCHAR型の文字数制限は、バイト数ではなく文字数で指定される。</u><br>
<code>WHERE</code> 句 と <code>HAVING</code> 句の組み合わせを以下に示す。<br>
例えば、VARCHAR(100) は、100文字まで格納できる。<br>
UTF-8エンコーディングでは、日本語100文字は最大300バイトとなる。<br>
<br>
WHERE句での文字列長検査には、<code>CHAR_LENGTH</code> 関数を使用することを推奨する。<br>
<br>
<br>
  <syntaxhighlight lang="sql">
  <syntaxhighlight lang="mysql">
  SELECT * FROM articles WHERE CHAR_LENGTH(title) > 50;
-- 給与が5000以上の従業員を対象に、
-- 従業員数が3人以上の部署の平均給与を計算
  SELECT department_id, COUNT(*), AVG(salary)
FROM employees
WHERE salary >= 5000
GROUP BY department_id
HAVING COUNT(*) >= 3;
  </syntaxhighlight>
  </syntaxhighlight>
<br><br>
== その他の文字列関数 ==
MySQLには、その他にも多数の文字列関数が提供されている。<br>
<br>
<br>
下表に、主要な関数を示す。<br>
複数の条件を指定する例を以下に示す。<br>
<br>
<br>
<center>
<syntaxhighlight lang="mysql">
{| class="wikitable"
-- 従業員数が5人以上、かつ平均給与が7000以上の部署
|+ その他の文字列関数
SELECT department_id, COUNT(*), AVG(salary)
! 関数 !! 説明 !! 使用例
FROM employees
|-
GROUP BY department_id
| REVERSE || 文字列を反転 || REVERSE('Hello') → 'olleH'
HAVING COUNT(*) >= 5 AND AVG(salary) >= 7000;
|-
</syntaxhighlight>
| REPEAT || 文字列を繰り返し || REPEAT('X', 5) → 'XXXXX'
|-
| SPACE || 指定数の空白を生成 || SPACE(3) → '  '
|-
| FORMAT || 数値を千の位区切りでフォーマット || FORMAT(1234567.89, 2) → '1,234,567.89'
|-
| QUOTE || SQL文字列エスケープ || QUOTE("It's") → 'It\'s'
|-
| SOUNDEX || サウンデックス (音韻表現) || SOUNDEX('Smith') → 'S530'
|-
| STRCMP || 文字列比較 (0=等しい, -1=小, 1=大) || STRCMP('a', 'b') → -1
|}
</center>
<br>
<br>
===== REVERSE =====
エイリアスの使用を以下に示す。<br>
<code>REVERSE</code> 関数は、文字列を反転する。<br>
<br>
<br>
  <syntaxhighlight lang="sql">
  <syntaxhighlight lang="mysql">
  SELECT REVERSE('Hello World');
-- HAVING句でエイリアスを使用
  -- 結果: 'dlroW olleH'
  SELECT department_id, COUNT(*) AS emp_count, AVG(salary) AS avg_salary
   
  FROM employees
  SELECT REVERSE('12345');
  GROUP BY department_id
-- 結果: '54321'
  HAVING emp_count >= 5 AND avg_salary >= 7000;
  </syntaxhighlight>
  </syntaxhighlight>
<br><br>
== ウインドウ関数としての使用 ==
MySQL 8.0以降では、集約関数を <code>OVER()</code> 句と組み合わせてウインドウ関数として使用できる。<br>
ウインドウ関数として使用した場合、行を削減せず各行が保持される。<br>
<br>
<br>
===== REPEAT =====
基本的な使用例を以下に示す。<br>
<code>REPEAT</code> 関数は、文字列を指定回数繰り返す。<br>
<br>
<br>
  <syntaxhighlight lang="sql">
  <syntaxhighlight lang="mysql">
  SELECT REPEAT('*', 10);
-- 各従業員の給与と全体の平均給与を表示
  -- 結果: '**********'
  SELECT employee_id,
        first_name,
        salary,
        AVG(salary) OVER() AS avg_salary
  FROM employees;
   
   
  SELECT REPEAT('MySQL ', 3);
  -- 結果例:
  -- 結果: 'MySQL MySQL MySQL '
-- +-------------+------------+--------+------------+
  -- | employee_id | first_name | salary | avg_salary |
-- +-------------+------------+--------+------------+
-- |        100 | John      |  8000 |      6500 |
-- |        101 | Jane      |  7000 |      6500 |
-- |        102 | Bob        |  5000 |      6500 |
-- +-------------+------------+--------+------------+
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br>
===== SPACE =====
<code>PARTITION BY</code> 句を以下に示す。<br>
<code>SPACE</code> 関数は、指定数の空白文字を生成する。<br>
<code>PARTITION BY</code> 句は、ウインドウを部分的に分割して、部分ごとの集約を行う。<br>
<br>
<br>
  <syntaxhighlight lang="sql">
  <syntaxhighlight lang="mysql">
  SELECT CONCAT('Hello', SPACE(5), 'World');
-- 部署ごとの平均給与を各行に表示
  -- 結果: 'Hello    World'
  SELECT employee_id,
        first_name,
        department_id,
        salary,
        AVG(salary) OVER(PARTITION BY department_id) AS dept_avg_salary
FROM employees;
  -- 結果例:
-- +-------------+------------+---------------+--------+------------------+
-- | employee_id | first_name | department_id | salary | dept_avg_salary  |
-- +-------------+------------+---------------+--------+------------------+
-- |        100 | John      |            10 |  8000 |            7000 |
-- |        101 | Jane      |            10 |  6000 |            7000 |
-- |        102 | Bob        |            20 |  5000 |            5500 |
-- |        103 | Alice      |            20 |  6000 |            5500 |
-- +-------------+------------+---------------+--------+------------------+
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br>
===== FORMAT =====
<code>ORDER BY</code> 句を以下に示す。<br>
<code>FORMAT</code> 関数は、数値を千の位区切りでフォーマットする。<br>
<code>ORDER BY</code> 句は、ウインドウ内の行順序を指定する。<br>
これにより、累積合計や移動平均などの計算が可能になる。<br>
<br>
<br>
  <syntaxhighlight lang="sql">
  <syntaxhighlight lang="mysql">
  SELECT FORMAT(1234567.89, 2);
-- 部署ごとの給与の累積合計
  -- 結果: '1,234,567.89'
  SELECT employee_id,
        first_name,
        department_id,
        salary,
        SUM(salary) OVER(PARTITION BY department_id ORDER BY employee_id) AS cumulative_salary
  FROM employees;
   
   
  SELECT FORMAT(1234567.89, 0);
  -- 結果例:
  -- 結果: '1,234,568' (小数点以下四捨五入)
-- +-------------+------------+---------------+--------+-------------------+
   
-- | employee_id | first_name | department_id | salary | cumulative_salary |
  SELECT FORMAT(1234567.89, 2, 'de_DE');
-- +-------------+------------+---------------+--------+-------------------+
  -- 結果: '1.234.567,89' (ドイツ語ロケール)
-- |        100 | John      |            10 |  8000 |              8000 |
  -- |        101 | Jane      |            10 |  6000 |            14000 |
  -- |        102 | Bob        |            20 |  5000 |              5000 |
  -- |        103 | Alice      |            20 |  6000 |            11000 |
  -- +-------------+------------+---------------+--------+-------------------+
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br>
===== QUOTE =====
フレーム指定を以下に示す。<br>
<code>QUOTE</code> 関数は、SQL文字列としてエスケープする。<br>
<code>ROWS</code> 関数 または <code>RANGE</code> 句を使用して、ウインドウ内の特定の行範囲に対する集約を行うことができる。<br>
<br>
<br>
  <syntaxhighlight lang="sql">
  <syntaxhighlight lang="mysql">
  SELECT QUOTE("It's a test");
  -- 現在行と直前の2行 (合計3行) の移動平均
  -- 結果: 'It\'s a test'
  SELECT employee_id,
        first_name,
SELECT QUOTE('He said "Hello"');
        salary,
  -- 結果: 'He said \"Hello\"'
        AVG(salary) OVER(ORDER BY employee_id ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS moving_avg
  FROM employees;
   
   
  SELECT QUOTE(NULL);
-- 現在行と前後1行 (合計3行) の移動平均
  -- 結果: NULL
  SELECT employee_id,
        first_name,
        salary,
        AVG(salary) OVER(ORDER BY employee_id ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS moving_avg
  FROM employees;
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br>
===== SOUNDEX =====
下表に、フレーム指定のキーワードを示す。<br>
<code>SOUNDEX</code> 関数は、英語の音韻表現を生成する。<br>
類似した発音の単語を検索する場合に使用する。<br>
<br>
<br>
<syntaxhighlight lang="sql">
<center>
SELECT SOUNDEX('Smith');
{| class="wikitable"
-- 結果: 'S530'
|+ フレーム指定のキーワード
|-
SELECT SOUNDEX('Smythe');
! キーワード !! 説明
  -- 結果: 'S530' (Smithと同じ音韻コード)
|-
   
| ROWS || 物理的な行数でフレームを指定する
SELECT * FROM users WHERE SOUNDEX(name) = SOUNDEX('Jon');
|-
  -- 'John', 'Jon', 'Jonn' 等を検索
| RANGE || 値の範囲でフレームを指定する
|-
| UNBOUNDED PRECEDING || パーティションの最初の行から
|-
| UNBOUNDED FOLLOWING || パーティションの最後の行まで
|-
| CURRENT ROW || 現在の行
|-
| n PRECEDING || 現在行からn行前
|-
| n FOLLOWING || 現在行からn行後
|}
</center>
<br>
* 累積合計の例
*: <syntaxhighlight lang="mysql">
  -- 部署ごとの給与の累積合計 (明示的なフレーム指定)
  SELECT employee_id,
        first_name,
        department_id,
        salary,
        SUM(salary) OVER(
          PARTITION BY department_id
          ORDER BY employee_id
          ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
        ) AS cumulative_salary
  FROM employees;
  </syntaxhighlight>
  </syntaxhighlight>
<br>
*: <br>
===== STRCMP =====
* 移動平均の例
<code>STRCMP</code> 関数は、2つの文字列を比較する。<br>
*: <syntaxhighlight lang="mysql">
<br>
  -- 直近3件の給与の移動平均
<syntaxhighlight lang="sql">
  SELECT employee_id,
SELECT STRCMP('abc', 'abc');
        first_name,
  -- 結果: 0 (等しい)
        salary,
   
        AVG(salary) OVER(
SELECT STRCMP('abc', 'def');
          ORDER BY employee_id
-- 結果: -1 (第1引数が小)
          ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
        ) AS moving_avg_3
SELECT STRCMP('def', 'abc');
  FROM employees;
  -- 結果: 1 (第1引数が大)
  </syntaxhighlight>
  </syntaxhighlight>
<br><br>
== エンコード関連 ==
MySQLは、文字列とバイナリデータのエンコード変換関数を提供している。<br>
<br>
<br>
下表に、主要なエンコード関連関数を示す。<br>
下表に、サポートされる集約関数を示す。<br>
MySQL 8.0.12以降では、以下に示す集約関数がウインドウ関数としてサポートされている。<br>
<br>
<br>
<center>
<center>
{| class="wikitable"
{| class="wikitable"
|+ エンコード関連関数
|+ ウインドウ関数としてサポートされる集約関数 (MySQL 8.0.12以降)
! 関数 !! 説明 !! 使用例
|-
|-
| HEX || 16進数変換 || HEX('MySQL') → '4D7953514C'
! 集約関数
|-
|-
| UNHEX || 16進数→バイナリ文字列 || UNHEX('4D7953514C') → 'MySQL'
| COUNT
|-
|-
| ASCII || 最初の文字のASCIIコード || ASCII('A') → 65
| SUM
|-
|-
| ORD || 最初の文字のUnicodeコードポイント || ORD('あ') → 12354
| AVG
|-
|-
| CHAR || 整数コード→文字列 || CHAR(65, 66, 67) → 'ABC'
| MAX
|-
|-
| BIN || 2進数変換 || BIN(12) → '1100'
| MIN
|-
|-
| OCT || 8進数変換 || OCT(12) → '14'
| GROUP_CONCAT
|-
|-
| TO_BASE64 || Base64エンコード || TO_BASE64('MySQL') → 'TXlTUUw='
| JSON_ARRAYAGG
|-
|-
| FROM_BASE64 || Base64デコード || FROM_BASE64('TXlTUUw=') → 'MySQL'
| JSON_OBJECTAGG
|}
|}
</center>
</center>
<br><br>
== パフォーマンス ==
==== インデックスの活用 ====
集約関数のパフォーマンスは、インデックスの有無に大きく影響される。<br>
<br>
<br>
===== HEX / UNHEX =====
<code>MAX</code> / <code>MIN</code> 関数とインデックスの関係を以下に示す。<br>
<code>HEX</code> 関数は、文字列またはバイナリデータを16進数表現に変換する。<br>
* インデックスが存在する列に対して <code>MAX</code> / <code>MIN</code> を使用する場合、インデックスを活用した高速な検索が可能である。
<code>UNHEX</code> 関数は、16進数表現をバイナリデータに変換する。<br>
* これは、<code>ORDER BY column DESC LIMIT 1</code> (<code>MAX</code>) や <code>ORDER BY column ASC LIMIT 1</code> (<code>MIN</code>) と同等の最適化が行われるためである。
<br>
<br>
  <syntaxhighlight lang="sql">
  <syntaxhighlight lang="mysql">
SELECT HEX('MySQL');
  -- インデックスが存在する場合、高速に実行される
  -- 結果: '4D7953514C'
  SELECT MAX(employee_id) FROM employees;
  SELECT UNHEX('4D7953514C');
-- 結果: 'MySQL'
   
   
SELECT HEX(255);
  -- EXPLAIN で確認
  -- 結果: 'FF'
  EXPLAIN SELECT MAX(employee_id) FROM employees;
  -- type: index (インデックススキャン)
  SELECT HEX('あ');
  -- 結果: 'E38182' (UTF-8エンコーディング)
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br>
===== ASCII / ORD =====
<code>COUNT</code> 関数とインデックスの関係を以下に示す。<br>
<code>ASCII</code> 関数は、文字列の最初の文字のASCIIコードを返す。<br>
* InnoDBでは、<code>COUNT(*)</code> は最小の利用可能なセカンダリインデックスをスキャンする。
<code>ORD</code> 関数は、文字列の最初の文字のUNICODEコードポイントを返す。<br>
* プライマリキーより小さいセカンダリインデックスが存在する場合、そちらを優先的に使用する。
<br>
<br>
  <syntaxhighlight lang="sql">
  <syntaxhighlight lang="mysql">
  SELECT ASCII('A');
-- セカンダリインデックスが存在する場合、そちらをスキャン
-- 結果: 65
  SELECT COUNT(*) FROM employees;
   
   
SELECT ASCII('MySQL');
  -- EXPLAIN で確認
  -- 結果: 77 (最初の文字 'M')
  EXPLAIN SELECT COUNT(*) FROM employees;
  -- key: index_name (セカンダリインデックス)
  SELECT ORD('あ');
  -- 結果: 12354 (Unicodeコードポイント U+3042)
SELECT ORD('MySQL');
-- 結果: 77
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br>
<code>ORD</code> 関数は、マルチバイト文字に対応している。<br>
<code>GROUP BY</code> とインデックスの関係を以下に示す。<br>
* <code>GROUP BY</code> 列にインデックスが存在する場合、ソート処理が不要になる場合がある。
* <code>EXPLAIN</code> で <code>Using temporary; Using filesort</code> が表示されない場合、インデックスが活用されている。
<br>
<br>
===== CHAR =====
  <syntaxhighlight lang="mysql">
<code>CHAR</code> 関数は、整数コードから文字列を生成する。<br>
-- department_idにインデックスが存在する場合、高速に実行される
<br>
  SELECT department_id, COUNT(*)
  <syntaxhighlight lang="sql">
  FROM employees
  SELECT CHAR(65, 66, 67);
GROUP BY department_id;
  -- 結果: 'ABC'
   
   
SELECT CHAR(77, 121, 83, 81, 76);
  -- EXPLAINで確認
  -- 結果: 'MySQL'
  EXPLAIN SELECT department_id, COUNT(*) FROM employees GROUP BY department_id;
  -- Extra: Using index (インデックスのみでクエリを実行)
  SELECT CHAR(12354 USING utf8mb4);
  -- 結果: 'あ' (Unicodeコードポイント U+3042)
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br>
<code>USING</code> 句で文字セットを指定できる。<br>
==== 大量データの集計 ====
大量のデータを集計する場合、パフォーマンスに注意が必要である。<br>
<br>
<br>
===== BIN / OCT =====
一時テーブルの使用を以下に示す。<br>
<code>BIN</code> 関数は、整数を2進数表現に変換する。<br>
* <code>GROUP BY</code> でグループ化を行う場合、内部的に一時テーブル (TEMPORARY TABLE) が使用される可能性がある。
<code>OCT</code> 関数は、整数を8進数表現に変換する。<br>
* 一時テーブルのサイズが <code>tmp_table_size</code> および <code>max_heap_table_size</code> を超える場合、ディスク上の一時テーブルが使用される。
* ディスク上の一時テーブルは、メモリ上の一時テーブルより遅い。
<br>
<br>
  <syntaxhighlight lang="sql">
  <syntaxhighlight lang="sql">
  SELECT BIN(12);
  -- 一時テーブルのサイズ制限を確認
  -- 結果: '1100'
SHOW VARIABLES LIKE 'tmp_table_size';
  SHOW VARIABLES LIKE 'max_heap_table_size';
   
   
SELECT BIN(255);
  -- EXPLAIN で一時テーブルの使用を確認
  -- 結果: '11111111'
  EXPLAIN SELECT department_id, COUNT(*) FROM employees GROUP BY department_id;
  -- Extra: Using temporary (一時テーブルを使用)
  SELECT OCT(12);
  -- 結果: '14'
SELECT OCT(255);
-- 結果: '377'
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br>
===== TO_BASE64 / FROM_BASE64 =====
パフォーマンス改善の方法を以下に示す。<br>
<code>TO_BASE64</code> 関数は、文字列をBase64エンコードする。<br>
* インデックスの追加
<code>FROM_BASE64</code> 関数は、Base64文字列をデコードする。<br>
*: <code>GROUP BY</code> 列にインデックスを追加する。
<br>
* 一時テーブルサイズの拡張
これらの関数は、MySQL 5.6以降で使用可能である。<br>
*: <code>tmp_table_size</code> および <code>max_heap_table_size</code> を増やす。
<br>
* パーティショニング
<syntaxhighlight lang="sql">
*: 大量データをパーティション分割して、集計対象を絞る。
SELECT TO_BASE64('MySQL');
-- 結果: 'TXlTUUw='
SELECT FROM_BASE64('TXlTUUw=');
-- 結果: 'MySQL'
SELECT TO_BASE64('こんにちは');
-- 結果: '44GT44KT44Gr44Gh44Gv'
SELECT FROM_BASE64('44GT44KT44Gr44Gh44Gv');
-- 結果: 'こんにちは'
</syntaxhighlight>
<br>
<br>
Base64エンコーディングは、バイナリデータをテキスト形式で格納する場合に有用である。<br>
<br><br>


== 照合順序との関係 ==
==== GROUP_CONCATの制限 ====
文字列関数の動作は、照合順序 (Collation) に依存する場合がある。<br>
<code>GROUP_CONCAT</code> は、出力制限に注意が必要である。<br>
照合順序は、文字列比較、ソート、大文字小文字の区別を制御する。<br>
<br>
<br>
MySQL 8.0のデフォルト照合順序は、<u>utf8mb4_0900_ai_ci</u> である。<br>
出力制限を以下に示す。<br>
* group_concat_max_len (デフォルト1024バイト)
*: <code>GROUP_CONCAT</code> の出力最大長を制御する。
*: この制限を超える場合、無警告で切り詰められる。
* max_allowed_packet
*: 最大パケットサイズも制限要因となる。
<br>
<br>
照合順序の種類を以下に示す。<br>
切り詰め時の挙動を以下に示す。<br>
* 制限を超えた場合、無警告で切り詰められる。
* 警告 (WARNING) は生成されない。
* <code>SHOW WARNINGS;</code> でも確認できない。
<br>
<br>
<center>
推奨される対策を以下に示す。<br>
{| class="wikitable"
* セッション単位で <code>group_concat_max_len</code> を拡張する。
|+ 主要な照合順序
* または、グローバルに <code>group_concat_max_len</code> を拡張する。
! 照合順序 !! 特性 !! 説明
|-
| utf8mb4_general_ci || 大小文字区別なし、高速 || MySQL 5.7以前のデフォルト
|-
| utf8mb4_unicode_ci || 大小文字区別なし、Unicode準拠 || 多言語対応
|-
| utf8mb4_0900_ai_ci || アクセント非依存、大小文字区別なし || MySQL 8.0デフォルト
|-
| utf8mb4_bin || バイナリ比較 || 大文字小文字、アクセントを区別
|}
</center>
<br>
<br>
===== COLLATE句での照合順序指定 =====
  <syntaxhighlight lang="mysql">
<code>COLLATE</code> 句により、クエリレベルで照合順序を指定できる。<br>
  -- セッション単位で拡張
<br>
  SET SESSION group_concat_max_len = 1000000;
  <syntaxhighlight lang="sql">
SELECT 'abc' = 'ABC';
  -- 結果: 1 (デフォルトでは大文字小文字区別なし)
  SELECT 'abc' = 'ABC' COLLATE utf8mb4_bin;
-- 結果: 0 (バイナリ比較では区別)
   
   
SELECT 'Müller' = 'Mueller' COLLATE utf8mb4_0900_ai_ci;
  -- グローバルに拡張
  -- 結果: 1 (アクセント非依存)
  SET GLOBAL group_concat_max_len = 1000000;
  SELECT 'Müller' = 'Mueller' COLLATE utf8mb4_bin;
-- 結果: 0 (バイナリ比較では区別)
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br>
===== BINARY演算子 =====
==== DISTINCT性能 ====
<code>BINARY</code> 演算子により、バイナリ比較を強制できる。<br>
<code>DISTINCT</code> を使用する集約関数は、追加の処理が必要なためパフォーマンスが低下する傾向がある。<br>
<br>
<br>
<syntaxhighlight lang="sql">
パフォーマンス特性を以下に示す。<br>
SELECT 'abc' = 'ABC';
* COUNT(DISTINCT column)
-- 結果: 1
*: <code>DISTINCT</code> 処理のため、<code>COUNT(column)</code> より遅い傾向がある。
*: 内部的に一時テーブルを使用する。
SELECT BINARY 'abc' = 'ABC';
* SUM(DISTINCT column) / AVG(DISTINCT column)
-- 結果: 0
*: 同様に、<code>DISTINCT</code> 処理のため遅い傾向がある。
SELECT 'abc' = BINARY 'ABC';
-- 結果: 0
SELECT LOCATE('WORLD', 'Hello World');
-- 結果: 7 (大文字小文字区別なし)
SELECT LOCATE(BINARY 'WORLD', 'Hello World');
-- 結果: 0 (バイナリ比較で不一致)
</syntaxhighlight>
<br>
<br>
===== 文字セットと照合順序の推奨 =====
推奨される対策を以下に示す。<br>
MySQLでは、<u>utf8mb4</u> 文字セットの使用を強く推奨する。<br>
* インデックスの追加
<u>utf8</u> 文字セットは、最大3バイト/文字であり、絵文字等の4バイト文字を格納できない。<br>
*: <code>DISTINCT</code> 対象列にインデックスを追加する。
<u>utf8mb4</u> 文字セットは、最大4バイト/文字であり、全てのUNICODE文字を格納できる。<br>
* サブクエリの使用
*: 大量データの場合、サブクエリで事前に <code>DISTINCT</code> を行うことで高速化できる場合がある。
<br>
<br>
* テーブル作成時の推奨設定
<syntaxhighlight lang="mysql">
*: <syntaxhighlight lang="sql">
  -- サブクエリで事前にDISTINCTを行う
  CREATE TABLE users (
SELECT COUNT(*) FROM (
     id INT PRIMARY KEY,
     SELECT DISTINCT department_id FROM employees
    name VARCHAR(100)
  ) AS distinct_depts;
  ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
  </syntaxhighlight>
  </syntaxhighlight>
*: <br>
* データベース全体のデフォルト文字セットを設定する場合
*: <syntaxhighlight lang="sql">
CREATE DATABASE mydb
    DEFAULT CHARACTER SET utf8mb4
    DEFAULT COLLATE utf8mb4_unicode_ci;
</syntaxhighlight>
<br><br>
== 文字列関数を使用する場合の注意 ==
==== 文字列関数とインデックス ====
WHERE句で文字列関数を使用すると、インデックスが使用されない場合が多い。<br>
これにより、クエリのパフォーマンスが大幅に低下する可能性がある。<br>
<br>
<br>
===== インデックスが使用されない例 =====
==== WITH ROLLUPの影響 ====
<syntaxhighlight lang="sql">
<code>WITH ROLLUP</code> は、追加の処理が必要なためパフォーマンスに影響する。<br>
-- インデックスが使用されない
SELECT * FROM users WHERE UPPER(name) = 'JOHN';
-- インデックスが使用されない
SELECT * FROM articles WHERE SUBSTRING(title, 1, 5) = 'MySQL';
</syntaxhighlight>
<br>
<br>
===== インデックスを使用する改善例 =====
パフォーマンス特性を以下に示す。<br>
<syntaxhighlight lang="sql">
* <code>WITH ROLLUP</code> は、小計行を生成するため、追加の処理が必要である。
-- インデックスが使用される
* 複数列でロールアップする場合、さらに処理が増加する。
SELECT * FROM users WHERE name = 'John';
-- インデックスが使用される (前方一致)
SELECT * FROM articles WHERE title LIKE 'MySQL%';
</syntaxhighlight>
<br>
<br>
===== 関数ベースインデックス (Generated Column) を使用する例 =====
推奨される対策を以下に示す。<br>
<syntaxhighlight lang="sql">
* インデックスの追加
-- Generated Columnを作成
*: <code>GROUP BY</code> 列にインデックスを追加する。
ALTER TABLE users ADD COLUMN name_upper VARCHAR(100) AS (UPPER(name)) STORED;
* UNION ALLの使用
*: 大量データの場合、<code>UNION ALL</code> で小計行を個別に計算する方が高速な場合がある。
-- インデックスを作成
CREATE INDEX idx_name_upper ON users(name_upper);
-- インデックスが使用される
SELECT * FROM users WHERE name_upper = 'JOHN';
</syntaxhighlight>
<br>
<br>
MySQL 8.0以降では、関数インデックスが直接サポートされている。<br>
  <syntaxhighlight lang="mysql">
  <syntaxhighlight lang="sql">
  -- WITH ROLLUP の代わりに UNION ALL を使用
  -- MySQL 8.0以降
  SELECT department_id, COUNT(*) FROM employees GROUP BY department_id
  CREATE INDEX idx_upper_name ON users((UPPER(name)));
  UNION ALL
   
  SELECT NULL, COUNT(*) FROM employees;
-- インデックスが使用される
  SELECT * FROM users WHERE UPPER(name) = 'JOHN';
  </syntaxhighlight>
  </syntaxhighlight>
<br><br>
== バージョン別の機能差異 ==
MySQLのバージョンによって、集約関数の機能に差異がある。<br>
<br>
<br>
==== 大きなテキストデータ ====
下表に、MySQL 5.7とMySQL 8.0の主要な違いを示す。<br>
TEXT型およびBLOB型での文字列関数使用は、パフォーマンスが低下する傾向がある。<br>
<br>
理由を以下に示す。<br>
* TEXT / BLOB型は、オフページ格納される場合がある。
*: データが別の場所に格納され、アクセスが遅くなる。
* インデックスの制限
*: TEXT/BLOB型の全体にインデックスを作成できない。
* メモリ使用量
*: 一時テーブルがディスクに作成される場合がある。
<br>
大きなテキストデータの処理には、以下に示す対策を推奨する。<br>
<br>
<br>
<center>
<center>
{| class="wikitable"
{| class="wikitable"
|+ 大きなテキストデータの処理に対する推奨対策
|+ MySQL 5.7 vs MySQL 8.0
|-
! 機能 !! MySQL 5.7 !! MySQL 8.0
! 対策 !! 説明
|-
| VARCHAR型を優先 || VARCHAR型は、最大65,535バイトまで格納可能<br>VARCHAR(5000) 等で十分な場合が多い。
|-
| 全文検索インデックス (FULLTEXT) を使用 || テキスト検索にはFULLTEXTインデックスが効率的である。
|-
|-
| アプリケーションレイヤでの処理 || 複雑な文字列処理は、アプリケーション側で実施する。
| JSON_ARRAYAGG / JSON_OBJECTAGG || 5.7.22以降でサポート || サポート
|}
</center>
<br>
===== マルチバイト文字の考慮 =====
マルチバイト文字セット (utf8mb4) では、以下に示すに注意する。<br>
<br>
<center>
{| class="wikitable"
|+ 文字列処理に関する注意事項
|-
|-
! 項目 !! 説明
| ウインドウ関数 (OVER句) || 未サポート || サポート (8.0以降)
|-
|-
| <code>LENGTH</code> 関数 と <code>CHAR_LENGTH</code> 関数の違い || <code>LENGTH</code> 関数はバイト長、<code>CHAR_LENGTH</code> 関数は文字数<br>日本語等では、<code>LENGTH</code>関数の戻り値は文字数の3倍
| GROUPING関数 || 未サポート || サポート (8.0.12以降)
|-
|-
| VARCHAR型の長さ制限 || VARCHAR(100) は、100文字 (最大400バイト)<br>バイト長制限ではなく文字数制限
| ONLY_FULL_GROUP_BY || デフォルトで有効 || デフォルトで有効
|-
|-
| インデックスキーのサイズ制限 || InnoDBのインデックスキー最大長は767バイト (デフォルト)<br>VARCHAR(255) のutf8mb4カラムは、最大1020バイトとなり、インデックス作成に失敗する可能性<br><code>innodb_large_prefix=ON</code> (MySQL 5.7以降デフォルト) で3072バイトまで拡張可能
| COUNT関数の戻り値 || BIGINT UNSIGNED || BIGINT
|}
|}
</center>
</center>
<br>
<br>
インデックス作成時の注意例を以下に示す。<br>
MySQL 5.7での注意点を以下に示す。<br>
<br>
* ウインドウ関数は使用できない。
<syntaxhighlight lang="sql">
*: <code>OVER()</code> 句はサポートされていない。
-- VARCHAR(255) utf8mb4カラムのインデックス作成
*: 代替として、サブクエリや自己結合を使用する必要がある。
CREATE INDEX idx_title ON articles(title);
* GROUPING関数は使用できない。
-- ERROR: Specified key was too long; max key length is 767 bytes
*: <code>WITH ROLLUP</code> で小計行を識別するには、<code>NULL</code> チェックのみを使用する。
-- 解決策1: プレフィックスインデックス
CREATE INDEX idx_title ON articles(title(191));
-- 191文字 * 4バイト = 764バイト (767バイト以下)
-- 解決策2: innodb_large_prefix有効化 (MySQL 5.7以降デフォルト)
SET GLOBAL innodb_large_prefix = 1;
SET GLOBAL innodb_file_format = 'Barracuda';
-- テーブルにROW_FORMAT=DYNAMICを指定
CREATE TABLE articles (
    id INT PRIMARY KEY,
    title VARCHAR(255)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE INDEX idx_title ON articles(title);
-- 成功 (最大3072バイトまで許容)
</syntaxhighlight>
<br>
===== NULL処理 =====
多くの文字列関数は、<code>NULL</code> を特別に扱う。<br>
<br>
<br>
<code>NULL</code> 処理の例を以下に示す。<br>
MySQL 8.0での新機能を以下に示す。<br>
* ウインドウ関数
*: 集約関数を <code>OVER()</code> 句と組み合わせて使用できる。
*: 累積合計、移動平均等の計算が可能になる。
* GROUPING関数
*: <code>WITH ROLLUP</code> で小計行を識別できる。
* 改善された集約関数のパフォーマンス
*: インデックスの活用が改善されている。
<br>
<br>
  <syntaxhighlight lang="sql">
MySQL 5.7でウインドウ関数を代替する例を以下に示す。<br>
  SELECT CONCAT('Hello', NULL, 'World');
  <syntaxhighlight lang="mysql">
  -- 結果: NULL (いずれかの引数がNULLなら結果はNULL)
-- MySQL 8.0 (ウインドウ関数)
  SELECT employee_id,
        salary,
        AVG(salary) OVER() AS avg_salary
  FROM employees;
   
   
SELECT CONCAT_WS(',', 'A', NULL, 'B');
  -- MySQL 5.7 (サブクエリで代替)
  -- 結果: 'A,B' (NULLはスキップ)
  SELECT e.employee_id,
        e.salary,
  SELECT LENGTH(NULL);
        (SELECT AVG(salary) FROM employees) AS avg_salary
-- 結果: NULL
  FROM employees e;
SELECT UPPER(NULL);
-- 結果: NULL
  SELECT COALESCE(name, '(未設定)') FROM users;
-- NULLを'(未設定)'で置換
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<u><code>NULL</code> を扱う場合は、<code>COALESCE</code> 関数 や <code>IFNULL</code> 関数を併用することを推奨する。</u><br>
<br><br>
<br><br>


1,087行目: 1,012行目:
{{#seo:
{{#seo:
|title={{PAGENAME}} : Exploring Electronics and SUSE Linux | MochiuWiki
|title={{PAGENAME}} : Exploring Electronics and SUSE Linux | MochiuWiki
|keywords=MochiuWiki,Mochiu,Wiki,Mochiu Wiki,MySQL,Database,SQL,String Functions,CONCAT,SUBSTRING,REPLACE,TRIM,UPPER,LOWER,LENGTH,CHAR_LENGTH,文字列関数,データベース,電気回路,電子回路,基板,プリント基板
|keywords=MochiuWiki,Mochiu,Wiki,Mochiu Wiki,Electric Circuit,Electric,pcb,Mathematics,AVR,TI,STMicro,AVR,ATmega,MSP430,STM,Arduino,Xilinx,FPGA,Verilog,HDL,PinePhone,Pine Phone,Raspberry,Raspberry Pi,C,C++,C#,Qt,Qml,MFC,Shell,Bash,Zsh,Fish,SUSE,SLE,Suse Enterprise,Suse Linux,openSUSE,open SUSE,Leap,Linux,uCLnux,MySQL,SQL,Database,集約関数,Aggregate Functions,COUNT,SUM,AVG,MAX,MIN,GROUP_CONCAT,JSON_ARRAYAGG,JSON_OBJECTAGG,GROUP BY,HAVING,WITH ROLLUP,ONLY_FULL_GROUP_BY,ウインドウ関数,Window Functions,電気回路,電子回路,基板,プリント基板
|description={{PAGENAME}} - MySQLの文字列関数に関する包括的なリファレンス | This page is {{PAGENAME}} in our wiki about electronic circuits and SUSE Linux
|description={{PAGENAME}} - MySQLの集約関数の使い方とパフォーマンス最適化 | This page is {{PAGENAME}} in our wiki about electronic circuits and SUSE Linux
|image=/resources/assets/MochiuLogo_Single_Blue.png
|image=/resources/assets/MochiuLogo_Single_Blue.png
}}
}}

2026年2月15日 (日) 20:14時点における最新版

概要

MySQLの集約関数 (Aggregate Functions) は、複数の行から単一の値を計算する関数である。
集約関数は、GROUP BY 句と組み合わせて使用することで、データをグループ化して集計を行うことができる。

主要な集約関数には、COUNTSUMAVGMAXMINGROUP_CONCAT が含まれる。
また、MySQL 5.7.22以降では、JSON_ARRAYAGG および JSON_OBJECTAGG が追加されている。

集約関数は、NULL 値を自動的に無視する特性を持つ (一部例外を除く)。
全ての値が NULL の場合、集約関数は NULL を返す。(COUNT(*) は、0を返す)

MySQL 8.0以降では、集約関数を OVER() 句と組み合わせてウインドウ関数として使用できる。
ウインドウ関数として使用した場合、行を削減せず各行が保持される。

GROUP BY 句を使用する際は、MySQL 8.0のデフォルトSQLモードである ONLY_FULL_GROUP_BY に注意が必要である。
このモードでは、SELECT リストの非集約列は全て GROUP BY 句に含まれるか、関数従属である必要がある。

集約関数のパフォーマンスは、インデックスの有無、データ量、DISTINCT の使用などに影響される。
EXPLAIN で実行計画を確認し、Using temporaryUsing filesort をチェックすることが推奨される。


基本構文

MySQLで提供される主要な集約関数を以下に示す。

主要な集約関数一覧
関数名 説明 戻り値型
COUNT(*) 全ての行数をカウント (NULL含む) BIGINT
COUNT(column) 指定列のNULL以外の値をカウント BIGINT
COUNT(DISTINCT column) 指定列の重複を除いた値をカウント BIGINT
SUM(column) 指定列の合計値を計算 (NULL除外) DECIMAL または DOUBLE
SUM(DISTINCT column) 指定列の重複を除いた合計値を計算 DECIMAL または DOUBLE
AVG(column) 指定列の平均値を計算 (NULL除外) DECIMAL または DOUBLE
AVG(DISTINCT column) 指定列の重複を除いた平均値を計算 DECIMAL または DOUBLE
MAX(column) 指定列の最大値を取得 列の型に依存
MIN(column) 指定列の最小値を取得 列の型に依存
GROUP_CONCAT(column) 指定列の値を連結した文字列を生成 TEXT
JSON_ARRAYAGG(column) 指定列の値をJSON配列として集約 (MySQL 5.7.22+) JSON
JSON_OBJECTAGG(key, value) キーと値のペアをJSONオブジェクトとして集約 (MySQL 5.7.22+) JSON


COUNT関数

COUNT 関数は、行数をカウントする集約関数である。

基本的な使用例を以下に示す。

 -- 全ての行数をカウント (NULL含む)
 SELECT COUNT(*) FROM employees;
 
 -- 指定列のNULL以外の値をカウント
 SELECT COUNT(department_id) FROM employees;
 
 -- 重複を除いたカウント
 SELECT COUNT(DISTINCT department_id) FROM employees;
 
 -- 複数列の組み合わせで重複を除いたカウント
 SELECT COUNT(DISTINCT department_id, job_id) FROM employees;


COUNT(*)COUNT(column) の違いを以下に示す。

COUNT(*) と COUNT(column) の違い
関数 説明
COUNT(*) 全ての行数をカウントする。
NULL 値を含む全ての行を対象とする。
COUNT(column) 指定列の NULL 以外の値のみをカウントする。
NULL 値は自動的に除外される。


パフォーマンス特性
ストレージエンジン 説明
InnoDB COUNT(*) は、最小の利用可能なセカンダリインデックスをスキャンする。
プライマリキーより小さいセカンダリインデックスが存在する場合、そちらを優先的に使用する。
MyISAM 内部的に行数を保持しているため、WHERE 句なしの COUNT(*) は高速に実行される。


戻り値の型は、MySQL 8.0以降では常に BIGINT である。

COUNT(DISTINCT column1, column2) のように複数列を指定した場合、列の組み合わせでユニークな行をカウントする。

SUM関数

SUM 関数は、指定列の合計値を計算する集約関数である。

基本的な使用例を以下に示す。

 -- 給与の合計
 SELECT SUM(salary) FROM employees;
 
 -- 部署ごとの給与合計
 SELECT department_id, SUM(salary) FROM employees GROUP BY department_id;
 
 -- 重複を除いた合計
 SELECT SUM(DISTINCT salary) FROM employees;


NULL 値の処理を以下に示す。

  • NULL 値は自動的に除外される。
  • 全ての値が NULL の場合、SUMNULL を返す。
  • COUNT(column) が0の場合、SUMNULL を返す。


戻り値の型を以下に示す。

  • 整数型または DECIMAL型の引数
    DECIMAL 型を返す。
  • FLOAT型または DOUBLE型の引数
    DOUBLE 型を返す。


※注意

  • 数値型の列にのみ適用可能
    時間型 (TIME、DATE、DATETIME) に直接適用できない。
    TIME_TO_SEC() 等で事前に数値に変換する必要がある。
  • オーバーフロー
    整数型の合計が型の範囲を超える場合、オーバーフローが発生する可能性がある。
    DECIMAL 型または BIGINT 型を使用することを推奨する。


SUM(DISTINCT column) を使用すると、重複する値を除いて合計を計算する。

時間型の合計を計算する例を以下に示す。

 -- TIME型の合計 (秒に変換)
 SELECT SEC_TO_TIME(SUM(TIME_TO_SEC(work_time))) FROM daily_records;


AVG関数

AVG 関数は、指定列の平均値を計算する集約関数である。

基本的な使用例を以下に示す。

 -- 給与の平均
 SELECT AVG(salary) FROM employees;
 
 -- 部署ごとの給与平均
 SELECT department_id, AVG(salary) FROM employees GROUP BY department_id;
 
 -- 重複を除いた平均
 SELECT AVG(DISTINCT salary) FROM employees;


内部的な計算方法を以下に示す。

  • AVG(column) は、SUM(column) / COUNT(column) と同等である。
  • NULL 値は自動的に除外される。


戻り値の型を以下に示す。

  • 整数型または DECIMAL型の引数
    DECIMAL(14, 4) 型を返す。
  • FLOAT型または DOUBLE型の引数
    DOUBLE 型を返す。


精度に関する注意を以下に示す。

  • 整数型の平均を計算する場合、小数点以下が切り捨てられない
    DECIMAL(14, 4) で正確な小数値が返される。
  • 丸め処理
    ROUND(AVG(column), 2) で任意の桁数に丸めることができる。


AVG(DISTINCT column) を使用すると、重複する値を除いて平均を計算する。

丸め処理の例を以下に示す。

 -- 小数点以下2桁に丸める
 SELECT ROUND(AVG(salary), 2) FROM employees;
 
 -- 整数に切り捨て
 SELECT FLOOR(AVG(salary)) FROM employees;
 
 -- 整数に切り上げ
 SELECT CEIL(AVG(salary)) FROM employees;


MAX / MIN関数

MAX 関数 および MIN 関数は、指定列の最大値および最小値を取得する集約関数である。

基本的な使用例を以下に示す。

 -- 最大給与
 SELECT MAX(salary) FROM employees;
 
 -- 最小給与
 SELECT MIN(salary) FROM employees;
 
 -- 部署ごとの最大給与・最小給与
 SELECT department_id, MAX(salary), MIN(salary)
 FROM employees
 GROUP BY department_id;


数値型以外への適用を以下に示す。

  • 文字列型
    辞書順 (照合順序に依存) で比較される。
    照合順序 (COLLATION) によって結果が異なる場合がある。
  • 日付型 (DATE、DATETIME、TIMESTAMP)
    時系列順で比較される。
    最も古い日付 (MIN)、最も新しい日付 (MAX) を取得できる。


インデックスとの関係を以下に示す。

  • インデックスが存在する列に対して MAX / MIN を使用する場合、インデックスを活用した高速な検索が可能である。
  • これは、ORDER BY column DESC LIMIT 1 (MAX) や ORDER BY column ASC LIMIT 1 (MIN) と同等の最適化が行われるためである。


NULL 値の処理を以下に示す。

  • NULL 値は常に無視される。
  • 全ての値が NULL の場合、MAX / MINNULL を返す。


文字列型への適用例を以下に示す。

 -- 辞書順で最初の名前
 SELECT MIN(first_name) FROM employees;
 
 -- 辞書順で最後の名前
 SELECT MAX(first_name) FROM employees;


日付型への適用例を以下に示す。

 -- 最も古い入社日
 SELECT MIN(hire_date) FROM employees;
 
 -- 最も新しい入社日
 SELECT MAX(hire_date) FROM employees;


GROUP_CONCAT関数

GROUP_CONCAT 関数は、グループ内の値を連結した文字列を生成する集約関数である。

基本的な使用例を以下に示す。

 -- 部署ごとの従業員名を連結
 SELECT department_id, GROUP_CONCAT(first_name)
 FROM employees
 GROUP BY department_id;
 
 -- セパレータを指定
 SELECT department_id, GROUP_CONCAT(first_name SEPARATOR '; ')
 FROM employees
 GROUP BY department_id;
 
 -- ORDER BY句でソート
 SELECT department_id, GROUP_CONCAT(first_name ORDER BY first_name ASC)
 FROM employees
 GROUP BY department_id;
 
 -- 重複を除外
 SELECT department_id, GROUP_CONCAT(DISTINCT job_id)
 FROM employees
 GROUP BY department_id;


オプション句を以下に示す。

  • SEPARATOR句
    連結時のセパレータを指定する。
    デフォルトはカンマ (,) である。
    SEPARATOR でセパレータなしの連結も可能である。
  • ORDER BY句
    連結前に値をソートする。
    ORDER BY column ASC または ORDER BY column DESC を指定できる。
  • DISTINCT
    重複する値を除外してから連結する。


出力制限に関する注意を以下に示す。

  • group_concat_max_len システム変数
    GROUP_CONCAT の出力最大長を制御する (デフォルト1024バイト)。
    この制限を超える場合、無警告で切り詰められる。
    SET SESSION group_concat_max_len = 1000000; で拡張可能である。
  • max_allowed_packet
    最大パケットサイズも制限要因となる。


NULL 値の処理を以下に示す。

  • NULL 値は自動的に除外される。
  • 全ての値が NULL の場合、GROUP_CONCATNULL を返す。


複数列の連結例を以下に示す。

 -- 名前と役職を組み合わせて連結
 SELECT department_id,
        GROUP_CONCAT(CONCAT(first_name, ' (', job_id, ')') ORDER BY first_name)
 FROM employees
 GROUP BY department_id;


出力制限の設定例を以下に示す。

 -- セッション単位で制限を拡張
 SET SESSION group_concat_max_len = 1000000;
 
 -- グローバルに制限を拡張
 SET GLOBAL group_concat_max_len = 1000000;


JSON_ARRAYAGG / JSON_OBJECTAGG

JSON_ARRAYAGG 関数 および JSON_OBJECTAGG 関数は、MySQL 5.7.22以降で使用可能なJSON集約関数である。

JSON_ARRAYAGG

JSON_ARRAYAGG 関数は、グループ内の値をJSON配列として集約する。

基本的な使用例を以下に示す。

 -- 部署ごとの従業員名をJSON配列として集約
 SELECT department_id, JSON_ARRAYAGG(first_name)
 FROM employees
 GROUP BY department_id;
 
 -- 結果例
 -- {"department_id": 10, "names": ["John", "Jane", "Bob"]}


GROUP_CONCAT との違いを以下に示す。

  • NULL値の扱い
    JSON_ARRAYAGG は、NULL 値も配列要素として含める。
    GROUP_CONCAT は、NULL 値を無視する。
  • 戻り値の型
    JSON_ARRAYAGG は、JSON配列型を返す。
    GROUP_CONCAT は、文字列 (TEXT型) を返す。


NULL値を含む例を以下に示す。

 -- NULL値も配列要素として含まれる
 SELECT JSON_ARRAYAGG(commission_pct) FROM employees;
 
 -- 結果例: [0.2, 0.3, null, null, 0.15]


JSON_OBJECTAGG

JSON_OBJECTAGG 関数は、キーと値のペアをJSONオブジェクトとして集約する。

基本的な使用例を以下に示す。

 -- 従業員IDと名前のペアをJSONオブジェクトとして集約
 SELECT department_id, JSON_OBJECTAGG(employee_id, first_name)
 FROM employees
 GROUP BY department_id;
 
 -- 結果例
 -- {"department_id": 10, "employees": {"100": "John", "101": "Jane"}}


キー重複時の動作を以下に示す。

  • 同じキーが複数回現れる場合、最後の値で上書きされる。
  • どの値が最後になるかは、行の順序に依存する。
  • 明示的に ORDER BY 句を使用することで、順序を制御できる。


キー重複の例を以下に示す。

 -- 同じキーが複数回現れる場合、最後の値で上書き
 SELECT JSON_OBJECTAGG(job_id, first_name)
 FROM employees;
 
 -- 結果例: {"CLERK": "Sarah", "MANAGER": "Bob"}
 -- (CLERKやMANAGERが複数いる場合、最後の行の値が使用される)



GROUP BYとの組み合わせ

基本的な使い方

GROUP BY 句は、集約関数と組み合わせて使用することにより、データをグループ化して集計を行う。

基本的な使用例を以下に示す。

 -- 部署ごとの従業員数
 SELECT department_id, COUNT(*)
 FROM employees
 GROUP BY department_id;
 
 -- 部署ごとの平均給与
 SELECT department_id, AVG(salary)
 FROM employees
 GROUP BY department_id;
 
 -- 役職ごとの最大給与・最小給与
 SELECT job_id, MAX(salary), MIN(salary)
 FROM employees
 GROUP BY job_id;


複数列でのグループ化を以下に示す。

 -- 部署と役職の組み合わせでグループ化
 SELECT department_id, job_id, COUNT(*), AVG(salary)
 FROM employees
 GROUP BY department_id, job_id;


WHERE 句との組み合わせを以下に示す。

 -- 給与が5000以上の従業員を対象に部署ごとの平均給与を計算
 SELECT department_id, AVG(salary)
 FROM employees
 WHERE salary >= 5000
 GROUP BY department_id;


実行順序を以下に示す。

  1. WHERE句でフィルタリング (グループ化前)
  2. GROUP BY句でグループ化
  3. 集約関数で集計
  4. HAVING句でフィルタリング (グループ化後)
  5. ORDER BY句でソート
  6. LIMIT句で件数制限


WITH ROLLUP

WITH ROLLUP 修飾子は、小計と総計を含む追加行を生成する。

基本的な使用例を以下に示す。

 -- 部署ごとの従業員数と総計
 SELECT department_id, COUNT(*)
 FROM employees
 GROUP BY department_id WITH ROLLUP;
 
 -- 結果例:
 -- +---------------+----------+
 -- | department_id | COUNT(*) |
 -- +---------------+----------+
 -- |            10 |        5 |
 -- |            20 |        8 |
 -- |            30 |       12 |
 -- |          NULL |       25 |  -- 総計行
 -- +---------------+----------+


複数列でのロールアップを以下に示す。

 -- 部署と役職の組み合わせでロールアップ
 SELECT department_id, job_id, COUNT(*)
 FROM employees
 GROUP BY department_id, job_id WITH ROLLUP;
 
 -- 結果例:
 -- +---------------+---------+----------+
 -- | department_id | job_id  | COUNT(*) |
 -- +---------------+---------+----------+
 -- |            10 | CLERK   |        3 |
 -- |            10 | MANAGER |        2 |
 -- |            10 | NULL    |        5 |  -- 部署10の小計
 -- |            20 | CLERK   |        5 |
 -- |            20 | ANALYST |        3 |
 -- |            20 | NULL    |        8 |  -- 部署20の小計
 -- |          NULL | NULL    |       13 |  -- 総計
 -- +---------------+---------+----------+


GROUPING 関数を以下に示す。
GROUPING 関数は、NULL 値が通常のグループ値か、小計行かを区別するために使用する。

 -- GROUPING関数で小計行を識別
 SELECT department_id,
        job_id,
        COUNT(*),
        GROUPING(department_id) AS dept_grouping,
        GROUPING(job_id) AS job_grouping
 FROM employees
 GROUP BY department_id, job_id WITH ROLLUP;
 
 -- GROUPING関数の戻り値:
 -- 0 = 通常のグループ値
 -- 1 = 小計行 (ROLLUPによって生成されたNULL)


小計行のラベル付け例を以下に示す。

 -- 小計行にラベルを付ける
 SELECT IF(GROUPING(department_id), 'Total', department_id) AS department,
        IF(GROUPING(job_id), 'Subtotal', job_id) AS job,
        COUNT(*)
 FROM employees
 GROUP BY department_id, job_id WITH ROLLUP;


ONLY_FULL_GROUP_BY モード

ONLY_FULL_GROUP_BY は、MySQL 8.0のデフォルトSQLモードに含まれる厳格なグループ化ルールである。

基本的なルールを以下に示す。

  • SELECT リストの非集約列は、全て GROUP BY 句に含まれる必要がある。
  • または、非集約列が GROUP BY 列に関数従属である必要がある。


エラーの例を以下に示す。

 -- エラー: first_name が GROUP BY に含まれていない
 SELECT department_id, first_name, COUNT(*)
 FROM employees
 GROUP BY department_id;
 
 -- エラーメッセージ:
 -- ERROR 1055 (42000): Expression #2 of SELECT list is not in GROUP BY clause
 -- and contains nonaggregated column 'employees.first_name' which is not
 -- functionally dependent on columns in GROUP BY clause


正しい書き方を以下に示す。

 -- 正: first_name を GROUP BY に含める
 SELECT department_id, first_name, COUNT(*)
 FROM employees
 GROUP BY department_id, first_name;
 
 -- 正: first_name を集約関数に含める
 SELECT department_id, GROUP_CONCAT(first_name), COUNT(*)
 FROM employees
 GROUP BY department_id;


ANY_VALUE 関数を以下に示す。
ANY_VALUE 関数は、グループ内の任意の値を返す関数である。
ONLY_FULL_GROUP_BY モードでも、ANY_VALUE を使用することで非集約列を SELECT リストに含めることができる。

 -- ANY_VALUE関数で回避
 SELECT department_id, ANY_VALUE(first_name), COUNT(*)
 FROM employees
 GROUP BY department_id;
 
 -- 注意: どの値が返されるかは不定である


ONLY_FULL_GROUP_BY モードの無効化を以下に示す。

 -- セッション単位で無効化
 SET SESSION sql_mode = (SELECT REPLACE(@@sql_mode, 'ONLY_FULL_GROUP_BY', ''));
 
 -- グローバルに無効化
 SET GLOBAL sql_mode = (SELECT REPLACE(@@sql_mode, 'ONLY_FULL_GROUP_BY', ''));


関数従属の例を以下に示す。

 -- employee_id がプライマリキーの場合、他の列は関数従属
 SELECT employee_id, first_name, last_name, COUNT(*)
 FROM employees
 GROUP BY employee_id;
 
 -- employee_id がプライマリキーであるため、first_name と last_name は
 -- employee_id に関数従属しており、エラーにならない



HAVING句との組み合わせ

HAVING 句は、グループ化後の結果に対してフィルタリングを行う。

WHERE 句 と HAVING 句の違いを以下に示す。

WHERE句とHAVING句の違い
項目 WHERE句 HAVING句
フィルタリングのタイミング グループ化前 グループ化後
条件の対象 個々の行 グループ
集約関数の使用 使用できない 使用できる


基本的な使用例を以下に示す。

 -- 従業員数が5人以上の部署のみを抽出
 SELECT department_id, COUNT(*)
 FROM employees
 GROUP BY department_id
 HAVING COUNT(*) >= 5;
 
 -- 平均給与が7000以上の部署のみを抽出
 SELECT department_id, AVG(salary)
 FROM employees
 GROUP BY department_id
 HAVING AVG(salary) >= 7000;


WHERE 句 と HAVING 句の組み合わせを以下に示す。

 -- 給与が5000以上の従業員を対象に、
 -- 従業員数が3人以上の部署の平均給与を計算
 SELECT department_id, COUNT(*), AVG(salary)
 FROM employees
 WHERE salary >= 5000
 GROUP BY department_id
 HAVING COUNT(*) >= 3;


複数の条件を指定する例を以下に示す。

 -- 従業員数が5人以上、かつ平均給与が7000以上の部署
 SELECT department_id, COUNT(*), AVG(salary)
 FROM employees
 GROUP BY department_id
 HAVING COUNT(*) >= 5 AND AVG(salary) >= 7000;


エイリアスの使用を以下に示す。

 -- HAVING句でエイリアスを使用
 SELECT department_id, COUNT(*) AS emp_count, AVG(salary) AS avg_salary
 FROM employees
 GROUP BY department_id
 HAVING emp_count >= 5 AND avg_salary >= 7000;



ウインドウ関数としての使用

MySQL 8.0以降では、集約関数を OVER() 句と組み合わせてウインドウ関数として使用できる。
ウインドウ関数として使用した場合、行を削減せず各行が保持される。

基本的な使用例を以下に示す。

 -- 各従業員の給与と全体の平均給与を表示
 SELECT employee_id,
        first_name,
        salary,
        AVG(salary) OVER() AS avg_salary
 FROM employees;
 
 -- 結果例:
 -- +-------------+------------+--------+------------+
 -- | employee_id | first_name | salary | avg_salary |
 -- +-------------+------------+--------+------------+
 -- |         100 | John       |   8000 |       6500 |
 -- |         101 | Jane       |   7000 |       6500 |
 -- |         102 | Bob        |   5000 |       6500 |
 -- +-------------+------------+--------+------------+


PARTITION BY 句を以下に示す。
PARTITION BY 句は、ウインドウを部分的に分割して、部分ごとの集約を行う。

 -- 部署ごとの平均給与を各行に表示
 SELECT employee_id,
        first_name,
        department_id,
        salary,
        AVG(salary) OVER(PARTITION BY department_id) AS dept_avg_salary
 FROM employees;
 
 -- 結果例:
 -- +-------------+------------+---------------+--------+------------------+
 -- | employee_id | first_name | department_id | salary | dept_avg_salary  |
 -- +-------------+------------+---------------+--------+------------------+
 -- |         100 | John       |            10 |   8000 |             7000 |
 -- |         101 | Jane       |            10 |   6000 |             7000 |
 -- |         102 | Bob        |            20 |   5000 |             5500 |
 -- |         103 | Alice      |            20 |   6000 |             5500 |
 -- +-------------+------------+---------------+--------+------------------+


ORDER BY 句を以下に示す。
ORDER BY 句は、ウインドウ内の行順序を指定する。
これにより、累積合計や移動平均などの計算が可能になる。

 -- 部署ごとの給与の累積合計
 SELECT employee_id,
        first_name,
        department_id,
        salary,
        SUM(salary) OVER(PARTITION BY department_id ORDER BY employee_id) AS cumulative_salary
 FROM employees;
 
 -- 結果例:
 -- +-------------+------------+---------------+--------+-------------------+
 -- | employee_id | first_name | department_id | salary | cumulative_salary |
 -- +-------------+------------+---------------+--------+-------------------+
 -- |         100 | John       |            10 |   8000 |              8000 |
 -- |         101 | Jane       |            10 |   6000 |             14000 |
 -- |         102 | Bob        |            20 |   5000 |              5000 |
 -- |         103 | Alice      |            20 |   6000 |             11000 |
 -- +-------------+------------+---------------+--------+-------------------+


フレーム指定を以下に示す。
ROWS 関数 または RANGE 句を使用して、ウインドウ内の特定の行範囲に対する集約を行うことができる。

 -- 現在行と直前の2行 (合計3行) の移動平均
 SELECT employee_id,
        first_name,
        salary,
        AVG(salary) OVER(ORDER BY employee_id ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS moving_avg
 FROM employees;
 
 -- 現在行と前後1行 (合計3行) の移動平均
 SELECT employee_id,
        first_name,
        salary,
        AVG(salary) OVER(ORDER BY employee_id ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS moving_avg
 FROM employees;


下表に、フレーム指定のキーワードを示す。

フレーム指定のキーワード
キーワード 説明
ROWS 物理的な行数でフレームを指定する
RANGE 値の範囲でフレームを指定する
UNBOUNDED PRECEDING パーティションの最初の行から
UNBOUNDED FOLLOWING パーティションの最後の行まで
CURRENT ROW 現在の行
n PRECEDING 現在行からn行前
n FOLLOWING 現在行からn行後


  • 累積合計の例
     -- 部署ごとの給与の累積合計 (明示的なフレーム指定)
     SELECT employee_id,
            first_name,
            department_id,
            salary,
            SUM(salary) OVER(
               PARTITION BY department_id
               ORDER BY employee_id
               ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
            ) AS cumulative_salary
     FROM employees;
    

  • 移動平均の例
     -- 直近3件の給与の移動平均
     SELECT employee_id,
            first_name,
            salary,
            AVG(salary) OVER(
               ORDER BY employee_id
               ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
            ) AS moving_avg_3
     FROM employees;
    


下表に、サポートされる集約関数を示す。
MySQL 8.0.12以降では、以下に示す集約関数がウインドウ関数としてサポートされている。

ウインドウ関数としてサポートされる集約関数 (MySQL 8.0.12以降)
集約関数
COUNT
SUM
AVG
MAX
MIN
GROUP_CONCAT
JSON_ARRAYAGG
JSON_OBJECTAGG



パフォーマンス

インデックスの活用

集約関数のパフォーマンスは、インデックスの有無に大きく影響される。

MAX / MIN 関数とインデックスの関係を以下に示す。

  • インデックスが存在する列に対して MAX / MIN を使用する場合、インデックスを活用した高速な検索が可能である。
  • これは、ORDER BY column DESC LIMIT 1 (MAX) や ORDER BY column ASC LIMIT 1 (MIN) と同等の最適化が行われるためである。


 -- インデックスが存在する場合、高速に実行される
 SELECT MAX(employee_id) FROM employees;
 
 -- EXPLAIN で確認
 EXPLAIN SELECT MAX(employee_id) FROM employees;
 -- type: index (インデックススキャン)


COUNT 関数とインデックスの関係を以下に示す。

  • InnoDBでは、COUNT(*) は最小の利用可能なセカンダリインデックスをスキャンする。
  • プライマリキーより小さいセカンダリインデックスが存在する場合、そちらを優先的に使用する。


 -- セカンダリインデックスが存在する場合、そちらをスキャン
 SELECT COUNT(*) FROM employees;
 
 -- EXPLAIN で確認
 EXPLAIN SELECT COUNT(*) FROM employees;
 -- key: index_name (セカンダリインデックス)


GROUP BY とインデックスの関係を以下に示す。

  • GROUP BY 列にインデックスが存在する場合、ソート処理が不要になる場合がある。
  • EXPLAINUsing temporary; Using filesort が表示されない場合、インデックスが活用されている。


 -- department_idにインデックスが存在する場合、高速に実行される
 SELECT department_id, COUNT(*)
 FROM employees
 GROUP BY department_id;
 
 -- EXPLAINで確認
 EXPLAIN SELECT department_id, COUNT(*) FROM employees GROUP BY department_id;
 -- Extra: Using index (インデックスのみでクエリを実行)


大量データの集計

大量のデータを集計する場合、パフォーマンスに注意が必要である。

一時テーブルの使用を以下に示す。

  • GROUP BY でグループ化を行う場合、内部的に一時テーブル (TEMPORARY TABLE) が使用される可能性がある。
  • 一時テーブルのサイズが tmp_table_size および max_heap_table_size を超える場合、ディスク上の一時テーブルが使用される。
  • ディスク上の一時テーブルは、メモリ上の一時テーブルより遅い。


 -- 一時テーブルのサイズ制限を確認
 SHOW VARIABLES LIKE 'tmp_table_size';
 SHOW VARIABLES LIKE 'max_heap_table_size';
 
 -- EXPLAIN で一時テーブルの使用を確認
 EXPLAIN SELECT department_id, COUNT(*) FROM employees GROUP BY department_id;
 -- Extra: Using temporary (一時テーブルを使用)


パフォーマンス改善の方法を以下に示す。

  • インデックスの追加
    GROUP BY 列にインデックスを追加する。
  • 一時テーブルサイズの拡張
    tmp_table_size および max_heap_table_size を増やす。
  • パーティショニング
    大量データをパーティション分割して、集計対象を絞る。


GROUP_CONCATの制限

GROUP_CONCAT は、出力制限に注意が必要である。

出力制限を以下に示す。

  • group_concat_max_len (デフォルト1024バイト)
    GROUP_CONCAT の出力最大長を制御する。
    この制限を超える場合、無警告で切り詰められる。
  • max_allowed_packet
    最大パケットサイズも制限要因となる。


切り詰め時の挙動を以下に示す。

  • 制限を超えた場合、無警告で切り詰められる。
  • 警告 (WARNING) は生成されない。
  • SHOW WARNINGS; でも確認できない。


推奨される対策を以下に示す。

  • セッション単位で group_concat_max_len を拡張する。
  • または、グローバルに group_concat_max_len を拡張する。


 -- セッション単位で拡張
 SET SESSION group_concat_max_len = 1000000;
 
 -- グローバルに拡張
 SET GLOBAL group_concat_max_len = 1000000;


DISTINCT性能

DISTINCT を使用する集約関数は、追加の処理が必要なためパフォーマンスが低下する傾向がある。

パフォーマンス特性を以下に示す。

  • COUNT(DISTINCT column)
    DISTINCT 処理のため、COUNT(column) より遅い傾向がある。
    内部的に一時テーブルを使用する。
  • SUM(DISTINCT column) / AVG(DISTINCT column)
    同様に、DISTINCT 処理のため遅い傾向がある。


推奨される対策を以下に示す。

  • インデックスの追加
    DISTINCT 対象列にインデックスを追加する。
  • サブクエリの使用
    大量データの場合、サブクエリで事前に DISTINCT を行うことで高速化できる場合がある。


 -- サブクエリで事前にDISTINCTを行う
 SELECT COUNT(*) FROM (
    SELECT DISTINCT department_id FROM employees
 ) AS distinct_depts;


WITH ROLLUPの影響

WITH ROLLUP は、追加の処理が必要なためパフォーマンスに影響する。

パフォーマンス特性を以下に示す。

  • WITH ROLLUP は、小計行を生成するため、追加の処理が必要である。
  • 複数列でロールアップする場合、さらに処理が増加する。


推奨される対策を以下に示す。

  • インデックスの追加
    GROUP BY 列にインデックスを追加する。
  • UNION ALLの使用
    大量データの場合、UNION ALL で小計行を個別に計算する方が高速な場合がある。


 -- WITH ROLLUP の代わりに UNION ALL を使用
 SELECT department_id, COUNT(*) FROM employees GROUP BY department_id
 UNION ALL
 SELECT NULL, COUNT(*) FROM employees;



バージョン別の機能差異

MySQLのバージョンによって、集約関数の機能に差異がある。

下表に、MySQL 5.7とMySQL 8.0の主要な違いを示す。

MySQL 5.7 vs MySQL 8.0
機能 MySQL 5.7 MySQL 8.0
JSON_ARRAYAGG / JSON_OBJECTAGG 5.7.22以降でサポート サポート
ウインドウ関数 (OVER句) 未サポート サポート (8.0以降)
GROUPING関数 未サポート サポート (8.0.12以降)
ONLY_FULL_GROUP_BY デフォルトで有効 デフォルトで有効
COUNT関数の戻り値 BIGINT UNSIGNED BIGINT


MySQL 5.7での注意点を以下に示す。

  • ウインドウ関数は使用できない。
    OVER() 句はサポートされていない。
    代替として、サブクエリや自己結合を使用する必要がある。
  • GROUPING関数は使用できない。
    WITH ROLLUP で小計行を識別するには、NULL チェックのみを使用する。


MySQL 8.0での新機能を以下に示す。

  • ウインドウ関数
    集約関数を OVER() 句と組み合わせて使用できる。
    累積合計、移動平均等の計算が可能になる。
  • GROUPING関数
    WITH ROLLUP で小計行を識別できる。
  • 改善された集約関数のパフォーマンス
    インデックスの活用が改善されている。


MySQL 5.7でウインドウ関数を代替する例を以下に示す。

 -- MySQL 8.0 (ウインドウ関数)
 SELECT employee_id,
        salary,
        AVG(salary) OVER() AS avg_salary
 FROM employees;
 
 -- MySQL 5.7 (サブクエリで代替)
 SELECT e.employee_id,
        e.salary,
        (SELECT AVG(salary) FROM employees) AS avg_salary
 FROM employees e;