🧾SQL Server Stored Procedure 怎麼寫?SSMS 新手必學的預存程序教學(含範例與常見錯誤)
什麼是 Stored Procedure(預存程序)?用白話解釋
如果你把 SQL 想像成「你跟資料庫說要做什麼的指令」,那 Stored Procedure 就像是:
把一串常用的 SQL 步驟「打包成一個按鈕」,以後只要按一次就會自動完成。
對完全沒寫過的人來說,你可以把它想成:
- Excel 的巨集(Macro):把一堆動作錄起來,以後一鍵執行
- 咖啡機按鈕:你不需要每次研究怎麼磨豆、幾度水溫,你只要按「拿鐵」就會出來
Stored Procedure 的好處
- 可重複使用:同樣的查詢/更新不用每次重寫
- 較好維護:邏輯集中在資料庫端,改一次所有呼叫者都更新
- 安全性更好:可以只授權執行 SP,不必給使用者表格權限
- 效能常常更好:SQL Server 會對 SP 做最佳化(例如執行計畫快取)
在 SSMS 裡 Stored Procedure 通常怎麼寫?
你在 SSMS 裡新增一個 Stored Procedure,本質上就是寫一段 T-SQL,常見結構是:
-
CREATE PROCEDURE:建立新的 SP -
ALTER PROCEDURE:修改既有 SP -
EXEC:執行 SP -
BEGIN...END:把多行邏輯包起來 -
GO:告訴 SSMS「這段到這裡是一個批次」
範例 1:最簡單的 Stored Procedure(無參數)
假設你常常要查詢最近 50 筆訂單:
CREATE PROCEDURE dbo.usp_Order_GetRecent
AS
BEGIN
SELECT TOP (50)
OrderId, CustomerName, OrderDate, TotalAmount
FROM dbo.Orders
ORDER BY OrderDate DESC;
END;
GO
怎麼執行?
EXEC dbo.usp_Order_GetRecent;
📌 新手最常忽略:GO 不是 SQL 語法,是 SSMS 的批次分隔符。
範例 2:帶參數(最常見)
想查某個客戶的訂單:
CREATE PROCEDURE dbo.usp_Order_GetByCustomer
@CustomerId INT
AS
BEGIN
SELECT OrderId, OrderDate, TotalAmount
FROM dbo.Orders
WHERE CustomerId = @CustomerId
ORDER BY OrderDate DESC;
END;
GO
呼叫方式:
EXEC dbo.usp_Order_GetByCustomer @CustomerId = 101;
📌 觀念很重要:
參數就是你給這個「按鈕」的輸入值,例如客戶編號。
範例 3:參數有預設值(更貼近真實系統)
有時候你想提供預設條件,例如只看「已付款」:
CREATE PROCEDURE dbo.usp_Order_Search
@CustomerId INT,
@PaidStatus CHAR(1) = 'Y'
AS
BEGIN
SELECT OrderId, OrderDate, PaidStatus, TotalAmount
FROM dbo.Orders
WHERE CustomerId = @CustomerId
AND PaidStatus = @PaidStatus;
END;
GO
呼叫:
EXEC dbo.usp_Order_Search @CustomerId = 101;
-- PaidStatus 沒給就用預設 'Y'
範例 4:更新資料(UPDATE)— 很多企業系統都靠這個
例如更新會員的 Email:
CREATE PROCEDURE dbo.usp_Member_UpdateEmail
@MemberId INT,
@NewEmail NVARCHAR(200)
AS
BEGIN
UPDATE dbo.Members
SET Email = @NewEmail,
UpdatedAt = SYSDATETIME()
WHERE MemberId = @MemberId;
END;
GO
呼叫:
EXEC dbo.usp_Member_UpdateEmail @MemberId = 9, @NewEmail = N'test@example.com';
範例 5:回傳結果(OUTPUT 參數)— 常用來回傳單一值
很多時候前端或後端想知道「這個客戶有幾筆訂單」:
CREATE PROCEDURE dbo.usp_Order_CountByCustomer
@CustomerId INT,
@OrderCount INT OUTPUT
AS
BEGIN
SELECT @OrderCount = COUNT(*)
FROM dbo.Orders
WHERE CustomerId = @CustomerId;
END;
GO
使用:
DECLARE @Cnt INT;
EXEC dbo.usp_Order_CountByCustomer
@CustomerId = 101,
@OrderCount = @Cnt OUTPUT;
SELECT @Cnt AS OrderCount;
📌 新手必踩雷:
-
你在
EXEC呼叫時,OUTPUT 參數最後一定要寫OUTPUT
範例 6:交易(Transaction)+ 錯誤處理(企業必備)
像「扣庫存、寫訂單」這種動作,只要任何一步失敗,就要整筆回滾,避免資料不一致。
CREATE PROCEDURE dbo.usp_Order_CreateAndDeductStock
@ProductId INT,
@Qty INT,
@CustomerId INT
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRY
BEGIN TRAN;
-- 1) 檢查庫存是否足夠
IF EXISTS (
SELECT 1
FROM dbo.Inventory
WHERE ProductId = @ProductId
AND StockQty >= @Qty
)
BEGIN
-- 2) 扣庫存
UPDATE dbo.Inventory
SET StockQty = StockQty - @Qty
WHERE ProductId = @ProductId;
-- 3) 建訂單
INSERT INTO dbo.Orders(CustomerId, OrderDate, TotalAmount)
VALUES (@CustomerId, SYSDATETIME(), 0);
END
ELSE
BEGIN
-- 自訂錯誤:庫存不足
THROW 50001, 'Insufficient stock.', 1;
END
COMMIT TRAN;
END TRY
BEGIN CATCH
IF @@TRANCOUNT > 0 ROLLBACK TRAN;
THROW;
END CATCH
END;
GO
📌 你可以把它理解成:
- BEGIN TRAN:開始「一整包動作」
- COMMIT:全部成功才確定
- ROLLBACK:任何一步錯就全部取消
如何修改或刪除 Stored Procedure?
修改(ALTER)
ALTER PROCEDURE dbo.usp_Order_GetRecent
AS
BEGIN
SELECT TOP (20) OrderId, OrderDate
FROM dbo.Orders
ORDER BY OrderDate DESC;
END;
GO
刪除(DROP)
DROP PROCEDURE dbo.usp_Order_GetRecent;
新手最常見 6 個錯誤(你可以直接對照排查)
-
忘記寫
GO,導致建立失敗或混在同一批次 -
OUTPUT 參數呼叫忘記加
OUTPUT -
把
RETURN當成回傳資料(其實通常只用來回傳狀態碼) -
沒寫
SET NOCOUNT ON,應用程式可能多收到「影響幾列」訊息 - UPDATE/INSERT 沒包交易,資料容易寫一半就壞掉
- 沒有 TRY/CATCH,錯誤訊息不清楚也不容易追
結語:Stored Procedure 其實就是「把 SQL 打包成可重複使用的功能」
如果你剛開始學 SQL Server,請先掌握三件事就夠用 80%:
-
會寫
CREATE/ALTER PROCEDURE - 會用參數(Input + Output)
- 會用交易 + TRY/CATCH 保護資料一致性
留言
張貼留言