【初心者向け】SQLでの変数の使い方まとめ

データベース・SQL

SQLを書いていて、こんなことはありませんか?

  • 「同じ値を何度も書くのが面倒」
  • 「計算結果を一時的に保存したい」
  • 「条件によって値を変えたい」
  • 「複雑なクエリを読みやすくしたい」

そんなときに便利なのが、SQLの変数です。

変数を使うメリット

  • 値の再利用:同じ値を何度も書かなくて良い
  • 可読性向上:複雑なクエリが読みやすくなる
  • 保守性向上:値の変更が一箇所で済む
  • 計算効率化:中間結果を保存できる

重要な注意点 SQLの標準仕様では変数の定義がないため、データベースごとに書き方が異なります

この記事では、主要なデータベース(MySQL、PostgreSQL、SQL Server、Oracle)での変数の使い方を詳しく説明します。

スポンサーリンク

変数が必要になる具体的な場面

よくある使用場面

1. 計算で同じ値を使い回す

-- 悪い例:同じ値を何度も書く
SELECT 
    price * 1.10 AS tax_included_price,
    price * 1.10 * 0.05 AS commission,
    price * 1.10 - 100 AS discounted_price
FROM products;

-- 良い例:変数を使って明確にする
-- (MySQLの例)
SET @tax_rate = 1.10;
SELECT 
    price * @tax_rate AS tax_included_price,
    price * @tax_rate * 0.05 AS commission,
    price * @tax_rate - 100 AS discounted_price
FROM products;

2. 条件分岐での値の設定

-- 売上目標に応じた評価を設定

3. レポート作成での集計値の再利用

-- 月次売上レポートでの平均との比較

MySQL:ユーザー変数

基本的な使い方

変数の宣言と代入

SET @変数名 = 値;

基本例

-- 文字列変数
SET @name = '田中太郎';

-- 数値変数
SET @price = 1000;
SET @tax_rate = 1.10;

-- 日付変数
SET @today = CURDATE();

変数の使用

-- 変数を使った計算
SELECT @name AS 顧客名, @price * @tax_rate AS 税込価格;

実行結果

顧客名    | 税込価格
---------|--------
田中太郎  | 1100

実用的な例

例1:売上分析

-- 分析期間の設定
SET @start_date = '2024-01-01';
SET @end_date = '2024-03-31';
SET @target_amount = 1000000;

-- 期間売上の集計
SELECT 
    COUNT(*) AS 注文数,
    SUM(amount) AS 売上合計,
    AVG(amount) AS 平均単価,
    CASE 
        WHEN SUM(amount) >= @target_amount THEN '目標達成'
        ELSE '目標未達成'
    END AS 評価
FROM orders
WHERE order_date BETWEEN @start_date AND @end_date;

例2:価格計算

-- 料金設定
SET @base_price = 5000;
SET @discount_rate = 0.20;
SET @tax_rate = 0.10;

-- 最終価格の計算
SELECT 
    product_name,
    @base_price AS 基本料金,
    @base_price * @discount_rate AS 割引額,
    @base_price * (1 - @discount_rate) AS 割引後価格,
    @base_price * (1 - @discount_rate) * (1 + @tax_rate) AS 最終価格
FROM products;

変数への代入(SELECT INTO)

クエリ結果を変数に代入

-- 最新の注文金額を変数に代入
SELECT amount INTO @latest_amount 
FROM orders 
ORDER BY order_date DESC 
LIMIT 1;

-- 代入した値を使用
SELECT 
    @latest_amount AS 最新注文金額,
    @latest_amount * 1.1 AS 税込金額;

PostgreSQL:ブロック構文

DO文を使った基本例

PostgreSQLでは、通常のSELECT文で直接変数を使えません。DO文やファンション内で使用します。

基本構文

DO $$
DECLARE
    変数名 データ型 := 初期値;
BEGIN
    -- 処理
END $$;

基本例

DO $$
DECLARE
    customer_name VARCHAR(50) := '田中太郎';
    base_price NUMERIC := 1000;
    tax_rate NUMERIC := 1.10;
    final_price NUMERIC;
BEGIN
    final_price := base_price * tax_rate;
    RAISE NOTICE '顧客名: %, 税込価格: %', customer_name, final_price;
END $$;

実行結果

NOTICE:  顧客名: 田中太郎, 税込価格: 1100

実用的な例

例1:条件分岐処理

DO $$
DECLARE
    total_sales NUMERIC;
    target_sales NUMERIC := 1000000;
    achievement_rate NUMERIC;
    evaluation TEXT;
