「SQL Server - 副問い合わせ(サブクエリ)」の版間の差分

提供:MochiuWiki : SUSE, EC, PCB
ナビゲーションに移動 検索に移動
 
(同じ利用者による、間の5版が非表示)
28行目: 28行目:


== SELECT句にある副問合せ ==
== SELECT句にある副問合せ ==
副問合せで記述すべきクエリとは、以下に示すようなものである。<br>
* インデックスを活用できるシンプルなサブクエリ
* 明確な目的を持った相関サブクエリ
* 集計関数を使用した効率的な計算
<br>
<code>SELECT</code>句にある副問い合わせの例を示す。<br>
<code>SELECT</code>句にある副問い合わせの例を示す。<br>
  <syntaxhighlight lang="sql">
  <syntaxhighlight lang="sql">
  SELECT Name, Date, Sales,
  SELECT Name, Date, Sales,
Sales > (SELECT AVG(Sales) FROM T_Sample ) as AboveAverage
        Sales > (SELECT AVG(Sales) FROM T_Sample ) as AboveAverage -- 売上の平均を求める副問い合わせ
                                                                    -- AboveAverageカラムには、売上の平均を超えている場合は1、それ以外の場合は0が代入される
  FROM T_Sample;
  FROM T_Sample;
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br>
3行目は、<code>SELECT</code>句に売上の平均を求める副問い合わせである。<br>
# 結果
AboveAverageカラムには、売上の平均を超えている場合は1、それ以外の場合は0が代入される。<br>
<br>
結果は、以下の通りである。<br>
  Name    Date    Sales    AboveAverage
  Name    Date    Sales    AboveAverage
  A        20210101 200      1
  A        20210101 200      1
  A        20210102 150      0
  A        20210102 150      0
  B        20210103 100      0
  B        20210103 100      0
<br>
==== スカラーサブクエリ (単一の値を返す) ====
<syntaxhighlight lang="sql">
-- 最小売上との差分を計算
SELECT Name, Sales,
        Sales - (SELECT MIN(Sales) FROM T_Sample) as DiffFromMin
FROM T_Sample;
</syntaxhighlight>
<br>
==== 相関サブクエリ (外部クエリの値を参照) ====
* サブクエリで外部クエリのテーブル (t1) を参照している。
*: t2.Date < t1.Date
* サブクエリが外部クエリの各行に対して実行される。
* スカラサブクエリとして使用している。
*: 単一の値を返す。
<br>
# 外部クエリがT_Sampleテーブルの各レコード (t1) を処理する。
# 各レコードに対して、その行の日付より前の全ての売上の平均を計算する。
# 結果として、各レコードの名前、日付、売上、その日付以前の平均売上が表示される。
<br>
これは時系列分析でよく使用されるパターンであり、各時点での過去の平均値を計算するものである。<br>
<br>
<syntaxhighlight lang="sql">
-- 各レコードより前の日付の平均売上を計算
SELECT Name, Date, Sales,
        (SELECT AVG(Sales)
        FROM T_Sample t2
        WHERE t2.Date < t1.Date) as PriorAvgSales
FROM T_Sample t1;
</syntaxhighlight>
<br>
* サブクエリで外部クエリのテーブル (t1) を参照している。
*: t2.Sales = t1.Sales
* サブクエリが外部クエリの各行に対して実行される。
* EXIST句を使用している。
<br>
<syntaxhighlight lang="sql">
-- 売上が存在するレコードを取得
SELECT * FROM T_Sample t1
WHERE EXISTS (
    SELECT 1 FROM T_Sample t2
    WHERE t2.Sales = t1.Sales
);
</syntaxhighlight>
<br>
==== IN句 / EXISTS句 / ANY句 / ALL句 ====
<syntaxhighlight lang="sql">
-- 平均以上の売上のレコードを取得
SELECT * FROM T_Sample
WHERE Sales > ANY (
    SELECT Sales FROM T_Sample
    WHERE Sales > 150
);
</syntaxhighlight>
<br>
==== FROM句のサブクエリ ====
<syntaxhighlight lang="sql">
-- 日付ごとの累計売上を計算
SELECT t1.Date, t1.Sales,
        (SELECT SUM(t2.Sales)
        FROM T_Sample t2
        WHERE t2.Date <= t1.Date) as CumulativeSales
