SQL Server存储过程 #
一、存储过程概述 #
1.1 什么是存储过程 #
存储过程是一组预编译的T-SQL语句,存储在数据库中,可以通过名称调用执行。
text
存储过程优点:
├── 预编译,执行效率高
├── 减少网络流量
├── 代码复用
├── 安全性控制
├── 模块化设计
└── 减少SQL注入风险
1.2 存储过程类型 #
text
SQL Server存储过程类型
├── 用户定义存储过程
├── 系统存储过程(sp_开头)
├── 扩展存储过程(xp_开头)
├── CLR存储过程
└── 临时存储过程(#开头)
二、创建存储过程 #
2.1 基本语法 #
sql
CREATE { PROC | PROCEDURE } [schema_name.]procedure_name
[ { @parameter [ type_schema_name. ] data_type }
[ VARYING ] [ = default ] [ OUT | OUTPUT ] [READONLY]
] [ ,...n ]
[ WITH <procedure_option> [ ,...n ] ]
[ FOR REPLICATION ]
AS { [ BEGIN ] sql_statement [ ; ] [ ...n ] [ END ] }
[;]
<procedure_option> ::=
{
ENCRYPTION
| RECOMPILE
| EXECUTE AS Clause
}
2.2 创建简单存储过程 #
sql
-- 创建简单存储过程
CREATE PROCEDURE usp_get_all_users
AS
BEGIN
SELECT * FROM users;
END
GO
-- 执行存储过程
EXEC usp_get_all_users;
-- 或
EXECUTE usp_get_all_users;
-- 或省略EXEC(第一条语句)
usp_get_all_users;
2.3 带参数的存储过程 #
sql
-- 输入参数
CREATE PROCEDURE usp_get_user_by_id
@user_id INT
AS
BEGIN
SELECT * FROM users WHERE id = @user_id;
END
GO
-- 执行
EXEC usp_get_user_by_id @user_id = 1;
EXEC usp_get_user_by_id 1; -- 按位置传递
-- 默认参数值
CREATE PROCEDURE usp_get_users_by_status
@status BIT = 1
AS
BEGIN
SELECT * FROM users WHERE status = @status;
END
GO
-- 执行
EXEC usp_get_users_by_status; -- 使用默认值
EXEC usp_get_users_by_status 0; -- 传递新值
2.4 输出参数 #
sql
-- 输出参数
CREATE PROCEDURE usp_get_user_count
@status BIT,
@count INT OUTPUT
AS
BEGIN
SELECT @count = COUNT(*)
FROM users
WHERE status = @status;
END
GO
-- 执行并获取输出
DECLARE @result INT;
EXEC usp_get_user_count @status = 1, @count = @result OUTPUT;
PRINT 'Count: ' + CAST(@result AS VARCHAR);
2.5 返回值 #
sql
-- 使用RETURN返回状态
CREATE PROCEDURE usp_check_user
@user_id INT
AS
BEGIN
IF EXISTS (SELECT 1 FROM users WHERE id = @user_id)
RETURN 1; -- 存在
ELSE
RETURN 0; -- 不存在
END
GO
-- 执行并获取返回值
DECLARE @status INT;
EXEC @status = usp_check_user @user_id = 1;
PRINT 'Status: ' + CAST(@status AS VARCHAR);
三、变量和流程控制 #
3.1 变量 #
sql
CREATE PROCEDURE usp_demo_variables
AS
BEGIN
-- 声明变量
DECLARE @name NVARCHAR(50);
DECLARE @age INT = 25;
DECLARE @now DATETIME = GETDATE();
-- 赋值
SET @name = 'John';
-- 使用SELECT赋值
SELECT @name = name FROM users WHERE id = 1;
-- 使用变量
SELECT @name AS name, @age AS age, @now AS now;
END
3.2 条件语句 #
sql
CREATE PROCEDURE usp_get_user_info
@user_id INT
AS
BEGIN
DECLARE @status BIT;
SELECT @status = status FROM users WHERE id = @user_id;
-- IF...ELSE
IF @status = 1
PRINT 'Active user';
ELSE IF @status = 0
PRINT 'Inactive user';
ELSE
PRINT 'User not found';
-- 多条件
IF EXISTS (SELECT 1 FROM users WHERE id = @user_id AND salary > 5000)
PRINT 'High salary user';
END
3.3 循环语句 #
sql
CREATE PROCEDURE usp_demo_loop
@count INT
AS
BEGIN
DECLARE @i INT = 1;
-- WHILE循环
WHILE @i <= @count
BEGIN
PRINT 'Iteration: ' + CAST(@i AS VARCHAR);
SET @i = @i + 1;
-- BREAK和CONTINUE
IF @i = 5
BREAK; -- 退出循环
IF @i = 3
CONTINUE; -- 跳过本次迭代
END
END
3.4 CASE表达式 #
sql
CREATE PROCEDURE usp_get_user_level
@user_id INT
AS
BEGIN
SELECT
name,
salary,
CASE
WHEN salary >= 6000 THEN 'High'
WHEN salary >= 4000 THEN 'Medium'
ELSE 'Low'
END AS salary_level
FROM users
WHERE id = @user_id;
END
3.5 TRY…CATCH #
sql
CREATE PROCEDURE usp_safe_update
@user_id INT,
@new_salary DECIMAL(10,2)
AS
BEGIN
BEGIN TRY
BEGIN TRANSACTION;
UPDATE users SET salary = @new_salary WHERE id = @user_id;
IF @@ROWCOUNT = 0
RAISERROR('User not found', 16, 1);
COMMIT TRANSACTION;
PRINT 'Update successful';
END TRY
BEGIN CATCH
IF @@TRANCOUNT > 0
ROLLBACK TRANSACTION;
PRINT 'Error: ' + ERROR_MESSAGE();
PRINT 'Error Number: ' + CAST(ERROR_NUMBER() AS VARCHAR);
PRINT 'Error Severity: ' + CAST(ERROR_SEVERITY() AS VARCHAR);
PRINT 'Error State: ' + CAST(ERROR_STATE() AS VARCHAR);
PRINT 'Error Line: ' + CAST(ERROR_LINE() AS VARCHAR);
PRINT 'Error Procedure: ' + ISNULL(ERROR_PROCEDURE(), 'N/A');
END CATCH
END
四、存储过程选项 #
4.1 ENCRYPTION #
sql
-- 加密存储过程定义
CREATE PROCEDURE usp_secret_procedure
WITH ENCRYPTION
AS
BEGIN
SELECT * FROM users;
END
GO
-- 加密后无法查看定义
EXEC sp_helptext 'usp_secret_procedure'; -- 返回空
4.2 RECOMPILE #
sql
-- 每次执行重新编译
CREATE PROCEDURE usp_dynamic_query
@column_name NVARCHAR(50)
WITH RECOMPILE
AS
BEGIN
DECLARE @sql NVARCHAR(1000);
SET @sql = 'SELECT ' + @column_name + ' FROM users';
EXEC sp_executesql @sql;
END
4.3 EXECUTE AS #
sql
-- 指定执行上下文
CREATE PROCEDURE usp_admin_operation
WITH EXECUTE AS OWNER -- 以所有者身份执行
AS
BEGIN
SELECT * FROM sensitive_data;
END
五、表值参数 #
5.1 创建表类型 #
sql
-- 创建表类型
CREATE TYPE UserTableType AS TABLE (
name NVARCHAR(50),
email VARCHAR(100),
age INT
);
5.2 使用表值参数 #
sql
-- 使用表值参数
CREATE PROCEDURE usp_insert_users
@users UserTableType READONLY
AS
BEGIN
INSERT INTO users (name, email, age)
SELECT name, email, age FROM @users;
END
GO
-- 调用
DECLARE @new_users AS UserTableType;
INSERT INTO @new_users VALUES
('User1', 'user1@example.com', 25),
('User2', 'user2@example.com', 30);
EXEC usp_insert_users @users = @new_users;
六、动态SQL #
6.1 使用sp_executesql #
sql
CREATE PROCEDURE usp_search_users
@column_name NVARCHAR(50),
@search_value NVARCHAR(100)
AS
BEGIN
DECLARE @sql NVARCHAR(1000);
DECLARE @param_def NVARCHAR(500);
SET @sql = N'SELECT * FROM users WHERE ' + QUOTENAME(@column_name) + ' = @value';
SET @param_def = N'@value NVARCHAR(100)';
EXEC sp_executesql @sql, @param_def, @value = @search_value;
END
6.2 防止SQL注入 #
sql
-- 使用参数化查询
CREATE PROCEDURE usp_safe_search
@name NVARCHAR(50)
AS
BEGIN
DECLARE @sql NVARCHAR(1000);
-- 使用QUOTENAME防止注入
SET @sql = N'SELECT * FROM users WHERE name = @safe_name';
EXEC sp_executesql @sql, N'@safe_name NVARCHAR(50)', @safe_name = @name;
END
七、管理存储过程 #
7.1 修改存储过程 #
sql
-- 修改存储过程
ALTER PROCEDURE usp_get_all_users
AS
BEGIN
SELECT id, name, email FROM users;
END
7.2 删除存储过程 #
sql
-- 删除存储过程
DROP PROCEDURE usp_get_all_users;
-- 安全删除
IF OBJECT_ID('usp_get_all_users', 'P') IS NOT NULL
DROP PROCEDURE usp_get_all_users;
7.3 查看存储过程 #
sql
-- 查看定义
EXEC sp_helptext 'usp_get_all_users';
-- 查看依赖
EXEC sp_depends 'usp_get_all_users';
-- 查看信息
EXEC sp_help 'usp_get_all_users';
八、存储过程最佳实践 #
8.1 命名规范 #
text
存储过程命名规范:
├── 使用usp_前缀(用户存储过程)
├── 使用动词+名词:usp_get_user
├── 避免使用sp_前缀(系统存储过程)
└── 使用有意义的名称
8.2 设计原则 #
text
存储过程设计原则:
├── 单一职责
├── 使用参数验证
├── 使用错误处理
├── 添加注释
├── 使用SET NOCOUNT ON
├── 避免长事务
└── 使用事务保护
8.3 性能优化 #
sql
-- 使用SET NOCOUNT ON减少网络流量
CREATE PROCEDURE usp_optimized
AS
BEGIN
SET NOCOUNT ON; -- 不返回受影响行数
-- 使用临时表而非表变量(大数据量)
-- 使用适当的索引
-- 避免游标
END
九、总结 #
存储过程要点:
| 特性 | 说明 |
|---|---|
| 参数 | 输入、输出、默认值 |
| 返回值 | RETURN返回状态 |
| 流程控制 | IF、WHILE、CASE |
| 错误处理 | TRY…CATCH |
| 动态SQL | sp_executesql |
| 表值参数 | 批量数据传递 |
最佳实践:
- 使用SET NOCOUNT ON
- 使用参数验证
- 使用TRY…CATCH错误处理
- 使用事务保护
- 添加注释说明
下一步,让我们学习事务!
最后更新:2026-03-27