PostgreSQL 一時テーブル完全ガイド:複雑な処理を高速化する最強テクニック

データベース・SQL

「複雑な集計処理でクエリが重すぎる…」 「中間結果を保存したいけど、永続テーブルを作るほどでもない」 「複数のユーザーが同時に処理しても、データが混ざらないようにしたい」

PostgreSQLで大量データを扱っていると、こんな悩みに直面しませんか?

一時テーブル(TEMPORARY TABLE)は、これらの問題をすべて解決する強力な機能です。セッション単位で独立したテーブルを作成し、複雑な処理を段階的に実行できます。適切に使えば、処理速度を10倍から100倍高速化することも可能です。

この記事では、PostgreSQLの一時テーブルの基本から高度な活用法まで、実例とパフォーマンス測定結果を交えながら徹底的に解説していきます。


スポンサーリンク

🎯 一時テーブルの基本概念

一時テーブルとは?

通常テーブルとの違い

一時テーブルの特徴:

-- 通常テーブル(すべてのユーザーが共有)
CREATE TABLE normal_table (
    id INTEGER,
    data TEXT
);

-- 一時テーブル(セッション専用)
CREATE TEMP TABLE temp_table (
    id INTEGER,
    data TEXT
);

-- または
CREATE TEMPORARY TABLE temp_table2 (
    id INTEGER,
    data TEXT
);

主な違い:

特徴通常テーブル一時テーブル
可視性全セッション作成したセッションのみ
永続性永続的セッション終了で自動削除
保存場所通常のテーブルスペースtemp_buffersメモリ/一時ファイル
トランザクションCOMMIT後も存在ON COMMIT設定で制御可能
同時アクセス可能不可(セッション独立)

一時テーブルのライフサイクル

セッションとの関係

-- セッション1で作成
CREATE TEMP TABLE session_data (
    user_id INTEGER,
    action TEXT,
    timestamp TIMESTAMP DEFAULT NOW()
);

INSERT INTO session_data VALUES (1, 'login', NOW());
SELECT * FROM session_data;  -- データが見える

-- セッション2(別の接続)
SELECT * FROM session_data;  -- エラー!テーブルが存在しない
-- ERROR: relation "session_data" does not exist

💻 一時テーブルの作成と基本操作

基本的な作成方法

CREATE TEMP TABLE構文

様々な作成パターン:

-- 1. 基本的な作成
CREATE TEMP TABLE temp_users (
    id SERIAL PRIMARY KEY,
    username VARCHAR(50) NOT NULL,
    email VARCHAR(100),
    created_at TIMESTAMP DEFAULT NOW()
);

-- 2. 既存テーブルの構造をコピー
CREATE TEMP TABLE temp_orders (LIKE orders INCLUDING ALL);

-- 3. SELECT結果から作成
CREATE TEMP TABLE high_value_customers AS
SELECT 
    customer_id,
    customer_name,
    SUM(order_total) as total_spent,
    COUNT(*) as order_count
FROM customers c
JOIN orders o ON c.customer_id = o.customer_id
GROUP BY c.customer_id, c.customer_name
HAVING SUM(order_total) > 10000;

-- 4. WITH NO DATAで構造のみコピー
CREATE TEMP TABLE temp_products (LIKE products INCLUDING ALL) WITH NO DATA;

ON COMMIT句の活用

トランザクション終了時の動作制御

3つのON COMMITオプション:

-- 1. PRESERVE ROWS(デフォルト):データを保持
BEGIN;
CREATE TEMP TABLE temp_preserve (id INT, data TEXT) ON COMMIT PRESERVE ROWS;
INSERT INTO temp_preserve VALUES (1, 'test');
COMMIT;
SELECT * FROM temp_preserve;  -- データが残る