FROM T_Sample t1;
</syntaxhighlight>
<br>
==== アンチパターン ====
* 過度に複雑なネストされたサブクエリ
<syntaxhighlight lang="sql">
-- 避けるべき例
SELECT * FROM T_Sample
WHERE Sales > (
    SELECT AVG(Sales) FROM (
      SELECT Sales FROM T_Sample
      WHERE Sales > (
          SELECT MIN(Sales) FROM T_Sample
      )
    )
);
</syntaxhighlight>
<br>
* JOINで代替可能な非効率なサブクエリ
<syntaxhighlight lang="sql">
-- 非効率な例
SELECT * FROM T_Sample t1
WHERE EXISTS (
    SELECT 1 FROM T_Sample t2
    WHERE t2.Name = t1.Name
);
-- 推奨される記述方法
SELECT DISTINCT t1.*
FROM T_Sample t1
JOIN T_Sample t2 ON t1.Name = t2.Name;
</syntaxhighlight>
<br>
* 不必要なサブクエリの使用
<syntaxhighlight lang="sql">
-- 非効率な例
SELECT Name,
        (SELECT MAX(Sales) FROM T_Sample) as MaxSales
FROM T_Sample;
-- 推奨される記述方法
SELECT Name, MAX(Sales) OVER() as MaxSales
FROM T_Sample;
</syntaxhighlight>
<br>
<u>※注意</u><br>
<u>パフォーマンスの観点から、以下に示すことに注意が必要である。</u><br>
* サブクエリは適切なインデックスが存在するか確認する。
* 可能な限りJOINやウィンドウ関数で代替することを検討する。
* 実行計画を確認し、パフォーマンスへの影響を評価する。
<br><br>
<br><br>


61行目: 176行目:
<br>
<br>
また、<code>FROM</code>句にある副問い合わせのSQL文は、<code>WITH</code>句を使用して記述することもできる。<br>
また、<code>FROM</code>句にある副問い合わせのSQL文は、<code>WITH</code>句を使用して記述することもできる。<br>
* WITH句の例
* [[SQL_Server_-_WITH|WITH句の例]]
<br><br>
<br><br>


81行目: 196行目:
相関副問い合わせは、主問い合わせのSQL文を実行した後、副問い合わせのSQL文を実行する。<br>
相関副問い合わせは、主問い合わせのSQL文を実行した後、副問い合わせのSQL文を実行する。<br>
* [[SQL_Server_-_EXISTS|EXISTS句の例 (存在判定 / 相関副問い合わせ)]]
* [[SQL_Server_-_EXISTS|EXISTS句の例 (存在判定 / 相関副問い合わせ)]]
* 相関副問い合わせ(相関サブクエリ)の例  
* [[SQL Server - 相関副問い合わせ(相関サブクエリ)|相関副問い合わせ(相関サブクエリ)の例]]
<br><br>
<br><br>


190行目: 305行目:
  C        20210103 100
  C        20210103 100
