0. 簡介
【定義】:存儲過程(Stored Procedure) 是在大型數據庫系統中,一組為了完成特定功能的SQL 語句集,它存儲在數據庫中,一次編譯后永久有效,用戶通過指定存儲過程的名字並給出參數(如果該存儲過程帶有參數)來執行它。
【優缺點】:存儲過程優缺點都非常的明顯!幾乎每一篇討論存儲過程的文字,都是會說其他優點是balabala,缺點是balabala,然而最后作者的結論都是:“我不推薦使用存儲過程”。
具體的存儲過程的優缺點這里就不詳述了!
1. 語法細節
-
變量與變量之間使用逗號隔開,語句結尾無標點符號
-
聲明變量:
declare @variate_name variate_type
,例如聲明並賦值:declare @name nvarchar(50) ='shanzm'
變量賦值:set @variate_name =value
打印變量:print @variate_name
-
begin……end
之間的SQL語句稱之為一個代碼塊 -
可以使用
if……else
實現邏輯判斷 -
創建存儲過程:
create procedure pro_name
-
執行存儲過程:
execute pro_name
-
輸出參數:存儲過程返回的是SQL語句查閱結果,在定義參數后,添加
output
,設置為一個輸出參數(和C#中輸出參數類似),相當於多了一個返回值! -
創建存儲過程的基本形式:
create procedure pro_name_tableName
@param1 param1_type =defaultValue,--聲明參數的時候可以設置默認參數
@param2 param2_type,
as
begin
SET NOCOUNT ON;--關閉返回受影響行數
--sql語句
end
go
2. 示例1:模擬轉賬
①示例背景:使用存儲過程,模擬在一張存款表中實現用戶與用戶之間的轉賬
②准備工作1:在數據庫中創建表szmBank
CREATE TABLE [dbo].[szmBank]
(
[Id] [bigint] IDENTITY(1,1) NOT NULL,
[Balance] [decimal](18, 0) NOT NULL
)
添加一些測試數據:
Id Balance
--------------- ----------------
1 1000
2 2000
3 3000
③准備工作2:封裝C#代碼中的SQL輔助類SqlHelper
注意封裝的時候要有一個CommandType參數,決定是執行SQL語句還是存儲過程,
CommandType
是一個枚舉類型,其中Text值為執行SQL語句,StoreProcedure為執行存儲過程
具體封裝細節這里就不詳述了。
找到了2年前我封裝的一個SqlHelper.cs,常規使用沒有任何問題,僅供參考:
#region
// ===============================================================================
// Project Name :
// Project Description :
// ===============================================================================
// Class Name : SqlHelper
// Class Version : v1.0.0.0
// Class Description : SQL語句輔助類
// CLR : 4.0.30319.18408
// Author : shanzm
// Create Time : 2018-8-14 18:22:59
// Update Time : 2018-8-14 18:22:59
// ===============================================================================
// Copyright © SHANZM-PC 2018 . All rights reserved.
// ===============================================================================
#endregion
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
namespace _16StoreProcedure
{
public class SqlHelper
{
private static readonly string connStr =
ConfigurationManager.ConnectionStrings["connStr"].ConnectionString;
/// <summary>
/// 返回查詢結果的的表
/// </summary>
/// <param name="sql">SQL語句或存儲過程</param>
/// <param name="type">執行類型</param>
/// <param name="param">參數</param>
/// <returns></returns>
public static DataTable GetDataTable(string sql, CommandType type, params SqlParameter[] param)
{
using (SqlConnection conn = new SqlConnection(connStr))
{
using (SqlDataAdapter adapter = new SqlDataAdapter(sql, conn))
{
if (param != null)
{
adapter.SelectCommand.Parameters.AddRange(param);
}
adapter.SelectCommand.CommandType = type;
DataTable da = new DataTable();
adapter.Fill(da);
return da;
}
}
}
/// <summary>
/// 返回影響行數
/// </summary>
/// <param name="sql">SQL語句或存儲過程</param>
/// <param name="type">執行類型</param>
/// <param name="param">參數</param>
/// <returns></returns>
public static int ExecuteNonquery(string sql, CommandType type, params SqlParameter[] param)
{
using (SqlConnection conn = new SqlConnection(connStr))
{
using (SqlCommand cmd = new SqlCommand(sql, conn))
{
if (param != null)
{
cmd.Parameters.AddRange(param);
}
cmd.CommandType = type;
conn.Open();
return cmd.ExecuteNonQuery();
}
}
}
/// <summary>
/// 返回查詢結果的第一行第一個單元格的數據
/// </summary>
/// <param name="sql">SQL語句或存儲過程</param>
/// <param name="type">執行類型</param>
/// <param name="param">參數</param>
/// <returns></returns>
public static object ExecuteScalar(string sql, CommandType type, params SqlParameter[] param)
{
using (SqlConnection conn=new SqlConnection (connStr ))
{
using (SqlCommand cmd=new SqlCommand (sql,conn))
{
if (param !=null )
{
cmd.Parameters.AddRange(param);
}
cmd.CommandType = type ;
conn.Open();
return cmd.ExecuteScalar();
}
}
}
}
}
④編寫存儲過程:
在數據庫中:指定數據庫-->可編程性-->存儲過程-->右鍵:新建-->存儲過程:
SQL Server中編寫的SQL語句沒有默認的格式化,所有代碼排版按照我自己習慣進行Tab縮進
建議放到編輯器中查看下面的存儲過程,會好看一些!
SQL大小寫不敏感,我習慣小寫,方便閱讀!
-- =============================================
-- Author: shanzm
-- Create date: 2020年5月2日 19:56:51
-- Description: 模擬賬戶之間轉賬
-- =============================================
create procedure pro_transfer_szmbank
@from bigint,
@to bigint,
@balance decimal(18,0),
@returnNum int output--(1表示轉賬成功,2表示失敗,3表示余額不足)
as
begin
--判斷轉出賬戶是否有足夠的金額
declare @money decimal(18,0)
select @money=Balance from dbo.szmBank where Id=@from;
if @money-@balance>=0.1
--開始轉賬
begin
begin transaction
declare @sum int =0
--轉出賬戶扣錢
update szmBank set balance=balance-@balance where id=@from
set @sum=@sum+@@error
--轉入賬戶加錢
update szmBank set balance=balance+@balance where id=@to
set @sum=@sum+@@error
--判斷是否成功
if @sum<>0
begin
set @returnNum=2--轉賬失敗
rollback
end
else
begin
set @returnNum=1--轉賬成功
commit
end
end
else
begin
set @returnNum=3--余額不足
end
end
go
在數據庫中執行測試(F5):
--執行測試:
declare @ret int
execute pro_transfer_szmbank
@from='1',
@to='2',
@balance='10',
@returnNum=@ret output --注意1.輸出參數在執行語句中也是要表明"output"
--注意2.輸入輸出參數也是可以不賦值的,這里就沒有賦值,但是需要定義一個變量用於存儲輸出值,這里就是@ret(和使用輸出參數out的方式一樣)
--注意3.是@returnNum=@ret,而不@ret=@returnNum,這里就和函數中給參數賦值是一樣的,參數=value
print @ret --結果是打印:1,即存儲過程實現成功
【注意】:
-
@@ERROR
:當前一個語句遇到錯誤,則返回錯誤碼(比如除零錯誤的錯誤碼是:8134),否則返回0。需要注意的是
@@ERROR
只對其前一條sql語句有效,在每一條語句執行后會被立刻重置,因此應該在要驗證的語句執行后檢查數值或者是將它保存到局部變量中以備將來使用。 -
我們需要查看某個存儲過程,則可以使用數據中自帶的存儲過程查看:
sp_helptext pro_transfer_szmBank
-
修改現有的存儲過程,右鍵存儲過程-->修改:顯示的存儲過程只是把創建存儲過程中的
create
變為了alter
-
定義存成過程的時候參數類型默認的輸入參數,還有輸出參數(out) 和輸入輸出參數(output)
輸出參數和輸入輸出參數的區別就是:輸入參數在執行存儲過程的時候是不需要賦值的,輸入輸出參數在執行存儲過程的時候需要賦值 -
可以在SQL Server的SQL窗口選中某些SQL語句,點擊執行,即執行選中的SQL語句
⑤控制台中測試
新建一個控制台項目,在配置文件中添加連接字符串
因為封裝的SqlHelper中需要從配置文件中讀取數據庫連接字符串,所以添加引用:System.Configuration
static void Main(string[] args)
{
//轉出賬戶的Id
int from = 1;
//轉入賬戶的Id
int to = 2;
//轉賬金額
decimal balance = 10;
SqlParameter[] param =
{
new SqlParameter ("@from",from),
new SqlParameter("@to",to),
new SqlParameter ("@balance",balance),
//-------------------------------注意:這里設置為輸出參數
new SqlParameter ("@returnNum",System.Data.SqlDbType.Int){Direction=System.Data.ParameterDirection.Output }
};
//------------------------設置CommonType為StorProcedure類型
SqlHelper.ExecuteNonquery("pro_transfer_szmbank",System.Data.CommandType.StoredProcedure, param);
//------------------------獲取輸出參數
//根據輸出參數判斷轉賬結果
int outPutparam = Convert.ToInt16(param[3].Value);
switch (outPutparam)
{
case 1: Console.WriteLine($"success:從Id:{from}轉賬{balance}元到Id:{to}");break;
case 2: Console.WriteLine("error"); break;
case 3: Console.WriteLine("余額不足"); break;
}
Console.ReadKey();
}
測試結果:
success:從Id:1轉賬10元到Id:2
3. 示例2:測試返回DataTable
①存儲過程:
create procedure [dbo].[pro_ReturnDataTable]
as
begin
select Id as 用戶ID ,Balance as 余額 from szmBank;
end
go
②數據庫中測試:
execute pro_ReturnDataTable
測試結果:即顯示szmBank中的所有數據
③控制台中測試:
static void Main(string[] args)
{
DataTable dt = SqlHelper.GetDataTable("pro_ReturnDataTable", CommandType.StoredProcedure);
foreach (DataRow row in dt.Rows)
{
Console.WriteLine(row["用戶ID"].ToString() + ":" + row["余額"].ToString());
}
Console.ReadKey();
//TransferAccounts();
ReturnDataTable();
}
測試結果:即打印szmBank中的所有數據
4. 源代碼下載
-
所需要的數據庫表在示例中已說明,可以直接使用建表語句創建!
-
存儲過程的SQL語句在示例中完整的展示了,可以直接復制!