T-SQL编程 #
一、T-SQL概述 #
1.1 什么是T-SQL #
T-SQL(Transact-SQL)是 Microsoft SQL Server 的 SQL 方言,在标准 SQL 基础上增加了编程功能。
text
T-SQL扩展特性:
├── 变量声明和使用
├── 流程控制语句
├── 错误处理机制
├── 内置函数
├── 存储过程
├── 触发器
└── 动态SQL
二、变量 #
2.1 局部变量 #
sql
-- 声明变量
DECLARE @name NVARCHAR(50);
DECLARE @age INT;
DECLARE @salary DECIMAL(10,2) = 5000.00; -- 声明并赋值
-- 同时声明多个变量
DECLARE
@id INT = 1,
@name NVARCHAR(50) = 'John',
@active BIT = 1;
-- 使用SET赋值
SET @name = 'Jane';
-- 使用SELECT赋值
SELECT @name = name FROM users WHERE id = 1;
-- 使用变量
SELECT * FROM users WHERE name = @name;
-- 打印变量
PRINT @name;
PRINT 'Name: ' + @name;
PRINT 'Age: ' + CAST(@age AS VARCHAR);
2.2 表变量 #
sql
-- 声明表变量
DECLARE @temp_users TABLE (
id INT,
name NVARCHAR(50),
email VARCHAR(100)
);
-- 插入数据
INSERT INTO @temp_users
SELECT id, name, email FROM users WHERE status = 1;
-- 使用表变量
SELECT * FROM @temp_users;
-- 表变量限制:
-- 1. 不能有索引
-- 2. 不能作为存储过程参数(除非使用表类型)
-- 3. 作用域限制在批处理内
2.3 全局变量 #
sql
-- 全局变量(系统变量)
-- 当前版本
SELECT @@VERSION;
-- 受影响的行数
INSERT INTO users (name) VALUES ('Test');
SELECT @@ROWCOUNT AS '受影响行数';
-- 最后插入的标识值
INSERT INTO users (name) VALUES ('Test');
SELECT @@IDENTITY AS '最后ID';
SELECT SCOPE_IDENTITY() AS '当前作用域ID';
-- 事务计数
SELECT @@TRANCOUNT;
-- 错误号
SELECT @@ERROR;
-- 服务器名称
SELECT @@SERVERNAME;
-- 服务名称
SELECT @@SERVICENAME;
-- 当前语言
SELECT @@LANGUAGE;
-- 日期格式
SELECT @@DATEFIRST; -- 一周的第一天
三、流程控制 #
3.1 IF…ELSE #
sql
-- 基本IF语句
DECLARE @age INT = 25;
IF @age >= 18
PRINT 'Adult';
ELSE
PRINT 'Minor';
-- 多条件IF
IF @age < 18
PRINT 'Minor';
ELSE IF @age < 65
PRINT 'Adult';
ELSE
PRINT 'Senior';
-- 使用BEGIN...END块
IF EXISTS (SELECT 1 FROM users WHERE id = 1)
BEGIN
PRINT 'User exists';
UPDATE users SET status = 1 WHERE id = 1;
END
ELSE
BEGIN
PRINT 'User not found';
INSERT INTO users (name) VALUES ('New User');
END
3.2 WHILE #
sql
-- WHILE循环
DECLARE @i INT = 1;
WHILE @i <= 10
BEGIN
PRINT 'Iteration: ' + CAST(@i AS VARCHAR);
SET @i = @i + 1;
END
-- 使用BREAK和CONTINUE
DECLARE @i INT = 0;
WHILE @i < 10
BEGIN
SET @i = @i + 1;
IF @i = 5
CONTINUE; -- 跳过本次迭代
IF @i = 8
BREAK; -- 退出循环
PRINT @i;
END
3.3 CASE表达式 #
sql
-- 简单CASE
SELECT
name,
CASE status
WHEN 1 THEN 'Active'
WHEN 0 THEN 'Inactive'
ELSE 'Unknown'
END AS status_text
FROM users;
-- 搜索CASE
SELECT
name,
salary,
CASE
WHEN salary >= 6000 THEN 'High'
WHEN salary >= 4000 THEN 'Medium'
ELSE 'Low'
END AS salary_level
FROM users;
-- CASE在ORDER BY中
SELECT * FROM users
ORDER BY
CASE status
WHEN 1 THEN 1
WHEN 0 THEN 2
ELSE 3
END;
-- CASE在UPDATE中
UPDATE users
SET status = CASE
WHEN salary >= 5000 THEN 1
ELSE 0
END;
3.4 WAITFOR #
sql
-- 等待指定时间
WAITFOR DELAY '00:00:05'; -- 等待5秒
PRINT 'Waited 5 seconds';
-- 等待到指定时间
WAITFOR TIME '14:30:00'; -- 等待到14:30
PRINT 'It is now 14:30';
-- 带超时的查询
BEGIN
WAITFOR (SELECT * FROM users FOR XML), TIMEOUT 5000;
END
3.5 GOTO #
sql
-- GOTO语句(不推荐使用)
DECLARE @i INT = 1;
StartLoop:
PRINT @i;
SET @i = @i + 1;
IF @i <= 5
GOTO StartLoop;
PRINT 'Loop finished';
四、错误处理 #
4.1 TRY…CATCH #
sql
-- 基本错误处理
BEGIN TRY
SELECT 1/0; -- 除零错误
END TRY
BEGIN CATCH
PRINT 'Error occurred';
PRINT 'Error Number: ' + CAST(ERROR_NUMBER() AS VARCHAR);
PRINT 'Error Message: ' + ERROR_MESSAGE();
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
4.2 错误函数 #
sql
-- 错误处理函数
BEGIN TRY
-- 可能出错的代码
INSERT INTO users (id, name) VALUES (1, 'Test');
END TRY
BEGIN CATCH
DECLARE @ErrorMessage NVARCHAR(4000);
DECLARE @ErrorSeverity INT;
DECLARE @ErrorState INT;
SELECT
@ErrorMessage = ERROR_MESSAGE(),
@ErrorSeverity = ERROR_SEVERITY(),
@ErrorState = ERROR_STATE();
-- 重新抛出错误
RAISERROR(@ErrorMessage, @ErrorSeverity, @ErrorState);
END CATCH
4.3 RAISERROR #
sql
-- 使用RAISERROR
RAISERROR('This is an error message', 16, 1);
-- 带参数的RAISERROR
RAISERROR('User %s not found with ID %d', 16, 1, 'John', 1);
-- 使用sp_addmessage创建自定义错误消息
EXEC sp_addmessage
@msgnum = 50001,
@severity = 16,
@msgtext = 'Custom error: User %s cannot be deleted';
-- 使用自定义错误消息
RAISERROR(50001, 16, 1, 'John');
4.4 THROW #
sql
-- 使用THROW(SQL Server 2012+)
THROW 50001, 'Custom error message', 1;
-- 重新抛出错误
BEGIN TRY
SELECT 1/0;
END TRY
BEGIN CATCH
THROW; -- 重新抛出原始错误
END CATCH
-- THROW vs RAISERROR
-- THROW:总是设置@@ERROR,不需要格式化
-- RAISERROR:可以格式化消息,需要指定严重级别
4.5 事务中的错误处理 #
sql
-- 事务错误处理
BEGIN TRY
BEGIN TRANSACTION;
INSERT INTO users (name) VALUES ('User1');
INSERT INTO users (name) VALUES ('User2');
-- 可能出错的操作
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
IF @@TRANCOUNT > 0
ROLLBACK TRANSACTION;
PRINT 'Transaction rolled back: ' + ERROR_MESSAGE();
END CATCH
五、动态SQL #
5.1 EXEC执行 #
sql
-- 使用EXEC执行动态SQL
DECLARE @sql NVARCHAR(1000);
SET @sql = 'SELECT * FROM users';
EXEC(@sql);
-- 拼接字符串
DECLARE @table_name NVARCHAR(50) = 'users';
SET @sql = 'SELECT * FROM ' + @table_name;
EXEC(@sql);
5.2 sp_executesql #
sql
-- 使用sp_executesql(推荐)
DECLARE @sql NVARCHAR(1000);
DECLARE @param_def NVARCHAR(500);
DECLARE @user_id INT = 1;
SET @sql = N'SELECT * FROM users WHERE id = @id';
SET @param_def = N'@id INT';
EXEC sp_executesql @sql, @param_def, @id = @user_id;
-- 返回值
DECLARE @sql NVARCHAR(1000);
DECLARE @param_def NVARCHAR(500);
DECLARE @count INT;
SET @sql = N'SELECT @cnt = COUNT(*) FROM users WHERE status = @status';
SET @param_def = N'@status BIT, @cnt INT OUTPUT';
EXEC sp_executesql @sql, @param_def, @status = 1, @cnt = @count OUTPUT;
PRINT 'Count: ' + CAST(@count AS VARCHAR);
5.3 防止SQL注入 #
sql
-- 不安全的方式(SQL注入风险)
DECLARE @name NVARCHAR(50) = "'; DROP TABLE users; --";
DECLARE @sql NVARCHAR(1000);
SET @sql = 'SELECT * FROM users WHERE name = ''' + @name + '''';
EXEC(@sql); -- 危险!
-- 安全的方式(参数化)
DECLARE @name NVARCHAR(50) = "'; DROP TABLE users; --";
DECLARE @sql NVARCHAR(1000);
SET @sql = N'SELECT * FROM users WHERE name = @safe_name';
EXEC sp_executesql @sql, N'@safe_name NVARCHAR(50)', @safe_name = @name;
-- 使用QUOTENAME
DECLARE @table_name NVARCHAR(50) = 'users';
DECLARE @sql NVARCHAR(1000);
SET @sql = N'SELECT * FROM ' + QUOTENAME(@table_name);
EXEC(@sql);
六、批处理 #
6.1 GO命令 #
sql
-- GO分隔批处理
SELECT * FROM users;
GO
SELECT * FROM orders;
GO
-- GO可以指定执行次数
SELECT GETDATE();
GO 5 -- 执行5次
6.2 批处理限制 #
sql
-- CREATE PROCEDURE必须是批处理第一条语句
-- 错误
SELECT * FROM users;
CREATE PROCEDURE usp_test AS SELECT 1;
GO
-- 正确
SELECT * FROM users;
GO
CREATE PROCEDURE usp_test AS SELECT 1;
GO
七、注释 #
7.1 单行注释 #
sql
-- 这是单行注释
SELECT * FROM users; -- 行尾注释
7.2 多行注释 #
sql
/*
* 这是多行注释
* 可以跨越多行
*/
SELECT * FROM users;
八、最佳实践 #
8.1 编码规范 #
text
T-SQL编码规范:
├── 使用有意义的变量名
├── 添加适当的注释
├── 使用一致的缩进
├── 使用BEGIN...END块
├── 使用错误处理
├── 避免使用GOTO
├── 使用参数化查询
└── 使用SET NOCOUNT ON
8.2 性能建议 #
sql
-- 使用SET NOCOUNT ON
CREATE PROCEDURE usp_example
AS
BEGIN
SET NOCOUNT ON; -- 减少网络流量
-- 操作
SELECT * FROM users;
END
-- 避免使用游标
-- 使用集合操作替代
-- 差
DECLARE cur CURSOR FOR SELECT id FROM users;
-- 好
UPDATE users SET status = 1 WHERE status = 0;
九、总结 #
T-SQL编程要点:
| 特性 | 说明 |
|---|---|
| 变量 | DECLARE、SET、SELECT |
| 流程控制 | IF、WHILE、CASE |
| 错误处理 | TRY…CATCH |
| 动态SQL | sp_executesql |
| 批处理 | GO |
最佳实践:
- 使用参数化查询
- 添加错误处理
- 使用SET NOCOUNT ON
- 避免SQL注入
- 添加适当注释
下一步,让我们学习游标!
最后更新:2026-03-27