-- 2. DELETE ROWS:コミット時にデータを削除(構造は残る)
BEGIN;
CREATE TEMP TABLE temp_delete (id INT, data TEXT) ON COMMIT DELETE ROWS;
INSERT INTO temp_delete VALUES (1, 'test');
SELECT * FROM temp_delete;  -- データあり
COMMIT;
SELECT * FROM temp_delete;  -- データなし(テーブルは存在)

-- 3. DROP:コミット時にテーブル自体を削除
BEGIN;
CREATE TEMP TABLE temp_drop (id INT, data TEXT) ON COMMIT DROP;
INSERT INTO temp_drop VALUES (1, 'test');
SELECT * FROM temp_drop;  -- データあり
COMMIT;
SELECT * FROM temp_drop;  -- エラー!テーブルが存在しない

インデックスと制約

一時テーブルの最適化

-- 一時テーブルにもインデックスを作成可能
CREATE TEMP TABLE temp_large_data (
    id SERIAL PRIMARY KEY,
    user_id INTEGER NOT NULL,
    product_id INTEGER NOT NULL,
    quantity INTEGER CHECK (quantity > 0),
    price DECIMAL(10,2),
    order_date DATE
);

-- インデックスを追加
CREATE INDEX idx_temp_user ON temp_large_data(user_id);
CREATE INDEX idx_temp_date ON temp_large_data(order_date);

-- 外部キー制約(同一セッション内の一時テーブル間でのみ可能)
CREATE TEMP TABLE temp_categories (
    category_id SERIAL PRIMARY KEY,
    category_name TEXT
);

CREATE TEMP TABLE temp_products (
    product_id SERIAL PRIMARY KEY,
    product_name TEXT,
    category_id INTEGER REFERENCES temp_categories(category_id)
);

🚀 実践的な活用パターン

パターン1:複雑な集計処理の分割

段階的な処理で可読性と性能を向上

-- 売上分析を段階的に実行
-- ステップ1:期間内の売上データを一時テーブルに格納
CREATE TEMP TABLE temp_sales_period AS
SELECT 
    o.order_id,
    o.customer_id,
    o.order_date,
    oi.product_id,
    oi.quantity,
    oi.unit_price,
    (oi.quantity * oi.unit_price) as line_total
FROM orders o
JOIN order_items oi ON o.order_id = oi.order_id
WHERE o.order_date BETWEEN '2024-01-01' AND '2024-12-31'
AND o.status = 'completed';

-- インデックスを追加して後続処理を高速化
CREATE INDEX idx_temp_sales_customer ON temp_sales_period(customer_id);
CREATE INDEX idx_temp_sales_product ON temp_sales_period(product_id);

-- ステップ2:顧客ごとの集計
CREATE TEMP TABLE temp_customer_summary AS
SELECT 
    customer_id,
    COUNT(DISTINCT order_id) as order_count,
    SUM(quantity) as total_items,
    SUM(line_total) as total_amount,
    AVG(line_total) as avg_order_value
FROM temp_sales_period
GROUP BY customer_id;

-- ステップ3:商品ごとの集計
CREATE TEMP TABLE temp_product_summary AS
SELECT 
    product_id,
    COUNT(DISTINCT customer_id) as unique_customers,
    SUM(quantity) as units_sold,
    SUM(line_total) as revenue,
    AVG(unit_price) as avg_price
FROM temp_sales_period
GROUP BY product_id;

-- 最終結果:顧客と商品のクロス分析
SELECT 
    c.customer_name,
    cs.order_count,
    cs.total_amount,
    p.product_name,
    ps.units_sold,
    ps.revenue
FROM temp_customer_summary cs
JOIN customers c ON cs.customer_id = c.customer_id
JOIN temp_product_summary ps ON ps.product_id IN (
    SELECT product_id 
    FROM temp_sales_period 
    WHERE customer_id = cs.customer_id
)
JOIN products p ON ps.product_id = p.product_id
ORDER BY cs.total_amount DESC
LIMIT 100;

パターン2:バッチ処理での活用

大量データの段階的処理

