SQL Server自定义函数、存储过程与触发器实战详解

月亮慢半拍
月亮慢半拍
发布时间:2026-10-13| 4人浏览过
原创

掌握SQL Server中的核心数据库对象是优化数据操作的关键。本文深入解析自定义函数(标量与表值)、带参存储过程(含输出参数)以及Before/After触发器的具体用法。结合银行交易模拟场景,提供完整的T-SQL代码示例,涵盖从基础查询封装到复杂业务逻辑校验的实际操作。

一、自定义函数的编写与调用

SQL Server中的函数主要分为系统函数和自定义函数。自定义函数根据返回值类型不同,可分为标量值函数(返回单个值)和表值函数(返回表格数据)。以下针对几种典型业务场景进行说明。

第1步:创建无参标量函数求和

目的:获取所有银行卡的总金额。

操作:定义返回类型为 money 的函数,使用聚合函数计算总和并返回。

create function GetSumCardMoney()
returns money 
as
begin
	declare @AllMOney money
	select @AllMOney = (select SUM(CardMoney) from BankCard)
	return @AllMOney
end

调用方式:在Select语句中需带上架构名(如dbo)。

select dbo.GetSumCardMoney()

第2步:创建带参标量函数查找姓名

目的:根据传入的账户ID获取对应的真实姓名。

操作:定义输入参数 @AccountId int,通过变量接收查询结果并返回。

create function GetNameById(@AccountId int)
returns varchar(20)
as
begin
	declare @RealName varchar(20)
	select @RealName = (select RealName from AccountInfo where AccountId = @AccountId)
	return @RealName
end

调用方式:

print dbo.GetNameById(2)

第3步:创建表值函数查询交易记录

目的:根据时间段查询复杂的关联交易数据。

方案一(多语句表值函数):适合逻辑较复杂,包含变量定义或中间处理的场景。

create function GetExchangeByTime(@StartTime varchar(30),@EndTime varchar(30))
returns @ExchangeTable table
(
	RealName varchar(30),
	CardNo varchar(30),
	MoneyInBank money,
	MoneyOutBank money,
	ExchangeTime smalldatetime
)
as
begin
	insert into @ExchangeTable
	select AccountInfo.RealName,CardExchange.CardNo,CardExchange.MoneyInBank,
	CardExchange.MoneyOutBank,CardExchange.ExchangeTime from CardExchange
	left join BankCard on CardExchange.CardNo = BankCard.CardNo
	left join AccountInfo on BankCard.AccountId = AccountInfo.AccountId
	where CardExchange.ExchangeTime between @StartTime+' 00:00:00' and @EndTime+' 23:59:59'
	return
end

方案二(内联表值函数):逻辑简单,直接返回一个SELECT语句,性能通常更高且语法更简洁。

create function GetExchangeByTime(@StartTime varchar(30),@EndTime varchar(30))
returns table
as
	return
	select AccountInfo.RealName,CardExchange.CardNo,CardExchange.MoneyInBank,
	CardExchange.MoneyOutBank,CardExchange.ExchangeTime from CardExchange
	left join BankCard on CardExchange.CardNo = BankCard.CardNo
	left join AccountInfo on BankCard.AccountId = AccountInfo.AccountId
	where CardExchange.ExchangeTime between @StartTime+' 00:00:00' and @EndTime+' 23:59:59'

调用方式:表值函数类似表使用,无需加 dbo. 前缀即可直接放在 FROM 后。

select * from GetExchangeByTime('2018-6-19','2018-6-19')

第4步:利用函数实现数据状态映射

目的将数字状态码转换为可读文本,并根据余额分级。

操作分别建立 GetGradeByMoney 和 GetStatusByNumber 两个标量函数,内部使用 IF...ELSE 判断。

create function GetGradeByMoney(@myMoney int)
returns varchar(10)
as
begin
	declare @result varchar(10)
	if @myMoney < 300000 
		set @result = '普通用户'
	else
		set @result = 'VIP用户'
	return @result