BEGIN
    -- 売上合計を取得
    SELECT SUM(amount) INTO total_sales 
    FROM orders 
    WHERE order_date >= '2024-01-01';
    
    -- 達成率の計算
    achievement_rate := (total_sales / target_sales) * 100;
    
    -- 評価の判定
    IF achievement_rate >= 100 THEN
        evaluation := '目標達成';
    ELSIF achievement_rate >= 80 THEN
        evaluation := '良好';
    ELSE
        evaluation := '要改善';
    END IF;
    
    -- 結果の表示
    RAISE NOTICE '売上合計: %, 達成率: %%, 評価: %', 
                 total_sales, achievement_rate, evaluation;
END $$;

例2:ループ処理

DO $$
DECLARE
    counter INTEGER := 1;
    max_count INTEGER := 5;
    result_text TEXT := '';
BEGIN
    WHILE counter <= max_count LOOP
        result_text := result_text || '項目' || counter || ' ';
        counter := counter + 1;
    END LOOP;
    
    RAISE NOTICE '結果: %', result_text;
END $$;

ファンクションでの変数使用

カスタム関数の作成

CREATE OR REPLACE FUNCTION calculate_discount_price(
    original_price NUMERIC,
    discount_rate NUMERIC
) RETURNS NUMERIC AS $$
DECLARE
    discount_amount NUMERIC;
    final_price NUMERIC;
BEGIN
    discount_amount := original_price * discount_rate;
    final_price := original_price - discount_amount;
    
    RETURN final_price;
END;
$$ LANGUAGE plpgsql;

-- 使用例
SELECT 
    product_name,
    price AS 元価格,
    calculate_discount_price(price, 0.20) AS 割引後価格
FROM products;

SQL Server(T-SQL):型指定が必要

基本的な使い方

変数の宣言

DECLARE @変数名 データ型;
DECLARE @変数名 データ型 = 初期値;

基本例

-- 変数の宣言と初期化
DECLARE @customer_name NVARCHAR(50) = N'田中太郎';
DECLARE @base_price INT = 1000;
DECLARE @tax_rate DECIMAL(3,2) = 1.10;
DECLARE @final_price DECIMAL(10,2);

-- 計算
SET @final_price = @base_price * @tax_rate;

-- 結果の表示
SELECT 
    @customer_name AS 顧客名,
    @base_price AS 基本価格,
    @final_price AS 税込価格;

実用的な例

例1:売上レポート

DECLARE @start_date DATE = '2024-01-01';
DECLARE @end_date DATE = '2024-03-31';
DECLARE @total_sales MONEY;
DECLARE @order_count INT;
DECLARE @avg_order MONEY;

-- 集計値の取得
SELECT 
    @total_sales = SUM(amount),
    @order_count = COUNT(*),
    @avg_order = AVG(amount)
FROM orders
WHERE order_date BETWEEN @start_date AND @end_date;

-- レポートの作成
SELECT 
    @start_date AS 開始日,
    @end_date AS 終了日,
    @order_count AS 注文数,
    @total_sales AS 売上合計,
    @avg_order AS 平均注文額,
    CASE 
        WHEN @total_sales >= 1000000 THEN N'目標達成'
        ELSE N'目標未達成'
    END AS 評価;

例2:条件分岐処理

DECLARE @score INT = 85;
DECLARE @grade NVARCHAR(10);
DECLARE @comment NVARCHAR(100);

-- 条件分岐
IF @score >= 90
BEGIN
    SET @grade = N'A';
    SET @comment = N'優秀';
END
ELSE IF @score >= 80
BEGIN
    SET @grade = N'B';
    SET @comment = N'良好';
END
ELSE IF @score >= 70
BEGIN
    SET @grade = N'C';
    SET @comment = N'普通';
END
ELSE
BEGIN
    SET @grade = N'D';
    SET @comment = N'要改善';
END

-- 結果表示
SELECT 
    @score AS 点数,
    @grade AS 評価,
    @comment AS コメント;

ストアドプロシージャでの活用

プロシージャの作成

CREATE PROCEDURE GetCustomerSummary
    @customer_id INT
AS
BEGIN
    DECLARE @customer_name NVARCHAR(100);
    DECLARE @total_orders INT;
    DECLARE @total_amount MONEY;
    DECLARE @last_order_date DATE;
    
    -- 顧客情報の取得
    SELECT @customer_name = name 
    FROM customers 
    WHERE id = @customer_id;
    
    -- 注文履歴の集計
    SELECT 
        @total_orders = COUNT(*),
        @total_amount = SUM(amount),
        @last_order_date = MAX(order_date)
    FROM orders
    WHERE customer_id = @customer_id;
    
    -- 結果の返却
    SELECT 
        @customer_name AS 顧客名,
        @total_orders AS 総注文数,
        @total_amount AS 総購入額,
        @last_order_date AS 最終注文日;