-- 大量データを効率的に処理する関数
CREATE OR REPLACE FUNCTION process_large_dataset(
    p_batch_size INTEGER DEFAULT 10000
)
RETURNS TABLE(processed_count INTEGER, execution_time INTERVAL) AS $$
DECLARE
    v_offset INTEGER := 0;
    v_total_processed INTEGER := 0;
    v_batch_count INTEGER;
    v_start_time TIMESTAMP := clock_timestamp();
BEGIN
    -- 処理用一時テーブルを作成
    CREATE TEMP TABLE temp_batch_process (
        id BIGINT,
        data JSONB,
        processed_at TIMESTAMP DEFAULT NOW()
    ) ON COMMIT DROP;
    
    -- バッチ処理ループ
    LOOP
        -- バッチデータを一時テーブルに挿入
        INSERT INTO temp_batch_process (id, data)
        SELECT 
            id,
            to_jsonb(row_to_json(t.*))
        FROM large_table t
        WHERE NOT EXISTS (
            SELECT 1 FROM processed_records pr 
            WHERE pr.id = t.id
        )
        ORDER BY id
        LIMIT p_batch_size
        OFFSET v_offset;
        
        GET DIAGNOSTICS v_batch_count = ROW_COUNT;
        
        EXIT WHEN v_batch_count = 0;
        
        -- バッチごとの処理
        WITH processed AS (
            INSERT INTO processed_records (id, data, status)
            SELECT 
                id,
                data,
                'completed'
            FROM temp_batch_process
            RETURNING id
        )
        SELECT COUNT(*) INTO v_batch_count FROM processed;
        
        v_total_processed := v_total_processed + v_batch_count;
        v_offset := v_offset + p_batch_size;
        
        -- 一時テーブルをクリア
        TRUNCATE temp_batch_process;
        
        -- 進捗を表示
        RAISE NOTICE 'Processed % records', v_total_processed;
        
        -- CPUを休ませる
        PERFORM pg_sleep(0.1);
    END LOOP;
    
    RETURN QUERY SELECT 
        v_total_processed,
        clock_timestamp() - v_start_time;
END;
$$ LANGUAGE plpgsql;

パターン3:再帰的処理での使用

階層データの展開

-- 組織階層を一時テーブルで処理
CREATE TEMP TABLE temp_org_hierarchy AS
WITH RECURSIVE org_tree AS (
    -- ベースケース:トップレベル
    SELECT 
        employee_id,
        manager_id,
        employee_name,
        department,
        1 as level,
        employee_name::TEXT as path
    FROM employees
    WHERE manager_id IS NULL
    
    UNION ALL
    
    -- 再帰ケース
    SELECT 
        e.employee_id,
        e.manager_id,
        e.employee_name,
        e.department,
        ot.level + 1,
        ot.path || ' > ' || e.employee_name
    FROM employees e
    JOIN org_tree ot ON e.manager_id = ot.employee_id
)
SELECT * FROM org_tree;

-- 階層ごとの統計
SELECT 
    level,
    COUNT(*) as employee_count,
    STRING_AGG(DISTINCT department, ', ') as departments
FROM temp_org_hierarchy
GROUP BY level
ORDER BY level;

パターン4:ETL処理での活用

データクレンジングと変換

-- ETL処理用の一時テーブル群
-- 1. ステージングテーブル
CREATE TEMP TABLE temp_staging (
    raw_data TEXT,
    loaded_at TIMESTAMP DEFAULT NOW()
);

-- CSVデータをロード
COPY temp_staging(raw_data) FROM '/tmp/import_data.csv';

-- 2. パース済みデータ
CREATE TEMP TABLE temp_parsed AS
SELECT 
    split_part(raw_data, ',', 1)::INTEGER as id,
    split_part(raw_data, ',', 2) as name,
    split_part(raw_data, ',', 3)::DATE as date,
    NULLIF(split_part(raw_data, ',', 4), '')::DECIMAL as amount
FROM temp_staging;