end

create function GetStatusByNumber(@myNum int)
returns varchar(10)
as
begin
	declare @result varchar(10)
	if @myNum = 1 set @result = '正常'
	else if @myNum = 2 set @result = '挂失'
	else if @myNum = 3 set @result = '冻结'
	else if @myNum = 4 set @result = '注销'
	else set @result = '异常'	
	return @result
end

查询调用:

select CardNo 卡号,AccountCode 身份证,RealName 姓名,CardMoney 余额,
dbo.GetGradeByMoney(CardMoney) 账户等级,dbo.GetStatusByNumber(CardState) 卡状态
from BankCard inner join AccountInfo on BankCard.AccountId = AccountInfo.AccountId

第5步:计算精确实岁

目的:根据出生日期计算当前实足年龄。

测试数据准备:

create table Emp
(
	EmpId int primary key identity(1,2),
	empName varchar(20),
	empSex varchar(4),   
	empBirth smalldatetime 
)

函数逻辑:先计算年份差,若本月小于出生月,或本月等于出生月但本日小于出生日,则年龄减1。

create function GetAgeByBirth(@birth smalldatetime)
returns int
as
begin
	declare @age int
	set @age = year(getdate()) - year(@birth)
	if month(getdate()) < month(@birth)
		set @age = @age - 1
	if month(getdate()) = month(@birth) and day(getdate()) < day(@birth)
		set @age = @age -1
	return @age
end

调用示例:

select *,dbo.GetAgeByBirth(empBirth) 年龄 from Emp

二、触发器的逻辑与控制

触发器是一种特殊的存储过程,它在特定事件发生时自动执行。主要类型包括:

  • After 触发器:在完成INSERT、UPDATE或DELETE操作之后执行。
  • Instead of 触发器:取代原本的操作执行,原操作不会发生,除非在触发器内部显式执行。

以下案例基于 Department (部门表)和 People (人员表)进行测试。

第1步:After Insert - 自动补全缺失部门

需求:插入员工时,若其所属部门不存在于部门表,则自动添加该部门,名称为“新部门”。

操作:利用 inserted 临时表检查是否存在。

create trigger tri_InsertPeople on People
after insert
as
if not exists(select * from Department where DepartmentId = (select DepartmentId from inserted))
	insert into Department(DepartmentId,DepartmentName)
	values((select DepartmentId from inserted),'新部门')

第2步:After Delete - 级联删除员工

需求:删除部门时,同时删除该部门下的所有员工。

操作:利用 deleted 临时表找到被删部门ID并清理人员表。

create trigger tri_DeleteDept on Department
after delete
as
delete from People where People.DepartmentId = 
(select DepartmentId from deleted)

第3步:Instead of Delete - 条件性删除

需求:仅当部门下没有员工时才允许删除该部门。

操作:使用 Instead of 拦截删除动作,判断 deleted 表中的ID在人员表中是否存在。

drop trigger tri_DeleteDept --若已存在需先删除
create trigger tri_DeleteDept on Department
Instead of delete
as
  if not exists(select * from People where DepartmentId = (select DepartmentId from deleted))
  begin
	delete from Department where DepartmentId = (select DepartmentId from deleted)
  end

第4步:After Update - 同步更新子表

需求:修改主表(部门)的主键ID时,同步更新外键表(人员)。

注意:在多行更新时,上述写法可能只处理最后一行或产生非预期结果,单行操作适用。

create trigger tri_UpdateDept on Department
after update
as
	update People set DepartmentId = (select DepartmentId from inserted)
	where DepartmentId = (select DepartmentId from deleted)

三、存储过程的高级应用

存储过程是预编译的SQL集合,支持输入输出参数及返回值,适用于复杂的业务流程。

第1步:无参存储过程查询极值

需求:查询余额最低的银行卡信息。

操作:

