# PostgreSQL 的存儲過程怎么寫
## 目錄
1. [存儲過程概述](#存儲過程概述)
2. [PL/pgSQL基礎語法](#plpgsql基礎語法)
3. [創建存儲過程](#創建存儲過程)
4. [參數傳遞](#參數傳遞)
5. [流程控制](#流程控制)
6. [異常處理](#異常處理)
7. [事務管理](#事務管理)
8. [動態SQL](#動態sql)
9. [性能優化建議](#性能優化建議)
10. [實際應用案例](#實際應用案例)
11. [與函數的區別](#與函數的區別)
12. [最佳實踐](#最佳實踐)
## 存儲過程概述
PostgreSQL的存儲過程是使用PL/pgSQL語言編寫的數據庫端程序,具有以下特點:
- **服務器端執行**:減少網絡傳輸開銷
- **預編譯**:提高執行效率
- **事務控制**:可在過程中管理事務
- **復雜邏輯**:支持條件判斷、循環等編程結構
```sql
-- 簡單示例
CREATE OR REPLACE PROCEDURE greet(name TEXT)
AS $$
BEGIN
RSE NOTICE 'Hello, %!', name;
END;
$$ LANGUAGE plpgsql;
PL/pgSQL是PostgreSQL的過程語言擴展,語法特點包括:
塊結構:
[ <<label>> ]
[ DECLARE
declarations ]
BEGIN
statements
EXCEPTION
WHEN condition THEN
handler_statements
END [ label ];
變量聲明:
DECLARE
counter INTEGER := 0;
user_name VARCHAR(50);
start_time TIMESTAMP := NOW();
數據類型:支持所有PostgreSQL數據類型
基本創建語法:
CREATE [OR REPLACE] PROCEDURE procedure_name([parameters])
[LANGUAGE lang_name]
AS $$
-- 過程體
$$;
完整示例:
CREATE OR REPLACE PROCEDURE transfer_funds(
sender_id INT,
receiver_id INT,
amount DECIMAL(10,2)
)
LANGUAGE plpgsql
AS $$
DECLARE
sender_balance DECIMAL(10,2);
BEGIN
-- 檢查發送方余額
SELECT balance INTO sender_balance FROM accounts WHERE id = sender_id;
IF sender_balance < amount THEN
RSE EXCEPTION 'Insufficient funds: %', sender_balance;
END IF;
-- 執行轉賬
UPDATE accounts SET balance = balance - amount WHERE id = sender_id;
UPDATE accounts SET balance = balance + amount WHERE id = receiver_id;
-- 記錄交易
INSERT INTO transactions(from_account, to_account, amount, trans_date)
VALUES (sender_id, receiver_id, amount, NOW());
COMMIT;
END;
$$;
PostgreSQL存儲過程支持三種參數模式:
IN參數(默認):輸入參數
CREATE PROCEDURE add_employee(IN name TEXT, IN salary NUMERIC)
OUT參數:輸出參數
CREATE PROCEDURE get_stats(OUT max_salary NUMERIC, OUT min_salary NUMERIC)
INOUT參數:雙向參數
CREATE PROCEDURE increment_counter(INOUT counter INTEGER)
調用示例:
-- 調用OUT參數過程
CALL get_stats(NULL, NULL); -- 需要占位符
-- 更優雅的方式
DO $$
DECLARE
max_val NUMERIC;
min_val NUMERIC;
BEGIN
CALL get_stats(max_val, min_val);
RSE NOTICE 'Max: %, Min: %', max_val, min_val;
END;
$$;
-- IF-THEN-ELSE
IF condition THEN
statements
[ ELSIF condition THEN
statements ]
[ ELSE
statements ]
END IF;
-- CASE語句
CASE search_expression
WHEN expression THEN statements
[ WHEN expression THEN statements ... ]
[ ELSE statements ]
END CASE;
-- 基本LOOP
LOOP
statements
EXIT WHEN condition;
END LOOP;
-- WHILE循環
WHILE condition LOOP
statements
END LOOP;
-- FOR循環(整數范圍)
FOR i IN 1..10 LOOP
RSE NOTICE 'Counter: %', i;
END LOOP;
-- FOR循環(查詢結果)
FOR record IN SELECT * FROM employees LOOP
-- 處理每條記錄
END LOOP;
PostgreSQL提供完善的異常處理機制:
BEGIN
-- 可能出錯的代碼
EXCEPTION
WHEN division_by_zero THEN
-- 處理除以零錯誤
WHEN OTHERS THEN
-- 處理其他所有錯誤
RSE NOTICE 'Error: %', SQLERRM;
END;
常見異常條件:
- NO_DATA_FOUND
- TOO_MANY_ROWS
- INTEGRITY_CONSTRNT_VIOLATION
- CHECK_VIOLATION
存儲過程中可以控制事務:
CREATE PROCEDURE process_order(order_id INT)
AS $$
BEGIN
-- 自動開始事務
-- 鎖定訂單記錄
PERFORM * FROM orders WHERE id = order_id FOR UPDATE;
-- 檢查庫存
IF NOT check_inventory(order_id) THEN
RSE EXCEPTION 'Insufficient inventory';
END IF;
-- 更新庫存
UPDATE inventory SET quantity = quantity - 1
WHERE product_id IN (SELECT product_id FROM order_items WHERE order_id = order_id);
-- 標記訂單為已完成
UPDATE orders SET status = 'completed' WHERE id = order_id;
COMMIT; -- 顯式提交
EXCEPTION
WHEN OTHERS THEN
ROLLBACK; -- 出錯時回滾
RSE;
END;
$$ LANGUAGE plpgsql;
使用EXECUTE執行動態SQL:
CREATE PROCEDURE dynamic_query(table_name TEXT, column_name TEXT, search_value TEXT)
AS $$
DECLARE
query TEXT;
result RECORD;
BEGIN
query := format('SELECT * FROM %I WHERE %I = $1', table_name, column_name);
EXECUTE query INTO result USING search_value;
RSE NOTICE 'Found: %', result;
END;
$$ LANGUAGE plpgsql;
安全注意事項:
1. 使用format()函數和%I標識符占位符
2. 參數化查詢(USING子句)
3. 避免直接拼接SQL字符串
– 好 EXECUTE ‘UPDATE users SET status = \(1 WHERE id = \)2’ USING new_status, user_id;
2. **批量操作**:
```sql
-- 使用RETURNING子句獲取多行
FOR result IN
UPDATE products SET price = price * 1.1
WHERE category = 'electronics'
RETURNING id, name, price
LOOP
RSE NOTICE 'Updated: %', result;
END LOOP;
DECLARE
cur CURSOR FOR SELECT * FROM large_table;
batch_size INT := 1000;
batch RECORD[];
BEGIN
OPEN cur;
LOOP
FETCH FORWARD batch_size FROM cur INTO batch;
EXIT WHEN batch IS NULL;
-- 處理批次數據
END LOOP;
CLOSE cur;
END;
CREATE PROCEDURE archive_old_data(retention_months INT DEFAULT 12)
AS $$
DECLARE
cutoff_date DATE;
archived_count INT;
BEGIN
cutoff_date := CURRENT_DATE - (retention_months * INTERVAL '1 month');
-- 創建歸檔表(如果不存在)
CREATE TABLE IF NOT EXISTS archived_orders (LIKE orders INCLUDING ALL);
-- 歸檔數據
WITH moved_rows AS (
DELETE FROM orders
WHERE order_date < cutoff_date
RETURNING *
)
INSERT INTO archived_orders SELECT * FROM moved_rows;
GET DIAGNOSTICS archived_count = ROW_COUNT;
RSE NOTICE 'Archived % orders older than %', archived_count, cutoff_date;
-- 更新統計信息
ANALYZE orders;
ANALYZE archived_orders;
END;
$$ LANGUAGE plpgsql;
| 特性 | 存儲過程 | 函數 |
|---|---|---|
| 返回值 | 無或通過OUT參數 | 必須返回單個值或表 |
| 調用方式 | CALL | SELECT或表達式 |
| 事務控制 | 可以包含COMMIT/ROLLBACK | 不能控制事務 |
| 計劃緩存 | 無 | 有 |
| 使用場景 | 執行操作 | 計算和返回數據 |
命名規范:
update_customer_statusbilling_generate_invoice文檔注釋: “`sql /*
”`
錯誤處理:
測試策略:
DO $$
BEGIN
-- 測試用例1
BEGIN
CALL transfer_funds(1, 2, 100);
RSE NOTICE 'Test 1 passed';
EXCEPTION WHEN OTHERS THEN
RSE EXCEPTION 'Test 1 failed: %', SQLERRM;
END;
-- 測試用例2:余額不足
BEGIN
CALL transfer_funds(1, 2, 100000);
RSE EXCEPTION 'Test 2 should have failed';
EXCEPTION
WHEN OTHERS THEN
IF SQLSTATE = 'P0001' THEN
RSE NOTICE 'Test 2 passed (expected exception)';
ELSE
RSE EXCEPTION 'Test 2 failed with wrong exception: %', SQLERRM;
END IF;
END;
END;
$$;
版本控制:
OR REPLACEPostgreSQL存儲過程是強大的服務器端編程工具,通過合理使用可以: - 減少網絡往返 - 保證數據一致性 - 實現復雜業務邏輯 - 提高性能
掌握PL/pgSQL語言和存儲過程開發技巧,可以顯著提升數據庫應用的開發效率和運行性能。 “`
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。