END;

-- 実行例
EXEC GetCustomerSummary @customer_id = 123;

Oracle(PL/SQL):最も高機能

基本的な使い方

無名ブロック

DECLARE
    customer_name VARCHAR2(50) := '田中太郎';
    base_price NUMBER := 1000;
    tax_rate NUMBER := 1.10;
    final_price NUMBER;
BEGIN
    final_price := base_price * tax_rate;
    
    DBMS_OUTPUT.PUT_LINE('顧客名: ' || customer_name);
    DBMS_OUTPUT.PUT_LINE('税込価格: ' || final_price);
END;
/

実用的な例

例1:カーソルを使った処理

DECLARE
    CURSOR c_orders IS
        SELECT order_id, amount, order_date
        FROM orders
        WHERE order_date >= SYSDATE - 30;
    
    v_total_amount NUMBER := 0;
    v_order_count NUMBER := 0;
    v_avg_amount NUMBER;
BEGIN
    FOR rec IN c_orders LOOP
        v_total_amount := v_total_amount + rec.amount;
        v_order_count := v_order_count + 1;
        
        DBMS_OUTPUT.PUT_LINE('注文ID: ' || rec.order_id || 
                           ', 金額: ' || rec.amount);
    END LOOP;
    
    IF v_order_count > 0 THEN
        v_avg_amount := v_total_amount / v_order_count;
        
        DBMS_OUTPUT.PUT_LINE('--- 集計結果 ---');
        DBMS_OUTPUT.PUT_LINE('注文数: ' || v_order_count);
        DBMS_OUTPUT.PUT_LINE('合計金額: ' || v_total_amount);
        DBMS_OUTPUT.PUT_LINE('平均金額: ' || v_avg_amount);
    END IF;
END;
/

例2:例外処理付きの処理

DECLARE
    v_customer_id NUMBER := 123;
    v_customer_name VARCHAR2(100);
    v_order_count NUMBER;
    v_not_found_error EXCEPTION;
BEGIN
    -- 顧客情報の取得
    BEGIN
        SELECT name INTO v_customer_name
        FROM customers
        WHERE id = v_customer_id;
    EXCEPTION
        WHEN NO_DATA_FOUND THEN
            RAISE v_not_found_error;
    END;
    
    -- 注文数の取得
    SELECT COUNT(*) INTO v_order_count
    FROM orders
    WHERE customer_id = v_customer_id;
    
    DBMS_OUTPUT.PUT_LINE('顧客名: ' || v_customer_name);
    DBMS_OUTPUT.PUT_LINE('注文数: ' || v_order_count);
    
EXCEPTION
    WHEN v_not_found_error THEN
        DBMS_OUTPUT.PUT_LINE('エラー: 顧客ID ' || v_customer_id || ' が見つかりません');
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('予期しないエラーが発生しました: ' || SQLERRM);
END;
/

データベース別の比較

機能比較

機能MySQLPostgreSQLSQL ServerOracle
変数宣言SET @var = valDECLARE var TYPEDECLARE @var TYPEvar TYPE := val
型指定不要必須必須必須
スコープセッションブロック内バッチ内ブロック内
条件分岐限定的IF/CASEIF/CASEIF/CASE
ループ処理なしWHILE/FORWHILEWHILE/FOR
例外処理なしEXCEPTIONTRY/CATCHEXCEPTION

使い分けの指針

MySQL

  • シンプルな計算や値の再利用
  • レポート作成での一時的な値保存
  • セッション内での値の受け渡し

PostgreSQL

  • 複雑なビジネスロジックの実装
  • 関数やプロシージャでの処理
  • データ移行やバッチ処理

SQL Server

  • エンタープライズでの本格的な開発
  • ストアドプロシージャでの業務処理
  • レポーティングサービスとの連携

Oracle

  • 大規模システムでの高度な処理
  • 複雑なデータ処理ロジック
  • パフォーマンスが重要なシステム

変数 vs プレースホルダ vs 一時テーブル

それぞれの使い分け

SQL変数

-- SQL内での計算に使用
SET @tax_rate = 1.10;
SELECT price * @tax_rate FROM products;

プレースホルダ(アプリケーション側)

-- アプリからの値受け渡し
SELECT * FROM users WHERE id = ?;  -- PHP
SELECT * FROM users WHERE id = $1; -- PostgreSQL

一時テーブル