<br><br>
<br><br>
== その他の使用例 ==
==== サブクエリと集約関数の組み合わせ ====
<center>
{| class="wikitable" | style="background-color:#fefefe;"
|+ employees テーブル構造
! style="background-color:#00ffff;" | カラム名
! style="background-color:#00ffff;" | データ型
! style="background-color:#00ffff;" | 説明
! style="background-color:#00ffff;" | 制約
|-
| id || SERIAL || 従業員ID || PRIMARY KEY
|-
| department_name || VARCHAR(50) || 部署名 || NOT NULL
|-
| employee_name || VARCHAR(100) || 従業員名 || NOT NULL
|-
| position || VARCHAR(50) || 役職 || NOT NULL
|-
| salary || INTEGER || 給与 ||
|-
| hire_date || DATE || 入社日 || NOT NULL
|}
</center>
<br>
<center>
{| class="wikitable" | style="background-color:#fefefe;"
|+ employees テーブルのサンプルデータ
! style="background-color:#00ffff;" | id
! style="background-color:#00ffff;" | 部署名
! style="background-color:#00ffff;" | 従業員名
! style="background-color:#00ffff;" | 役職
! style="background-color:#00ffff;" | 給与
! style="background-color:#00ffff;" | 入社日
|-
| 1 || 営業部 || 山田太郎 || 営業担当 || 350000 || 2022-01-15
|-
| 2 || 営業部 || 鈴木一郎 || 営業担当 || 350000 || 2022-02-01
|-
| 3 || 営業部 || 佐藤花子 || 営業主任 || 400000 || 2021-04-01
|-
| 4 || 営業部 || 田中美咲 || 営業担当 || 350000 || 2022-03-15
|-
| 5 || 営業部 || 高橋健一 || 営業部長 || 600000 || 2020-01-01
|-
| 6 || 総務部 || 伊藤真理 || 総務担当 || 330000 || 2022-06-01
|-
| 7 || 総務部 || 渡辺裕子 || 総務担当 || 330000 || 2022-07-15
|-
| 8 || 総務部 || 加藤正男 || 総務主任 || 380000 || 2021-08-01
|-
| 9 || 総務部 || 中村智子 || 総務担当 || 330000 || 2022-09-01
|-
| 10 || IT部 || 小林洋介 || プログラマー || 400000 || 2022-01-15
|}
</center>
<br>
<center>
{| class="wikitable" | style="background-color:#fefefe;"
|+ インデックス定義
! style="background-color:#00ffff;" | インデックス名
! style="background-color:#00ffff;" | 対象カラム
! style="background-color:#00ffff;" | 説明
|-
| idx_department_name || department_name || 部署名での検索を高速化
|}
</center>
<br>
以下の例では、特定の部署の人数が10人以上の場合、その部署の従業員を抽出している。<br>
<br>
* IN句を使用した方法
*: 可読性が良い。
*: 全カラムを取得する場合に適している。
* 特定のカラムを選択する方法
*: 必要なカラムのみを指定する。
*: ORDER BYで結果を並び替えが可能
*: <br>
* EXISTS句を使用した方法
*: 大量データの場合、IN句よりもパフォーマンスが良いことがある。
*: 存在確認のみを行うため、メモリ使用量が少なくなる可能性がある。
<br>
<syntaxhighlight lang="sql">
-- 基本的な書き方 : 10人以上いる部署の全従業員情報を取得
SELECT *
FROM  employees
WHERE  department_name IN (
    SELECT  department_name
    FROM    employees
    GROUP BY department_name
    HAVING  COUNT(*) >= 10
);
-- 複数のカラムを指定する場合 : 10人以上いる部署の従業員の基本情報のみを取得
SELECT
    t1.department_name,
    t1.employee_name,
    t1.position
FROM  employees t1
WHERE t1.department_name IN (
    SELECT  department_name
    FROM    employees
    GROUP BY department_name
    HAVING  COUNT(*) >= 10
)
ORDER BY t1.department_name;
-- EXISTS句を使用した別の書き方 : 10人以上いる部署の全従業員情報を取得
SELECT *
FROM employees t1
WHERE EXISTS (
    SELECT  1
    FROM    employees t2
    WHERE    t2.department_name = t1.department_name
    GROUP BY t2.department_name
    HAVING  COUNT(*) >= 10
);
-- 部署ごとの人数を確認する
SELECT
    department_name,
    COUNT(*) as employee_count
FROM    employees
GROUP BY department_name
ORDER BY employee_count DESC;
</syntaxhighlight>
<br><br>
{{#seo:
|title={{PAGENAME}} : Exploring Electronics and SUSE Linux | MochiuWiki
|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,Podman,電気回路,電子回路,基板,プリント基板
|description={{PAGENAME}} - 電子回路とSUSE Linuxに関する情報 | This page is {{PAGENAME}} in our wiki about electronic circuits and SUSE Linux
|image=/resources/assets/MochiuLogo_Single_Blue.png
}}