-- 3. データクレンジング
CREATE TEMP TABLE temp_cleaned AS
SELECT 
    id,
    TRIM(UPPER(name)) as normalized_name,
    date,
    COALESCE(amount, 0) as amount,
    CASE 
        WHEN amount IS NULL THEN 'missing_amount'
        WHEN amount < 0 THEN 'negative_amount'
        WHEN amount = 0 THEN 'zero_amount'
        ELSE 'valid'
    END as validation_status
FROM temp_parsed
WHERE id IS NOT NULL;

-- 4. 最終的なデータ投入
INSERT INTO production_table (id, name, date, amount)
SELECT id, normalized_name, date, amount
FROM temp_cleaned
WHERE validation_status = 'valid'
ON CONFLICT (id) DO UPDATE SET
    name = EXCLUDED.name,
    date = EXCLUDED.date,
    amount = EXCLUDED.amount,
    updated_at = NOW();

-- エラーレコードを記録
INSERT INTO error_log (record_id, error_type, raw_data)
SELECT 
    id,
    validation_status,
    raw_data
FROM temp_cleaned tc
JOIN temp_staging ts ON tc.id::TEXT = split_part(ts.raw_data, ',', 1)
WHERE validation_status != 'valid';

📊 パフォーマンス最適化

メモリ設定の調整

temp_buffersの最適化

-- 現在の設定を確認
SHOW temp_buffers;  -- デフォルト: 8MB

-- セッション単位で変更
SET temp_buffers = '256MB';

-- 一時テーブルのサイズを確認
SELECT 
    n.nspname as schema_name,
    c.relname as table_name,
    pg_size_pretty(pg_relation_size(c.oid)) as size,
    c.relkind,
    CASE 
        WHEN n.nspname LIKE 'pg_temp%' THEN 'TEMPORARY'
        ELSE 'PERMANENT'
    END as table_type
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind IN ('r', 't')
AND n.nspname LIKE 'pg_temp%'
ORDER BY pg_relation_size(c.oid) DESC;

ANALYZE実行で統計情報を更新

一時テーブルの最適化

-- 大量データを挿入後は必ずANALYZE
CREATE TEMP TABLE temp_large AS
SELECT * FROM orders
WHERE order_date >= '2024-01-01';

-- 統計情報を更新
ANALYZE temp_large;

-- 実行計画が改善される
EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM temp_large
WHERE customer_id = 1000;

UNLOGGED vs TEMP TABLE

用途に応じた選択

-- UNLOGGED TABLE(クラッシュ時にデータ消失するが高速)
CREATE UNLOGGED TABLE unlogged_work (
    id INTEGER,
    data TEXT
);

-- TEMP TABLE(セッション専用)
CREATE TEMP TABLE temp_work (
    id INTEGER,
    data TEXT
);

-- パフォーマンス比較
-- 大量INSERT時の速度
\timing on

-- UNLOGGED: 全ユーザー共有、WAL書き込みなし
INSERT INTO unlogged_work
SELECT generate_series(1, 1000000), 'data';
-- Time: 1823.456 ms

-- TEMP: セッション専用、WAL書き込みなし
INSERT INTO temp_work
SELECT generate_series(1, 1000000), 'data';
-- Time: 1654.321 ms (約10%高速)

🔧 高度な活用テクニック

グローバル一時テーブルの実装

複数セッションで構造を共有

-- グローバル一時テーブル風の実装
CREATE OR REPLACE FUNCTION create_global_temp_table()
RETURNS VOID AS $$
BEGIN
    -- セッション開始時に呼び出す
    IF NOT EXISTS (
        SELECT 1 FROM pg_tables 
        WHERE tablename = 'session_work_table'
        AND schemaname LIKE 'pg_temp%'
    ) THEN
        CREATE TEMP TABLE session_work_table (
            session_id TEXT DEFAULT pg_backend_pid()::TEXT,
            user_id INTEGER,
            data JSONB,
            created_at TIMESTAMP DEFAULT NOW()
        );
        
        CREATE INDEX idx_session_work_user ON session_work_table(user_id);
        CREATE INDEX idx_session_work_created ON session_work_table(created_at);
    END IF;