-- 複雑な集計結果の保存
CREATE TEMPORARY TABLE temp_sales AS
SELECT category, SUM(amount) as total
FROM orders GROUP BY category;

使い分けの基準

用途推奨方法理由
アプリからの値渡しプレースホルダセキュリティ・パフォーマンス
SQL内での計算変数可読性・再利用性
大量データの中間結果一時テーブルパフォーマンス
複雑な集計処理変数 + 一時テーブル処理の明確化

実践的な活用例

売上分析レポート

MySQL版

-- 分析パラメータの設定
SET @analysis_year = 2024;
SET @target_amount = 1000000;

-- 月別売上分析
SET @jan_sales = (SELECT COALESCE(SUM(amount), 0) FROM orders WHERE YEAR(order_date) = @analysis_year AND MONTH(order_date) = 1);
SET @feb_sales = (SELECT COALESCE(SUM(amount), 0) FROM orders WHERE YEAR(order_date) = @analysis_year AND MONTH(order_date) = 2);
SET @mar_sales = (SELECT COALESCE(SUM(amount), 0) FROM orders WHERE YEAR(order_date) = @analysis_year AND MONTH(order_date) = 3);

-- レポート作成
SELECT 
    @analysis_year AS 分析年,
    @jan_sales AS '1月売上',
    @feb_sales AS '2月売上', 
    @mar_sales AS '3月売上',
    @jan_sales + @feb_sales + @mar_sales AS '四半期合計',
    CASE 
        WHEN @jan_sales + @feb_sales + @mar_sales >= @target_amount THEN '目標達成'
        ELSE '目標未達成'
    END AS '評価';

顧客セグメント分析

SQL Server版

DECLARE @high_value_threshold MONEY = 100000;
DECLARE @medium_value_threshold MONEY = 50000;
DECLARE @recent_months INT = 6;

WITH CustomerSegments AS (
    SELECT 
        c.customer_id,
        c.name,
        SUM(o.amount) AS total_amount,
        COUNT(o.order_id) AS order_count,
        MAX(o.order_date) AS last_order_date,
        CASE 
            WHEN SUM(o.amount) >= @high_value_threshold THEN 'プレミアム'
            WHEN SUM(o.amount) >= @medium_value_threshold THEN '一般'
            ELSE 'エントリー'
        END AS customer_segment,
        CASE 
            WHEN MAX(o.order_date) >= DATEADD(MONTH, -@recent_months, GETDATE()) THEN 'アクティブ'
            ELSE '休眠'
        END AS activity_status
    FROM customers c
    LEFT JOIN orders o ON c.customer_id = o.customer_id
    GROUP BY c.customer_id, c.name
)
SELECT 
    customer_segment,
    activity_status,
    COUNT(*) AS 顧客数,
    AVG(total_amount) AS 平均購入額,
    AVG(order_count) AS 平均注文数
FROM CustomerSegments
GROUP BY customer_segment, activity_status
ORDER BY customer_segment, activity_status;

トラブルシューティング

よくあるエラー

1. MySQL:変数が初期化されていない

-- エラーになる例
SELECT @undefined_var * 100;  -- NULL * 100 = NULL

-- 正しい例  
SET @var = COALESCE(@var, 0);  -- 初期化してから使用
SELECT @var * 100;

2. PostgreSQL:ブロック外での変数使用

-- エラーになる例
DECLARE var_name INTEGER := 100;  -- ブロック外ではエラー

-- 正しい例
DO $$
DECLARE
    var_name INTEGER := 100;
BEGIN
    RAISE NOTICE 'Value: %', var_name;
END $$;

3. SQL Server:データ型の不一致

-- エラーになる例
DECLARE @num INT = 'abc';  -- 文字列を数値型に代入

-- 正しい例
DECLARE @num INT = 123;
DECLARE @str NVARCHAR(10) = '123';
DECLARE @converted_num INT = CAST(@str AS INT);

デバッグのコツ

変数の値を確認

-- MySQL
SELECT @var_name AS debug_value;

-- PostgreSQL  
RAISE NOTICE 'Debug: %', var_name;

-- SQL Server
PRINT 'Debug: ' + CAST(@var_name AS NVARCHAR(50));

-- Oracle
DBMS_OUTPUT.PUT_LINE('Debug: ' || var_name);

おわりに

SQLの変数は、プログラミングの基本スキルです。

この記事で学んだこと

  • データベース別の変数の使い方
  • 実用的な活用例
  • トラブルシューティング
  • 他の機能との使い分け

最初は使用するデータベースの基本的な変数操作から始めて、徐々に複雑な処理にチャレンジしていきましょう。

コメント

タイトルとURLをコピーしました