__FORCETOC__
__FORCETOC__
[[カテゴリ:SQL_Server]]
[[カテゴリ:SQL_Server]]

2024年11月26日 (火) 22:42時点における最新版

概要

副問い合わせ(サブクエリ)とは、SELECT文の結果を別のSQL文で使用することである。
副問い合わせは、INSERT文、UPDATE文、DELETE文でも使用できる。

以下に、SELECT文の使用時の副問合せが記述できる箇所を示す。

  • SELECT句に記述する副問合せ
  • FROM句に記述する副問合せ
  • WHERE句に記述する副問合せ
  • HAVING句に記述する副問合せ


ここでは、例で使用するテーブルを下表に示す。

T_Sampleテーブル
Name Date Sales
A 20210101 200
B 20210102 150
C 20210103 100



SELECT句にある副問合せ

副問合せで記述すべきクエリとは、以下に示すようなものである。

  • インデックスを活用できるシンプルなサブクエリ
  • 明確な目的を持った相関サブクエリ
  • 集計関数を使用した効率的な計算


SELECT句にある副問い合わせの例を示す。

 SELECT Name, Date, Sales,
        Sales > (SELECT AVG(Sales) FROM T_Sample ) as AboveAverage  -- 売上の平均を求める副問い合わせ
                                                                    -- AboveAverageカラムには、売上の平均を超えている場合は1、それ以外の場合は0が代入される
 FROM T_Sample;


# 結果
Name     Date     Sales     AboveAverage
A        20210101 200       1
A        20210102 150       0
B        20210103 100       0


スカラーサブクエリ (単一の値を返す)

 -- 最小売上との差分を計算
 SELECT Name, Sales, 
        Sales - (SELECT MIN(Sales) FROM T_Sample) as DiffFromMin
 FROM T_Sample;


相関サブクエリ (外部クエリの値を参照)

  • サブクエリで外部クエリのテーブル (t1) を参照している。
    t2.Date < t1.Date
  • サブクエリが外部クエリの各行に対して実行される。
  • スカラサブクエリとして使用している。
    単一の値を返す。


  1. 外部クエリがT_Sampleテーブルの各レコード (t1) を処理する。
  2. 各レコードに対して、その行の日付より前の全ての売上の平均を計算する。
  3. 結果として、各レコードの名前、日付、売上、その日付以前の平均売上が表示される。


これは時系列分析でよく使用されるパターンであり、各時点での過去の平均値を計算するものである。

 -- 各レコードより前の日付の平均売上を計算
 SELECT Name, Date, Sales,
        (SELECT AVG(Sales) 
         FROM T_Sample t2 
         WHERE t2.Date < t1.Date) as PriorAvgSales
 FROM T_Sample t1;


  • サブクエリで外部クエリのテーブル (t1) を参照している。
    t2.Sales = t1.Sales
  • サブクエリが外部クエリの各行に対して実行される。
  • EXIST句を使用している。


 -- 売上が存在するレコードを取得
 SELECT * FROM T_Sample t1
 WHERE EXISTS (
    SELECT 1 FROM T_Sample t2
    WHERE t2.Sales = t1.Sales
 );


IN句 / EXISTS句 / ANY句 / ALL句

 -- 平均以上の売上のレコードを取得
 SELECT * FROM T_Sample 
 WHERE Sales > ANY (
    SELECT Sales FROM T_Sample 
    WHERE Sales > 150
 );


FROM句のサブクエリ

 -- 日付ごとの累計売上を計算
 SELECT t1.Date, t1.Sales,
        (SELECT SUM(t2.Sales) 
         FROM T_Sample t2 
         WHERE t2.Date <= t1.Date) as CumulativeSales
 FROM T_Sample t1;