END;
$$ LANGUAGE plpgsql;

-- 各セッションで実行
SELECT create_global_temp_table();

一時テーブルを使ったピボット処理

動的なクロス集計

-- ピボットテーブルの作成
CREATE OR REPLACE FUNCTION create_pivot_table(
    p_year INTEGER
)
RETURNS TABLE(
    product_name TEXT,
    jan DECIMAL, feb DECIMAL, mar DECIMAL, apr DECIMAL,
    may DECIMAL, jun DECIMAL, jul DECIMAL, aug DECIMAL,
    sep DECIMAL, oct DECIMAL, nov DECIMAL, dec DECIMAL,
    total DECIMAL
) AS $$
BEGIN
    -- 月別売上を一時テーブルに格納
    CREATE TEMP TABLE temp_monthly_sales ON COMMIT DROP AS
    SELECT 
        p.product_name,
        EXTRACT(MONTH FROM o.order_date) as month_num,
        SUM(oi.quantity * oi.unit_price) as revenue
    FROM orders o
    JOIN order_items oi ON o.order_id = oi.order_id
    JOIN products p ON oi.product_id = p.product_id
    WHERE EXTRACT(YEAR FROM o.order_date) = p_year
    GROUP BY p.product_name, EXTRACT(MONTH FROM o.order_date);
    
    -- ピボット処理
    RETURN QUERY
    SELECT 
        t.product_name,
        SUM(CASE WHEN month_num = 1 THEN revenue END) as jan,
        SUM(CASE WHEN month_num = 2 THEN revenue END) as feb,
        SUM(CASE WHEN month_num = 3 THEN revenue END) as mar,
        SUM(CASE WHEN month_num = 4 THEN revenue END) as apr,
        SUM(CASE WHEN month_num = 5 THEN revenue END) as may,
        SUM(CASE WHEN month_num = 6 THEN revenue END) as jun,
        SUM(CASE WHEN month_num = 7 THEN revenue END) as jul,
        SUM(CASE WHEN month_num = 8 THEN revenue END) as aug,
        SUM(CASE WHEN month_num = 9 THEN revenue END) as sep,
        SUM(CASE WHEN month_num = 10 THEN revenue END) as oct,
        SUM(CASE WHEN month_num = 11 THEN revenue END) as nov,
        SUM(CASE WHEN month_num = 12 THEN revenue END) as dec,
        SUM(revenue) as total
    FROM temp_monthly_sales t
    GROUP BY t.product_name
    ORDER BY total DESC;
END;
$$ LANGUAGE plpgsql;

-- 使用例
SELECT * FROM create_pivot_table(2024);

一時テーブルでのウィンドウ関数

複雑な分析処理

-- ランキングと累積値を計算
CREATE TEMP TABLE temp_sales_analysis AS
WITH ranked_sales AS (
    SELECT 
        customer_id,
        order_date,
        total_amount,
        ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY order_date) as order_seq,
        RANK() OVER (ORDER BY total_amount DESC) as amount_rank,
        SUM(total_amount) OVER (
            PARTITION BY customer_id 
            ORDER BY order_date 
            ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
        ) as running_total,
        LAG(total_amount) OVER (PARTITION BY customer_id ORDER BY order_date) as prev_amount,
        LEAD(total_amount) OVER (PARTITION BY customer_id ORDER BY order_date) as next_amount
    FROM orders
    WHERE status = 'completed'
)
SELECT 
    *,
    running_total / order_seq as avg_order_value,
    CASE 
        WHEN prev_amount IS NULL THEN 0
        ELSE ((total_amount - prev_amount) / prev_amount * 100)
    END as growth_rate
FROM ranked_sales;

