🧾SQL Server Stored Procedure 怎麼寫?SSMS 新手必學的預存程序教學(含範例與常見錯誤)

什麼是 Stored Procedure(預存程序)?用白話解釋

如果你把 SQL 想像成「你跟資料庫說要做什麼的指令」,那 Stored Procedure 就像是:

把一串常用的 SQL 步驟「打包成一個按鈕」,以後只要按一次就會自動完成。

對完全沒寫過的人來說,你可以把它想成:

  • Excel 的巨集(Macro):把一堆動作錄起來,以後一鍵執行
  • 咖啡機按鈕:你不需要每次研究怎麼磨豆、幾度水溫,你只要按「拿鐵」就會出來

Stored Procedure 的好處

  1. 可重複使用:同樣的查詢/更新不用每次重寫
  2. 較好維護:邏輯集中在資料庫端,改一次所有呼叫者都更新
  3. 安全性更好:可以只授權執行 SP,不必給使用者表格權限
  4. 效能常常更好: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 個錯誤(你可以直接對照排查)

  1. 忘記寫 GO,導致建立失敗或混在同一批次
  2. OUTPUT 參數呼叫忘記加 OUTPUT
  3. RETURN 當成回傳資料(其實通常只用來回傳狀態碼)
  4. 沒寫 SET NOCOUNT ON,應用程式可能多收到「影響幾列」訊息
  5. UPDATE/INSERT 沒包交易,資料容易寫一半就壞掉
  6. 沒有 TRY/CATCH,錯誤訊息不清楚也不容易追

結語:Stored Procedure 其實就是「把 SQL 打包成可重複使用的功能」

如果你剛開始學 SQL Server,請先掌握三件事就夠用 80%:

  • 會寫 CREATE/ALTER PROCEDURE
  • 會用參數(Input + Output)
  • 會用交易 + TRY/CATCH 保護資料一致性

留言

這個網誌中的熱門文章

🔍Vue.js 專案錯誤排查:解決 numericFields is not defined 與合併儲存格邏輯最佳化

🖥️遠端桌面連線完整新手指南:Windows RDP、Chrome Remote Desktop、AnyDesk、TeamViewer 一次搞懂

🔎EF Core 連 Oracle 出現 ORA-00600 [kpp_concatq:2] 的完整排錯指南(含 EF Core ToString/CultureInfo 錯誤)