アンチパターン

  • 過度に複雑なネストされたサブクエリ
 -- 避けるべき例
 SELECT * FROM T_Sample 
 WHERE Sales > (
    SELECT AVG(Sales) FROM (
       SELECT Sales FROM T_Sample 
       WHERE Sales > (
          SELECT MIN(Sales) FROM T_Sample
       )
    )
 );


  • JOINで代替可能な非効率なサブクエリ
 -- 非効率な例
 SELECT * FROM T_Sample t1
 WHERE EXISTS (
    SELECT 1 FROM T_Sample t2
    WHERE t2.Name = t1.Name
 );
 
 -- 推奨される記述方法
 SELECT DISTINCT t1.* 
 FROM T_Sample t1
 JOIN T_Sample t2 ON t1.Name = t2.Name;


  • 不必要なサブクエリの使用
 -- 非効率な例
 SELECT Name, 
        (SELECT MAX(Sales) FROM T_Sample) as MaxSales
 FROM T_Sample;
 
 -- 推奨される記述方法
 SELECT Name, MAX(Sales) OVER() as MaxSales
 FROM T_Sample;


※注意
パフォーマンスの観点から、以下に示すことに注意が必要である。

  • サブクエリは適切なインデックスが存在するか確認する。
  • 可能な限りJOINやウィンドウ関数で代替することを検討する。
  • 実行計画を確認し、パフォーマンスへの影響を評価する。



FROM句にある副問い合わせ

FROM句にある副問い合わせの例を示す。

 SELECT b.Name, b.Sales, b.Date
 FROM (SELECT AVG(Sales) as Sales_Average FROM T_Sample) a, T_Sample b
 WHERE a.Sales_Average < b.Sales;


2行目は、FROM句にSalesカラムの平均を求める副問い合わせである。
副問い合わせの結果をテーブルのように使用している。

結果は、以下の通りである。

Name     Date     Sales
A        20210101 200


また、FROM句にある副問い合わせのSQL文は、WITH句を使用して記述することもできる。



WHERE句にある副問い合わせ

WHERE句にある副問合せの例を示す。

 SELECT * FFROM T_Sample
 WHERE Sales > (SELECT AVG(Sales) FROM T_Sample);


2行目は、WHERE句に売上の平均を求める副問い合わせである。
2行目の副問い合わせのSQL文を実行した後、1行目の主問い合わせのSQL文が実行される。

結果は、以下の通りである。

Name     Data     Sales
A        20210101 200


また、WHERE句にEXISTS句を接続することにより、相関副問い合わせを記述できる。
相関副問い合わせは、主問い合わせのSQL文を実行した後、副問い合わせのSQL文を実行する。



HAVING句にある副問い合わせ

HAVING句にある副問い合わせの例である。

 SELECT Name, MAX(Sales) 
 FROM T_Sample
 GROUP BY Name 
 HAVING MAX(Sales) > (SELECT AVG(Sales) FROM T_Sample);


4行目は、HAVING句に売上の平均を求める副問い合わせである。

結果は、以下の通りである。

Name     MAX(Sales)
A        200


HAVING句については、以下を参照すること。

  • 複数の行をまとめる (集約関数 / GROUP BY / HAVING)



副問い合わせの結果が複数件の場合

ここでは、例で使用するテーブルを下表に示す。

T_Sampleテーブル
Name Date Sales
A 20210101 200
B 20210102 150
C 20210103 100


T_Companyテーブル
Company
A
D


IN句

IN句は、副問い合わせの結果が複数件の場合に使用する。

 SELECT * FROM T_Sample 
 WHERE Sales IN (SELECT Company FROM T_Company);


2行目の副問い合わせは、結果が複数件返るため(AとC)、条件はイコール(=)ではなくIN句を使用する。

結果は、以下の通りである。

Name     Date     Sales
A        20210101 200


= ANY句

= ANY句は、副問い合わせの結果が複数件の場合に使用する。

 SELECT * FROM T_Sample
 WHERE Name = ANY (SELECT Company FROM T_Company);


2行目の副問い合わせは、結果が複数件返るため(AとC)、条件はイコール(=)ではなく= ANY句を使用する。

結果は、以下の通りである。

Name     Date     Sales
A        20210101 200


