預存程序是預先編譯的 T-SQL 程式碼區塊,封裝業務邏輯、減少網路往返、提升執行效率、強化安全性 (使用者僅需 EXECUTE 權限)。本文系統整理建立語法、參數選項、七大實戰範例模式與 C# 呼叫完整程式碼。
CREATE PROC[EDURE] [schema.]procedure_name [;number]
[ {@parameter data_type} [VARYING] [= default] [OUTPUT] ] [ ,...n ]
[ WITH { RECOMPILE | ENCRYPTION | RECOMPILE, ENCRYPTION } ]
[ FOR REPLICATION ]
AS
sql_statement [ ...n ]
| 選項 | 用途 |
|---|---|
;number | 同名程序分組,單一 DROP 移除整組 |
VARYING | 僅用於游標 OUTPUT 參數,表示動態結果集 |
DEFAULT | 參數預設值,呼叫時可省略 |
OUTPUT | 標記為輸出參數,可回傳值給呼叫端 |
RECOMPILE | 每次執行重新編譯,適用參數嗅探嚴重場景 |
ENCRYPTION | 加密 syscomments 定義文字,防止檢視/複寫 |
FOR REPLICATION | 僅複寫代理程式可執行,不得直接呼叫 |
CREATE PROC GetUserAccount AS SELECT * FROM UserAccount; GO
EXEC GetUserAccount;
CREATE PROC InsUserAccount AS
INSERT INTO UserAccount (UserName, PassWord, RegisterTime, RegisterIP)
VALUES (9, 9, '2013-01-02', 9); GO
EXEC InsUserAccount;
CREATE PROC InsUserAccountRet AS
INSERT INTO UserAccount (...) VALUES (10, 10, '2013-01-02', 10);
RETURN @@ROWCOUNT; GO
EXEC InsUserAccountRet; -- 可在 C# 取得 ReturnValue
CREATE PROC GetUserAccountOut @UserName NCHAR(20), @UserID INT OUTPUT AS
IF @UserName > 5 SELECT @UserID = COUNT(*) FROM UserAccount WHERE UserID > 25
ELSE SET @UserID = 1000; GO
DECLARE @Out INT; EXEC GetUserAccountOut '7', @Out OUTPUT; SELECT @Out;
CREATE PROC GetUserAccountAll @UserName NCHAR(20), @UserID INT OUTPUT AS
IF @UserName > 5 SELECT @UserID = COUNT(*) FROM UserAccount WHERE UserID > 25
ELSE SET @UserID = 1000;
RETURN @@ROWCOUNT; GO
CREATE PROC GetUserAccountOutRs @UserName NCHAR(20), @UserID INT OUTPUT AS
IF @UserName > 5 SELECT @UserID = COUNT(*) FROM UserAccount WHERE UserID > 25
ELSE SET @UserID = 1000;
SELECT * FROM UserAccount;
RETURN @@ROWCOUNT; GO
CREATE PROC GetMultiResults AS
SELECT * FROM UserAccount;
SELECT * FROM UserAccount WHERE UserID > 5; GO
EXEC GetMultiResults; -- C# DataSet.Tables[0], Tables[1]
public SqlCommand CreateCmd(string procName) {
var cmd = new SqlCommand(procName, conn) { CommandType = CommandType.StoredProcedure };
return cmd;
}
// 1. 單一結果集
var da = new SqlDataAdapter(CreateCmd("GetUserAccount"));
var ds = new DataSet(); da.Fill(ds); grid.DataSource = ds.Tables[0];
// 2. 無回傳值 DML
cmd = CreateCmd("InsUserAccount"); conn.Open();
lbl.Text = cmd.ExecuteNonQuery().ToString(); conn.Close();
// 3. 取得 RETURN 值
cmd = CreateCmd("InsUserAccountRet");
var pRet = new SqlParameter("ret", SqlDbType.Int) { Direction = ParameterDirection.ReturnValue };
cmd.Parameters.Add(pRet);
conn.Open(); cmd.ExecuteNonQuery(); conn.Close();
lbl.Text = pRet.Value.ToString();
// 4. 輸入/輸出參數
cmd = CreateCmd("GetUserAccountOut");
cmd.Parameters.Add("@UserName", SqlDbType.NChar, 20).Value = "7";
cmd.Parameters.Add("@UserID", SqlDbType.Int).Direction = ParameterDirection.Output;
conn.Open(); cmd.ExecuteNonQuery(); conn.Close();
lbl.Text = cmd.Parameters["@UserID"].Value.ToString();
// 5. 同時 RETURN + OUTPUT
cmd = CreateCmd("GetUserAccountAll");
cmd.Parameters.Add("@UserName", SqlDbType.NChar, 20).Value = "7";
cmd.Parameters.Add("@UserID", SqlDbType.Int).Direction = ParameterDirection.Output;
cmd.Parameters.Add("ret", SqlDbType.Int).Direction = ParameterDirection.ReturnValue;
conn.Open(); cmd.ExecuteNonQuery(); conn.Close();
// 讀取 cmd.Parameters["@UserID"].Value 及 cmd.Parameters["ret"].Value
// 6. 結果集 + OUTPUT
cmd = CreateCmd("GetUserAccountOutRs");
// ... 參數同 5 ...
var da2 = new SqlDataAdapter(cmd);
var ds2 = new DataSet(); da2.Fill(ds2); // ds2.Tables[0] 為結果集
// 7. 多重結果集
var da3 = new SqlDataAdapter(CreateCmd("GetMultiResults"));
var ds3 = new DataSet(); da3.Fill(ds3); // ds3.Tables[0], Tables[1]
| 變數 | 用途 |
|---|---|
| @@IDENTITY | 最後插入的識別值 (跨範圍,觸發器會干擾) |
| SCOPE_IDENTITY() | 當前範圍最後識別值 (建議使用) |
| @@ROWCOUNT | 上一陳述式影響列數 |
| @@ERROR | 上一陳述式錯誤號 (SQL 2005+ 建議 TRY/CATCH) |
| @@TRANCOUNT | 目前交易巢狀層級 |
| GETDATE() | 伺服器當前日期時間 |
| @@VERSION | 版本、處理器、建置日期 |
掌握預存程序參數模式與 C# 對應呼叫方式,即可構建高效、安全、可維護的資料存取層。