# データアナリスト面接のための上級SQL完全ガイド:サブクエリ、ピボット、クエリ最適化(2026年版) > データアナリスト面接で頻出する上級SQLトピックを網羅的に解説します。相関サブクエリ、条件付き集約によるピボット、EXPLAINによるクエリ最適化、インデックス戦略、アンチパターンまで、実践的なコード例とともに詳しく取り上げます。 - Published: 2026-05-15 - Updated: 2026-05-15 - Author: Anthony Fillion-Maillet - Tags: SQL, Data Analytics, Interview, PostgreSQL, Query Optimization - Reading time: 12 min --- データアナリストの採用面接では、基本的なSELECT文やJOINだけでなく、より高度なSQLの知識が求められる場面が増えています。サブクエリの使い分け、ピボットテーブルの構築、クエリ実行計画の読み解き方、インデックスの最適化戦略といったトピックは、2026年現在のデータアナリスト面接において頻繁に出題される重要分野です。本記事では、これらの上級SQLスキルを体系的に整理し、面接本番で即座に活用できる実践的なコード例とともに解説します。 > **面接官が見ているポイント** > > データアナリストの技術面接では、単にクエリが動作するかどうかだけでなく、パフォーマンスへの意識が評価されます。同じ結果を返す複数のアプローチの中から、なぜその方法を選んだのかを論理的に説明できることが、合格ラインを超える鍵となります。特にサブクエリとJOINの使い分け、EXISTS対INの選択理由、インデックスを活用した最適化の説明が求められます。 ## 相関サブクエリと非相関サブクエリの違い サブクエリには大きく分けて2種類あります。非相関サブクエリ(通常のサブクエリ)は外側のクエリから独立して一度だけ実行されます。一方、相関サブクエリは外側のクエリの各行に対して繰り返し評価されます。この違いはパフォーマンスに直接影響するため、面接では両者の動作原理を正確に説明できることが求められます。 以下は非相関サブクエリの典型例です。内側のSELECTが一度だけ実行され、その結果がスカラー値として外側のWHERE句で使用されます。 ```sql -- regular_subquery_threshold.sql -- Find all orders above the average order value SELECT order_id, customer_id, total_amount FROM orders WHERE total_amount > ( -- Executes once, returns a single scalar value SELECT AVG(total_amount) FROM orders ); ``` 次に、相関サブクエリの例です。外側のクエリの各行について、その従業員が所属する部署の平均給与が動的に計算されます。 ```sql -- correlated_subquery_department.sql -- Employees earning above their department average SELECT e.employee_id, e.name, e.department, e.salary FROM employees e WHERE e.salary > ( -- Re-evaluated for each employee's department SELECT AVG(e2.salary) FROM employees e2 WHERE e2.department = e.department ); ``` 相関サブクエリは直感的で読みやすいですが、行数が増えるにつれて実行コストが急激に増大します。面接では、この問題を認識した上で、CTEやJOINを使った最適化案を提示できることが重要です。以下のCTEを使ったアプローチでは、部署ごとの平均給与を一度だけ計算し、JOINで結合することでデータを単一パスで処理します。 ```sql -- optimized_with_cte.sql -- Same result, single pass over the data WITH dept_avg AS ( SELECT department, AVG(salary) AS avg_salary FROM employees GROUP BY department ) SELECT e.employee_id, e.name, e.department, e.salary FROM employees e JOIN dept_avg d ON e.department = d.department WHERE e.salary > d.avg_salary; ``` CTEは可読性とパフォーマンスの両面で優れた選択肢です。面接の場でも、まず相関サブクエリで問題を解き、その後CTEによる最適化を提示するという流れが高い評価を得やすいアプローチです。 ## データアナリスト面接で頻出するサブクエリパターン 面接では、サブクエリの基本構造に加えて、EXISTSとINの使い分けやSELECT句内のスカラーサブクエリといった応用パターンも問われます。 EXISTSは、条件に合致するレコードが存在するかどうかを判定する際に使用されます。INも同様の結果を返しますが、大規模データセットではEXISTSの方がパフォーマンス上有利です。EXISTSは条件に合致する最初の行が見つかった時点で評価を停止しますが、INはサブクエリの結果セット全体を生成する必要があるためです。 ```sql -- exists_vs_in.sql -- Customers who placed at least one order in 2026 (EXISTS -- preferred) SELECT c.customer_id, c.name FROM customers c WHERE EXISTS ( SELECT 1 FROM orders o WHERE o.customer_id = c.customer_id AND o.order_date >= '2026-01-01' ); -- Equivalent with IN (slower on large datasets) SELECT customer_id, name FROM customers WHERE customer_id IN ( SELECT customer_id FROM orders WHERE order_date >= '2026-01-01' ); ``` SELECT句内でサブクエリを使用するスカラーサブクエリは、各行に対して関連する集計値を付与する場面で活用されます。以下の例では、各商品に対してそのカテゴリ全体の売上合計を算出しています。 ```sql -- scalar_subquery_select.sql -- Each product with its category's total revenue SELECT p.product_id, p.product_name, p.category, ( SELECT SUM(oi.quantity * oi.unit_price) FROM order_items oi JOIN products p2 ON oi.product_id = p2.product_id WHERE p2.category = p.category ) AS category_total_revenue FROM products p; ``` スカラーサブクエリは便利ですが、カテゴリ数が多い場合はウィンドウ関数やCTEへの書き換えを検討すべきです。面接では「この手法の限界は何ですか」という追加質問に備えて、代替案を準備しておくことが推奨されます。 ## 条件付き集約によるピボットクエリ ピボットクエリは、行データを列形式に変換する手法であり、データアナリストの面接では定番の出題分野です。標準SQLでは、CASE式とSUM(またはCOUNT)を組み合わせた条件付き集約によってピボットを実現します。 以下の例では、商品ごとの月次売上を横方向に展開し、1行で各月の売上を確認できる形式に変換しています。 ```sql -- pivot_monthly_revenue.sql -- Monthly revenue pivot: one row per product, one column per month SELECT product_id, product_name, SUM(CASE WHEN EXTRACT(MONTH FROM order_date) = 1 THEN quantity * unit_price ELSE 0 END) AS jan_revenue, SUM(CASE WHEN EXTRACT(MONTH FROM order_date) = 2 THEN quantity * unit_price ELSE 0 END) AS feb_revenue, SUM(CASE WHEN EXTRACT(MONTH FROM order_date) = 3 THEN quantity * unit_price ELSE 0 END) AS mar_revenue, SUM(CASE WHEN EXTRACT(MONTH FROM order_date) = 4 THEN quantity * unit_price ELSE 0 END) AS apr_revenue, -- Repeat for remaining months SUM(quantity * unit_price) AS total_revenue FROM order_items oi JOIN orders o ON oi.order_id = o.order_id JOIN products p ON oi.product_id = p.product_id WHERE o.order_date >= '2026-01-01' GROUP BY product_id, product_name ORDER BY total_revenue DESC; ``` CASE式によるピボットは、列数が固定である場合に最も実用的です。集約関数をCOUNTに変えることで、売上金額ではなくイベント回数やセッション数のピボットにも応用できます。 ```sql -- pivot_user_activity.sql -- User activity: sessions by day of week and device type SELECT user_id, COUNT(CASE WHEN device_type = 'mobile' THEN 1 END) AS mobile_sessions, COUNT(CASE WHEN device_type = 'desktop' THEN 1 END) AS desktop_sessions, COUNT(CASE WHEN device_type = 'tablet' THEN 1 END) AS tablet_sessions, ROUND( COUNT(CASE WHEN device_type = 'mobile' THEN 1 END) * 100.0 / COUNT(*), 1 ) AS mobile_pct FROM user_sessions WHERE session_date >= CURRENT_DATE - INTERVAL '30 days' GROUP BY user_id HAVING COUNT(*) >= 5 -- Only users with meaningful activity ORDER BY mobile_pct DESC; ``` このパターンでは、HAVINGを使って十分なデータ量を持つユーザーのみをフィルタリングしている点も注目に値します。面接では、ピボット結果の信頼性を担保するためのフィルタリング戦略について質問されることがあります。 ## PostgreSQLのCROSSTABによる動的ピボット CASE式によるピボットは列を手動で定義する必要があるため、カテゴリ数が動的に変化するケースには不向きです。PostgreSQLでは`tablefunc`拡張のCROSSTAB関数を使用して、より柔軟なピボットを構築できます。 ```sql -- crosstab_dynamic_pivot.sql -- Enable the extension (once per database) CREATE EXTENSION IF NOT EXISTS tablefunc; -- Revenue by product and quarter using CROSSTAB SELECT * FROM crosstab( $$ SELECT product_name, 'Q' || EXTRACT(QUARTER FROM order_date)::TEXT AS quarter, SUM(quantity * unit_price) AS revenue FROM order_items oi JOIN orders o ON oi.order_id = o.order_id JOIN products p ON oi.product_id = p.product_id WHERE EXTRACT(YEAR FROM o.order_date) = 2026 GROUP BY product_name, quarter ORDER BY product_name, quarter $$, $$ VALUES ('Q1'), ('Q2'), ('Q3'), ('Q4') $$ ) AS pivot_table( product_name TEXT, q1_revenue NUMERIC, q2_revenue NUMERIC, q3_revenue NUMERIC, q4_revenue NUMERIC ); ``` CROSSTABは内部的に1番目のカラムを行識別子、2番目を列識別子、3番目を値として解釈します。面接では、CROSSTABの構文だけでなく、ピボット元のデータがORDER BYで正しくソートされている必要がある点も説明できると好印象です。なお、CROSSTABはPostgreSQL固有の機能であるため、他のRDBMSでは代替手段(SQL ServerのPIVOT句など)が必要となります。 ## クエリ最適化:EXPLAINプランとコスト分析 データアナリストの面接では、遅いクエリの原因特定と改善提案を求められることが少なくありません。PostgreSQLの`EXPLAIN ANALYZE`は、クエリの実行計画と実際の実行時間を表示する最も重要な診断ツールです。 ```sql -- explain_analyze_example.sql -- Analyze a slow query to identify bottlenecks EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT) SELECT c.customer_id, c.name, COUNT(o.order_id) AS order_count, SUM(o.total_amount) AS lifetime_value FROM customers c JOIN orders o ON c.customer_id = o.customer_id WHERE o.order_date >= '2025-01-01' GROUP BY c.customer_id, c.name HAVING SUM(o.total_amount) > 1000 ORDER BY lifetime_value DESC; ``` EXPLAINの出力を読み解く際のポイントは以下の通りです。Seq Scan(シーケンシャルスキャン)がフィルタ条件のある大規模テーブルに対して実行されている場合、インデックスの欠如が疑われます。Nested Loop結合が大量の行を処理している場合、Hash JoinやMerge Joinへの切り替えが検討されます。BUFFERSオプションは実際のディスクI/O量を示し、メモリ内にキャッシュされたページとディスクから読み込まれたページの比率を確認できます。 面接では、EXPLAINの出力を見て「ここがボトルネックです。インデックスを追加することでSeq ScanをIndex Scanに変換できます」という形で具体的な改善案を提示できることが期待されます。 ## 面接レベルのインデックス戦略 インデックスはクエリ最適化の中核をなす要素です。面接では、単にインデックスを作成できるだけでなく、どのカラムにどの種類のインデックスを適用すべきかを判断できることが求められます。 ```sql -- indexing_strategies.sql -- Index on order_date: filters a small percentage of rows CREATE INDEX idx_orders_date ON orders (order_date); -- Composite index for queries filtering on both columns CREATE INDEX idx_orders_customer_date ON orders (customer_id, order_date); -- Covering index: includes columns needed in SELECT -- Enables Index Only Scan (no table access) CREATE INDEX idx_orders_covering ON orders (customer_id, order_date) INCLUDE (total_amount, status); ``` 複合インデックスでは、カラムの順序が極めて重要です。WHERE句で最も選択性の高いカラムを先頭に配置することで、インデックスの絞り込み効率が最大化されます。カバリングインデックスは、INCLUDE句によりSELECTで必要なカラムもインデックスに含めることで、テーブル本体へのアクセスを完全に不要にします。これはIndex Only Scanと呼ばれ、I/Oコストを大幅に削減します。 部分インデックスは、テーブル全体ではなく特定の条件を満たす行のみをインデックス化する手法です。インデックスサイズが小さくなるため、メンテナンスコストと検索速度の両面で有利です。 ```sql -- partial_index.sql -- Only index active orders (smaller, faster index) CREATE INDEX idx_orders_active ON orders (customer_id, order_date) WHERE status = 'active'; -- Only index recent data for dashboard queries CREATE INDEX idx_orders_recent ON orders (order_date) WHERE order_date >= '2026-01-01'; ``` ダッシュボードクエリのように直近のデータのみを対象とするケースでは、部分インデックスがフルインデックスよりも大幅に高速に動作します。面接では、部分インデックスの適用条件と制約(WHERE句の条件がクエリのWHERE句と一致または包含関係にある必要がある点)を説明できることが差別化要因となります。 > **インデックスのアンチパターンに注意** > > インデックスを作成しても、クエリの書き方によってはオプティマイザがインデックスを使用しない場合があります。特に、インデックス付きカラムに対して関数を適用した場合、インデックスが無効化されることは面接で頻繁に問われるトピックです。また、過剰なインデックスの作成はINSERTやUPDATEのパフォーマンスを低下させるため、読み取りと書き込みのバランスを考慮する必要があります。 ## よくあるクエリのアンチパターン 面接の最終段階では、アンチパターンの認識と修正能力が試されることがあります。以下は実務でもよく見られる典型的な問題パターンです。 インデックス付きカラムに関数を適用すると、データベースエンジンはインデックスを使用できなくなります。代わりに、範囲比較を使用してインデックスを活用する形に書き換えます。 ```sql -- anti_patterns.sql -- BAD: function on indexed column disables the index SELECT * FROM orders WHERE EXTRACT(YEAR FROM order_date) = 2026; -- GOOD: range comparison uses the index SELECT * FROM orders WHERE order_date >= '2026-01-01' AND order_date < '2027-01-01'; ``` NOT INにNULLが含まれる場合の挙動は、SQLにおける代表的な落とし穴です。サブクエリの結果にNULL値が1つでも含まれると、NOT IN条件は全行に対してUNKNOWN(偽に準ずる)を返し、結果セットが空になります。NOT EXISTSはこの問題を安全に回避します。 ```sql -- not_in_null_trap.sql -- BAD: returns empty if any customer_id is NULL in subquery SELECT * FROM customers WHERE customer_id NOT IN ( SELECT customer_id FROM orders ); -- GOOD: NOT EXISTS handles NULLs correctly SELECT * FROM customers c WHERE NOT EXISTS ( SELECT 1 FROM orders o WHERE o.customer_id = c.customer_id ); ``` 面接では、このNULLトラップについて「なぜNOT INが空の結果を返すのか」を三値論理(TRUE/FALSE/UNKNOWN)を用いて説明できると、SQLの深い理解をアピールできます。 > **実践的な練習方法** > > 上記のクエリパターンは、手元のPostgreSQL環境で実際に実行して動作を確認することが最も効果的な学習方法です。EXPLAIN ANALYZEを使ってインデックスの有無による実行計画の違いを観察し、行数やデータ分布を変えながらパフォーマンス特性を体感してください。面接本番では、実際に手を動かした経験から得られる具体的な知見が説得力を生みます。 ## まとめ データアナリスト面接における上級SQLの出題範囲は広いですが、以下のポイントを押さえることで体系的に対策できます。 - **サブクエリの使い分け**:非相関サブクエリは一度だけ実行され、相関サブクエリは行ごとに再評価されます。パフォーマンスが問題となる場合はCTEやJOINへの書き換えを検討します - **EXISTSとINの選択**:大規模データセットではEXISTSが優位です。NOT INはNULLトラップのリスクがあるため、NOT EXISTSの使用が推奨されます - **ピボットクエリ**:CASE式と集約関数の組み合わせが標準的なアプローチです。PostgreSQLではCROSSTABによる動的ピボットも活用できます - **EXPLAIN ANALYZE**:クエリのボトルネック特定にはBUFFERSオプション付きのEXPLAIN ANALYZEが不可欠です。Seq Scanの検出とインデックス追加による改善提案ができることが期待されます - **インデックス戦略**:複合インデックスのカラム順序、カバリングインデックスによるIndex Only Scan、部分インデックスによるサイズ削減が重要な最適化手法です - **アンチパターンの回避**:インデックスカラムへの関数適用とNOT INのNULLトラップは、面接で最も頻繁に問われる落とし穴です 面接対策では、これらのパターンを暗記するだけでなく、各手法を選択した理由とトレードオフを自分の言葉で説明できるようにしておくことが重要です。クエリの正確性だけでなく、パフォーマンスへの意識と改善提案の能力が、データアナリストとしての評価を左右します。 --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/ja/blog/data-analytics/advanced-sql-data-analyst-interviews