-- 分析結果を使用
SELECT 
    customer_id,
    AVG(growth_rate) as avg_growth,
    MAX(amount_rank) as best_rank,
    MAX(running_total) as lifetime_value
FROM temp_sales_analysis
GROUP BY customer_id
HAVING AVG(growth_rate) > 10
ORDER BY lifetime_value DESC;

⚠️ 注意点とトラブルシューティング

よくある問題と解決方法

問題1:一時テーブルが見つからない

-- エラー: relation "temp_table" does not exist

-- 原因1: 別のセッション
-- 解決: 同じセッション内で実行する

-- 原因2: トランザクションロールバック
BEGIN;
CREATE TEMP TABLE temp_test (id INT);
ROLLBACK;  -- テーブルも消える
SELECT * FROM temp_test;  -- エラー!

-- 原因3: ON COMMIT DROP
BEGIN;
CREATE TEMP TABLE temp_test (id INT) ON COMMIT DROP;
COMMIT;  -- ここでテーブル削除
SELECT * FROM temp_test;  -- エラー!

問題2:ディスク容量不足

-- 一時ファイルの使用状況を確認
SELECT 
    datname,
    temp_files,
    pg_size_pretty(temp_bytes) as temp_size
FROM pg_stat_database
WHERE datname = current_database();

-- 一時ファイルの場所を確認
SHOW temp_tablespaces;

-- 大きな一時テーブルの確認
SELECT 
    n.nspname,
    c.relname,
    pg_size_pretty(pg_total_relation_size(c.oid)) as size
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE n.nspname LIKE 'pg_temp%'
ORDER BY pg_total_relation_size(c.oid) DESC;

問題3:メモリ不足エラー

-- ERROR: out of memory
-- DETAIL: Failed on request of size 1048576.

-- 解決1: work_memを増やす
SET work_mem = '512MB';

-- 解決2: 処理を分割
-- 悪い例(一度に大量処理)
CREATE TEMP TABLE huge_temp AS
SELECT * FROM billion_row_table;

-- 良い例(分割処理)
CREATE TEMP TABLE huge_temp (LIKE billion_row_table);

DO $$
DECLARE
    v_offset INTEGER := 0;
    v_limit INTEGER := 100000;
BEGIN
    LOOP
        INSERT INTO huge_temp
        SELECT * FROM billion_row_table
        ORDER BY id
        LIMIT v_limit OFFSET v_offset;
        
        EXIT WHEN NOT FOUND;
        v_offset := v_offset + v_limit;
    END LOOP;
END $$;

セキュリティとアクセス制御

一時テーブルの権限管理

-- 一時テーブルは作成者のみアクセス可能
-- 他のユーザーは見ることもできない

-- セッションIDを含む命名規則
CREATE TEMP TABLE secure_temp AS
SELECT 
    pg_backend_pid() as session_id,
    current_user as owner,
    NOW() as created_at,
    * 
FROM sensitive_data
LIMIT 1000;

-- 権限は付与できない
GRANT SELECT ON temp_table TO other_user;  -- エラー!

📈 実践例:リアルタイムレポート生成

複雑なレポートを高速化

