🐥

SQLのクエリ内実行順序を理解する

2023/10/04に公開

この記事ではSQLクエリ中の各句が、データベース内でどのような順序で実行されているかについて解説します。
SQLの実行順序を理解することでクエリのトラブルシューティングに役立ちます。

SQLのクエリ内実行順序

この節では以下のSQLクエリを例に解説していきます。

SELECT DISTINCT 
    employees.name, 
    departments.department_name, 
    COUNT(*) OVER (PARTITION BY departments.department_id) as department_count
FROM 
    employees
JOIN 
    departments ON employees.department_id = departments.department_id
WHERE 
    employees.salary > 50000
GROUP BY 
    employees.name, 
    departments.department_name
HAVING 
    COUNT(*) > 1
ORDER BY 
    department_count DESC
LIMIT 10 OFFSET 5;

SQLクエリは一般的に以下の順で実行されます。

  1. FROM 句: まず、FROM句で指定されたテーブルが読み込まれます。この例では、employeesテーブルが読み込まれます。

  2. JOIN 句: 次に、JOIN句で指定されたテーブルとの結合が行われます。この例では、departmentsテーブルとの結合が行われます。

  3. WHERE 句: WHERE句で指定された条件に基づいて行がフィルタリングされます。この例では、employees.salary > 50000という条件に基づいて行がフィルタリングされます。

  4. GROUP BY 句: 次に、GROUP BY句で指定された列に基づいて行がグループ化されます。この例では、employees.namedepartments.department_nameに基づいて行がグループ化されます。

  5. HAVING 句: HAVING句で指定された条件に基づいてグループがフィルタリングされます。この例では、COUNT(*) > 1という条件に基づいてグループがフィルタリングされます。

  6. SELECT 句: SELECT句で指定された列が選択されます。この例では、employees.name, departments.department_name, COUNT(*) OVER (PARTITION BY departments.department_id)が選択されます。

  7. DISTINCT 句: DISTINCT句はSELECT句の後に適用され、重複する行を除外します。

  8. ORDER BY 句: ORDER BY句で指定された列に基づいて結果セットがソートされます。この例では、department_count DESCに基づいて結果セットがソートされます。

  9. LIMITOFFSET 句: 最後に、LIMITとOFFSET句で指定された数の行だけが結果セットから選択されます。この例では、最初の5行はスキップされ(OFFSET)、次の10行だけが選択されます(LIMIT)。

上記の実行順序が採用されている理由
  1. FROM 句と JOIN 句: これらの句は最初に実行されます。これは、データベースがどのテーブルからデータを取得するか、またそれらのテーブルがどのように関連しているかを知る必要があるからです。

  2. WHERE 句: WHERE句は早い段階で実行されます。これは、不要なデータを可能な限り早くフィルタリングし、後続の操作で処理するデータ量を減らすためです。

  3. GROUP BY 句と HAVING 句: これらの句は、特定のグループに対する集計操作(例えば、グループごとの平均値や合計値など)を行うために使用されます。これらの句はWHERE句の後で実行されます。なぜなら、WHERE句によってフィルタリングされたデータセット上でこれらの操作を行うことが一般的だからです。

  4. SELECT 句: SELECT句は比較的遅い段階で実行されます。これは、SELECT句が最終的な結果セットで表示する列を指定するため、それ以前のすべての操作(JOIN, WHERE, GROUP BY, HAVING)が完了して初めて適用可能だからです。

  5. DISTINCT 句: DISTINCT句はSELECT句の後に適用されます。これは、最終的な結果セットから重複する行を除外するためです。

  6. ORDER BY 句: ORDER BY句は結果セットを特定の順序(昇順または降順)でソートしますが、すべての行が生成されて初めて適用可能です。

  7. LIMITOFFSET 句: 最終的な結果セットから特定の数の行を選択するため、最後に実行されます。

以上がSQLクエリの一般的な実行順序です。

ここからはSQLで初心者がよく遭遇するエラーとその原因についてSQLの実行順序の観点で解説します。

1 - WHERE句で集約関数(SUM, AVGなど)を適用してエラーになる場合

SQLでは、WHERE句の中で集約関数(SUM、COUNT、AVGなど)を直接使用することはできません。
これは、GROUP BYを適用してから集約が行われますが、WHERE句がGROUP BY句よりも先に実行されるために発生します。
したがって、以下のようなクエリはエラーになります:

SELECT department_id, SUM(salary)
FROM employees
WHERE SUM(salary) > 50000
GROUP BY department_id;

この問題を解決するためには、WHERE句ではなくHAVING句の中で集約関数を実行します。
HAVING句はGROUP BY句の後に適用され、集約された結果に対して条件を適用することができます。
したがって、上記のクエリは以下のように書き換えることができます:

SELECT department_id, SUM(salary)
FROM employees
GROUP BY department_id
HAVING SUM(salary) > 50000;

このクエリでは、まずemployeesテーブルの各department_idごとにsalaryの合計を計算し(GROUP BYとSUM)、次にその合計が50,000より大きいdepartment_idだけを選択します(HAVING)。
このように、HAVING句を使用することで、集約関数を条件として使用することが可能になります。

2 - WHERE句でエイリアスを使用してエラーになる場合

SELECT句でASにより指定したエイリアスは、WHERE句では使用できません。
これは、SQLがWHERE句を先に評価し、その後でSELECT句を評価するため、WHERE句実行時点でそのエイリアスがまだ存在しないためです。

以下のようなクエリはエラーとなります。

SELECT column1 AS alias1
FROM table1
WHERE alias1 = 'value';

このクエリはエラーを返します。なぜなら、alias1WHERE句が評価される時点ではまだ存在しないからです。

解決策:

  • サブクエリを使用する方法:
SELECT *
FROM (
    SELECT column1 AS alias1
    FROM table1
) subquery
WHERE alias1 = 'value';

このクエリでは、サブクエリ内でエイリアスが最初に作成されるため、その後のWHERE句で使用することが可能です。

  • HAVING句を使用する方法:
SELECT column1 AS alias1
FROM table1
HAVING alias1 = 'value';

このクエリでは、HAVING句がSELECT句の後に評価されるため、エイリアスを使用できます。ただし、この方法は集約関数と一緒に使うことが一般的です。

まとめ

SQLクエリの実行順序について解説しました。
実行順序を理解することで単にWHERE句で集約関数やエイリアスが使用できないことを暗記するだけでなく、どのような理由でそうなっているかを理解することができます。

参考資料

https://speakerdeck.com/soudai/pgcon21j-tutorial
https://sqlbolt.com/lesson/select_queries_order_of_execution
https://dev.to/kanani_nirav/secret-to-optimizing-sql-queries-understand-the-sql-execution-order-28m1

Discussion