※注意
MySQLでは、上記のSQL文の= ANY句は、IN句や= SOME句でも同様の結果となる。

NOT IN句

NOT IN句は、副問い合わせの結果が複数件かつ否定を行う場合に使用する。

 SELECT * FROM T_Sample 
 WHERE Name NOT IN (SELECT Company FROM T_Company);


2行目の副問い合わせは、結果が複数件返るため(AとC)、NOT IN句でAとD以外を抽出する。

結果は、以下の通りである。

Name     Date     Sales
B        20210102 150
C        20210103 100


<> ALL句

<> ALL句は、副問い合わせの結果が複数件かつ否定を行う場合に使用する。

 SELECT * FROM T_Sample
 WHERE Name <> ALL (SELECT Company FROM T_Company);


2行目の副問合せは、結果が複数件返るため(AとC)、<> ALL句でAとD以外を抽出する。

結果は、以下の通りである。

Name     Date     Sales
B        20210102 150
C        20210103 100



その他の使用例

サブクエリと集約関数の組み合わせ

employees テーブル構造
カラム名 データ型 説明 制約
id SERIAL 従業員ID PRIMARY KEY
department_name VARCHAR(50) 部署名 NOT NULL
employee_name VARCHAR(100) 従業員名 NOT NULL
position VARCHAR(50) 役職 NOT NULL
salary INTEGER 給与
hire_date DATE 入社日 NOT NULL


employees テーブルのサンプルデータ
id 部署名 従業員名 役職 給与 入社日
1 営業部 山田太郎 営業担当 350000 2022-01-15
2 営業部 鈴木一郎 営業担当 350000 2022-02-01
3 営業部 佐藤花子 営業主任 400000 2021-04-01
4 営業部 田中美咲 営業担当 350000 2022-03-15
5 営業部 高橋健一 営業部長 600000 2020-01-01
6 総務部 伊藤真理 総務担当 330000 2022-06-01
7 総務部 渡辺裕子 総務担当 330000 2022-07-15
8 総務部 加藤正男 総務主任 380000 2021-08-01
9 総務部 中村智子 総務担当 330000 2022-09-01
10 IT部 小林洋介 プログラマー 400000 2022-01-15


インデックス定義
インデックス名 対象カラム 説明
idx_department_name department_name 部署名での検索を高速化


以下の例では、特定の部署の人数が10人以上の場合、その部署の従業員を抽出している。

  • IN句を使用した方法
    可読性が良い。
    全カラムを取得する場合に適している。
  • 特定のカラムを選択する方法
    必要なカラムのみを指定する。
    ORDER BYで結果を並び替えが可能

  • EXISTS句を使用した方法
    大量データの場合、IN句よりもパフォーマンスが良いことがある。
    存在確認のみを行うため、メモリ使用量が少なくなる可能性がある。


 -- 基本的な書き方 : 10人以上いる部署の全従業員情報を取得
 SELECT *
 FROM   employees
 WHERE  department_name IN (
    SELECT   department_name
    FROM     employees
    GROUP BY department_name
    HAVING   COUNT(*) >= 10
 );
 
 -- 複数のカラムを指定する場合 : 10人以上いる部署の従業員の基本情報のみを取得
 SELECT 
    t1.department_name,
    t1.employee_name,
    t1.position
 FROM  employees t1
 WHERE t1.department_name IN (
    SELECT   department_name
    FROM     employees
    GROUP BY department_name
    HAVING   COUNT(*) >= 10
 )
 ORDER BY t1.department_name;
 
 -- EXISTS句を使用した別の書き方 : 10人以上いる部署の全従業員情報を取得
 SELECT *
 FROM employees t1
 WHERE EXISTS (
    SELECT   1
    FROM     employees t2
    WHERE    t2.department_name = t1.department_name
    GROUP BY t2.department_name
    HAVING   COUNT(*) >= 10
 );
 
 -- 部署ごとの人数を確認する
 SELECT 
    department_name,
    COUNT(*) as employee_count
 FROM     employees
 GROUP BY department_name
 ORDER BY employee_count DESC;