-- ダッシュボード用の集計処理
CREATE OR REPLACE FUNCTION generate_dashboard_report(
    p_start_date DATE,
    p_end_date DATE
)
RETURNS TABLE(
    metric_name TEXT,
    metric_value NUMERIC,
    comparison_period NUMERIC,
    change_percent NUMERIC
) AS $$
BEGIN
    -- 期間内データを一時テーブルに格納
    CREATE TEMP TABLE temp_period_data ON COMMIT DROP AS
    SELECT * FROM transactions
    WHERE transaction_date BETWEEN p_start_date AND p_end_date;
    
    CREATE INDEX idx_temp_period_date ON temp_period_data(transaction_date);
    CREATE INDEX idx_temp_period_type ON temp_period_data(transaction_type);
    
    -- 比較期間のデータ
    CREATE TEMP TABLE temp_comparison_data ON COMMIT DROP AS
    SELECT * FROM transactions
    WHERE transaction_date BETWEEN 
        p_start_date - (p_end_date - p_start_date + 1)::INTERVAL
        AND p_start_date - INTERVAL '1 day';
    
    -- KPI計算
    CREATE TEMP TABLE temp_metrics ON COMMIT DROP AS
    SELECT 
        'Total Revenue' as metric_name,
        SUM(amount) as current_value,
        (SELECT SUM(amount) FROM temp_comparison_data) as previous_value
    FROM temp_period_data
    WHERE transaction_type = 'sale'
    
    UNION ALL
    
    SELECT 
        'Transaction Count',
        COUNT(*),
        (SELECT COUNT(*) FROM temp_comparison_data)
    FROM temp_period_data
    
    UNION ALL
    
    SELECT 
        'Average Transaction',
        AVG(amount),
        (SELECT AVG(amount) FROM temp_comparison_data)
    FROM temp_period_data
    
    UNION ALL
    
    SELECT 
        'New Customers',
        COUNT(DISTINCT customer_id),
        (SELECT COUNT(DISTINCT customer_id) FROM temp_comparison_data)
    FROM temp_period_data;
    
    -- 結果を返す
    RETURN QUERY
    SELECT 
        metric_name,
        current_value,
        previous_value,
        ROUND(
            ((current_value - previous_value) / NULLIF(previous_value, 0)) * 100, 
            2
        ) as change_percent
    FROM temp_metrics;
END;
$$ LANGUAGE plpgsql;

-- 使用例
SELECT * FROM generate_dashboard_report('2024-01-01', '2024-01-31');

💡 ベストプラクティス

設計指針

いつ一時テーブルを使うべきか

-- 適している場合:
-- 1. 中間結果の保存
-- 2. 複雑なクエリの分割
-- 3. 同じデータを複数回参照
-- 4. バッチ処理

-- 適さない場合:
-- 1. 単純なクエリ(CTEで十分)
-- 2. 小規模データ(メモリで処理可能)
-- 3. 永続的なデータ保存が必要

パフォーマンスガイドライン

-- 推奨設定
SET temp_buffers = '256MB';  -- 大量の一時テーブル使用時
SET work_mem = '128MB';      -- ソート/ハッシュ処理

-- インデックス戦略
-- 1000行以上ならインデックス検討
CREATE TEMP TABLE temp_large (...);
INSERT INTO temp_large ...;  -- 大量データ
ANALYZE temp_large;          -- 統計更新
CREATE INDEX idx_temp ON temp_large(key_column);  -- インデックス作成

クリーンアップ戦略

-- 明示的な削除
DROP TABLE IF EXISTS temp_table;

-- 自動削除の活用
CREATE TEMP TABLE auto_cleanup (...) ON COMMIT DROP;

-- セッション終了時の確認
SELECT 
    n.nspname,
    c.relname,
    pg_size_pretty(pg_total_relation_size(c.oid)) as size
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE n.nspname LIKE 'pg_temp%';

📚 まとめ:一時テーブルでPostgreSQLを最大限活用

PostgreSQL一時テーブルの活用ポイント:

セッション独立でデータの混在を防ぐ複雑な処理を段階的に実行して高速化ON COMMIT句でライフサイクルを制御適切なインデックスとANALYZEで最適化メモリ設定(temp_buffers)の調整で性能向上

一時テーブルを使いこなせば、複雑な処理も劇的に高速化できます。

今すぐ実践すべき3つのステップ:

  1. 複雑なクエリを一時テーブルで分割してみる
  2. temp_buffersを適切に設定する
  3. ON COMMIT句を使い分けて効率化する

これらのテクニックで、PostgreSQLの真の力を引き出しましょう!


この記事が役立ったら、データベース処理で悩んでいる仲間にもシェアしてください。一緒にPostgreSQLマスターを目指しましょう!

コメント

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