--方案一:使用Top 1
create proc proc_MinMoneyCard
as
    select top 1 CardNo 银行卡号,RealName 姓名,CardMoney 余额
    from BankCard inner join AccountInfo on BankCard.AccountId = AccountInfo.AccountId
    order by CardMoney asc

--方案二:使用MIN函数(若有多人余额同为最低,会全部显示)
create proc proc_MinMoneyCard_v2
as
    select CardNo 银行卡号,RealName 姓名,CardMoney 余额
    from BankCard inner join AccountInfo on BankCard.AccountId = AccountInfo.AccountId
    where CardMoney=(select MIN(CardMoney) from BankCard)

第2步:含输入参数的写入操作

需求:模拟存钱,传入卡号和金额,更新余额并记录流水。

create proc proc_CunQian
@CardNo varchar(30),
@MoneyInBank money
as
    update BankCard set CardMoney = CardMoney + @MoneyInBank where CardNo = @CardNo
    insert into CardExchange(CardNo,MoneyInBank,MoneyOutBank,ExchangeTime)
    values(@CardNo,@MoneyInBank,0,GETDATE())

第3步:含返回值的取钱操作

需求:模拟取钱,成功返回1,失败返回-1。

注意:RETURN只能返回整数。

create proc proc_QuQian
@CardNo varchar(30),
@MoneyOutBank money
as
    update BankCard set CardMoney = CardMoney - @MoneyOutBank where CardNo = @CardNo
    if @@ERROR <> 0
        return -1
    insert into CardExchange(CardNo,MoneyInBank,MoneyOutBank,ExchangeTime)
    values(@CardNo,0,@MoneyOutBank,GETDATE())
    return 1

调用方法必须通过变量接收返回值:

declare @returnValue int
exec @returnValue = proc_QuQian '662018092100000002',1000000
print @returnValue

第4步:含Output参数的汇总统计

需求:统计某时间段内的存款总额和取款总额,同时返回明细。

操作:声明参数为 OUTPUT,并在过程中赋值。

create proc proc_SelectExchange
    @startTime varchar(20),
    @endTime varchar(20),
    @SumIn money output,
    @SumOut money output
as
select @SumIn = (select SUM(MoneyInBank) from CardExchange 
				where ExchangeTime between @startTime+' 00:00:00' and @endTime+' 23:59:59')
select @SumOut = (select SUM(MoneyOutBank) from CardExchange 
				where ExchangeTime between @startTime+' 00:00:00' and @endTime+' 23:59:59')
select * from CardExchange 
where ExchangeTime between @startTime+' 00:00:00' and @endTime+' 23:59:59'

调用方法:

declare @SumIn money     
declare @SumOut money   
exec proc_SelectExchange '2018-1-1','2018-12-31',@SumIn output,@SumOut output
select @SumIn
select @SumOut

第5步:输入输出参数混合使用(密码升级)

需求:验证密码正确性,若长度不足8位则自动填充随机数至8位并更新数据库。

操作:将 @pwd 既作为输入又标记为 output,内部通过循环拼接随机字符。

create proc procPwdUpgrade
@cardno nvarchar(20),
@pwd nvarchar(20) output
as
	if not exists(select * from BankCard where CardNo=@cardno and CardPwd=@pwd)
		set @pwd = '' --密码错误置空
	else
	begin
		if len(@pwd) < 8
		begin
			declare @len int = 8- len(@pwd)
			declare @i int = 1
			while @i <= @len
			begin
				set @pwd = @pwd + cast(FLOOR(RAND()*10) as varchar(1))
				set @i = @i+1
			end
			update BankCard set CardPwd = @pwd where CardNo=@cardno
		end
	end

调用演示:

declare @pwd nvarchar(20) = '123456'
exec procPwdUpgrade '6225547854125656',@pwd output
select @pwd

以上就是SQL Server中函数、存储过程与触发器的详细内容,更多关于SQL Server高级查询与流程控制的资料请关注本站其它相关文章!

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。