Skip to content

SQLServer T-SQL开发

T-SQL概述

T-SQL(Transact-SQL)是SQLServer的扩展SQL语言,它在标准SQL的基础上增加了变量、控制流语句、存储过程、函数、触发器等功能,用于实现复杂的业务逻辑和数据处理。T-SQL是SQLServer数据库开发的核心,掌握T-SQL开发对于SQLServer数据库开发人员至关重要。

变量

变量是T-SQL中用于存储临时数据的对象,分为局部变量和全局变量两种。

局部变量

局部变量以@开头,仅在声明它的批处理、存储过程或函数中有效。

声明和赋值

sql
-- 声明局部变量
DECLARE @EmployeeID INT;
DECLARE @EmployeeName VARCHAR(50), @Salary DECIMAL(10,2);

-- 赋值方式1:使用SET
SET @EmployeeID = 1;

-- 赋值方式2:使用SELECT
SELECT @EmployeeName = EmployeeName, @Salary = Salary 
FROM Employees 
WHERE EmployeeID = @EmployeeID;

-- 使用变量
SELECT @EmployeeName AS EmployeeName, @Salary AS Salary;

全局变量

全局变量以@@开头,由SQLServer系统维护,用于返回系统状态信息。

常用全局变量

sql
-- 当前数据库ID
SELECT @@DBID AS CurrentDBID;

-- 当前服务器名称
SELECT @@SERVERNAME AS ServerName;

-- 当前用户ID
SELECT @@UID AS UserID;

-- 错误号
SELECT @@ERROR AS ErrorNumber;

-- 影响行数
SELECT @@ROWCOUNT AS RowsAffected;

-- 事务计数
SELECT @@TRANCOUNT AS TransactionCount;

-- 当前日期时间
SELECT @@GETDATE() AS CurrentDateTime;

控制流语句

控制流语句用于控制T-SQL语句的执行顺序,包括条件语句和循环语句。

条件语句

IF...ELSE语句

IF...ELSE语句用于根据条件执行不同的T-SQL语句块。

示例

sql
-- 根据员工工资发放奖金
DECLARE @EmployeeID INT = 1;
DECLARE @Salary DECIMAL(10,2);
DECLARE @Bonus DECIMAL(10,2);

SELECT @Salary = Salary FROM Employees WHERE EmployeeID = @EmployeeID;

IF @Salary >= 8000
    SET @Bonus = @Salary * 0.1;
ELSE IF @Salary >= 6000
    SET @Bonus = @Salary * 0.08;
ELSE IF @Salary >= 4000
    SET @Bonus = @Salary * 0.05;
ELSE
    SET @Bonus = @Salary * 0.03;

SELECT @Bonus AS Bonus;

CASE语句

CASE语句用于根据条件返回不同的值,类似于其他编程语言中的switch语句。

示例

sql
-- 使用CASE语句对员工工资进行分级
SELECT EmployeeName, Salary,
       CASE 
           WHEN Salary >= 8000 THEN '高级' 
           WHEN Salary >= 6000 THEN '中级' 
           WHEN Salary >= 4000 THEN '初级' 
           ELSE '实习' 
       END AS SalaryLevel
FROM Employees;

循环语句

WHILE循环

WHILE循环用于重复执行T-SQL语句块,直到指定的条件为假。

示例

sql
-- 使用WHILE循环计算1到10的和
DECLARE @Sum INT = 0;
DECLARE @i INT = 1;

WHILE @i <= 10
BEGIN
    SET @Sum = @Sum + @i;
    SET @i = @i + 1;
END

SELECT @Sum AS TotalSum;

BREAK和CONTINUE语句

BREAK语句用于退出当前循环,CONTINUE语句用于跳过当前循环的剩余部分,继续下一次循环。

示例

sql
-- 使用BREAK和CONTINUE语句
DECLARE @i INT = 1;

WHILE @i <= 10
BEGIN
    -- 跳过偶数
    IF @i % 2 = 0
    BEGIN
        SET @i = @i + 1;
        CONTINUE;
    END
    
    -- 当i大于7时退出循环
    IF @i > 7
        BREAK;
    
    PRINT @i;
    SET @i = @i + 1;
END

存储过程

存储过程是预编译的T-SQL语句集合,存储在数据库中,可以通过名称调用。存储过程可以提高性能、减少网络开销、增强安全性和简化代码维护。

创建存储过程

基本语法

sql
CREATE PROCEDURE procedure_name
    @parameter1 data_type [= default_value] [OUTPUT],
    @parameter2 data_type [= default_value] [OUTPUT],
    ...
AS
BEGIN
    -- T-SQL语句
END;

示例

sql
-- 创建存储过程:获取员工信息
CREATE PROCEDURE GetEmployeeInfo
    @EmployeeID INT
AS
BEGIN
    SELECT EmployeeID, EmployeeName, DepartmentID, Salary, HireDate
    FROM Employees
    WHERE EmployeeID = @EmployeeID;
END;

-- 调用存储过程
EXEC GetEmployeeInfo @EmployeeID = 1;

-- 创建带输出参数的存储过程:获取部门员工数量
CREATE PROCEDURE GetDepartmentEmployeeCount
    @DepartmentID INT,
    @EmployeeCount INT OUTPUT
AS
BEGIN
    SELECT @EmployeeCount = COUNT(*) 
    FROM Employees 
    WHERE DepartmentID = @DepartmentID;
END;

-- 调用带输出参数的存储过程
DECLARE @Count INT;
EXEC GetDepartmentEmployeeCount @DepartmentID = 1, @EmployeeCount = @Count OUTPUT;
SELECT @Count AS EmployeeCount;

-- 创建带默认参数的存储过程:按部门查询员工
CREATE PROCEDURE GetEmployeesByDepartment
    @DepartmentID INT = NULL
AS
BEGIN
    SELECT EmployeeID, EmployeeName, DepartmentID, Salary, HireDate
    FROM Employees
    WHERE DepartmentID = @DepartmentID OR @DepartmentID IS NULL;
END;

-- 调用带默认参数的存储过程
EXEC GetEmployeesByDepartment; -- 返回所有员工
EXEC GetEmployeesByDepartment @DepartmentID = 1; -- 返回部门1的员工

修改和删除存储过程

sql
-- 修改存储过程
ALTER PROCEDURE GetEmployeeInfo
    @EmployeeID INT
AS
BEGIN
    SELECT e.EmployeeID, e.EmployeeName, d.DepartmentName, e.Salary, e.HireDate
    FROM Employees e
    JOIN Departments d ON e.DepartmentID = d.DepartmentID
    WHERE e.EmployeeID = @EmployeeID;
END;

-- 删除存储过程
DROP PROCEDURE IF EXISTS GetEmployeeInfo;

存储过程最佳实践

  • 为存储过程添加注释,说明其功能、参数和返回值
  • 使用命名前缀,如sp_表示系统存储过程,usp_表示用户存储过程
  • 避免在存储过程中使用SELECT *,只查询需要的列
  • 使用SET NOCOUNT ON减少网络开销
  • 合理使用参数,避免SQL注入
  • 对存储过程进行性能测试和优化

函数

函数是返回值的数据库对象,分为标量函数、表值函数和系统函数三种。

标量函数

标量函数返回单个值,可以在SELECT语句、WHERE子句或其他T-SQL语句中使用。

创建标量函数

sql
-- 创建标量函数:计算年龄
CREATE FUNCTION CalculateAge(@BirthDate DATE)
RETURNS INT
AS
BEGIN
    DECLARE @Age INT;
    SET @Age = DATEDIFF(YEAR, @BirthDate, GETDATE());
    -- 修正生日未过的情况
    IF MONTH(@BirthDate) > MONTH(GETDATE()) OR 
       (MONTH(@BirthDate) = MONTH(GETDATE()) AND DAY(@BirthDate) > DAY(GETDATE()))
        SET @Age = @Age - 1;
    RETURN @Age;
END;

-- 调用标量函数
SELECT dbo.CalculateAge('1990-01-01') AS Age;

-- 在SELECT语句中使用标量函数
SELECT EmployeeName, HireDate, dbo.CalculateAge(HireDate) AS YearsOfService
FROM Employees;

表值函数

表值函数返回一个表,可以在FROM子句中使用,分为内联表值函数和多语句表值函数两种。

内联表值函数

内联表值函数只包含一个SELECT语句,没有BEGIN和END块。

示例

sql
-- 创建内联表值函数:按部门查询员工
CREATE FUNCTION GetEmployeesByDepartment_ITVF(@DepartmentID INT)
RETURNS TABLE
AS
RETURN
(   
    SELECT EmployeeID, EmployeeName, DepartmentID, Salary, HireDate
    FROM Employees
    WHERE DepartmentID = @DepartmentID
);

-- 调用内联表值函数
SELECT * FROM dbo.GetEmployeesByDepartment_ITVF(1);

多语句表值函数

多语句表值函数包含多个T-SQL语句,需要显式定义返回表的结构。

示例

sql
-- 创建多语句表值函数:获取部门工资统计
CREATE FUNCTION GetDepartmentSalaryStats_MSTVF()
RETURNS @SalaryStats TABLE (
    DepartmentID INT,
    DepartmentName VARCHAR(50),
    EmployeeCount INT,
    AvgSalary DECIMAL(10,2),
    MaxSalary DECIMAL(10,2),
    MinSalary DECIMAL(10,2)
)
AS
BEGIN
    INSERT INTO @SalaryStats
    SELECT e.DepartmentID, d.DepartmentName, COUNT(*), AVG(e.Salary), MAX(e.Salary), MIN(e.Salary)
    FROM Employees e
    JOIN Departments d ON e.DepartmentID = d.DepartmentID
    GROUP BY e.DepartmentID, d.DepartmentName;
    
    RETURN;
END;

-- 调用多语句表值函数
SELECT * FROM dbo.GetDepartmentSalaryStats_MSTVF();

系统函数

SQLServer提供了丰富的系统函数,用于执行各种操作,如字符串处理、日期时间处理、数学计算等。

常用系统函数

sql
-- 字符串函数
SELECT LEN('SQLServer') AS StringLength;
SELECT UPPER('sqlserver') AS UpperCase;
SELECT LOWER('SQLSERVER') AS LowerCase;
SELECT SUBSTRING('SQLServer', 1, 3) AS SubString;
SELECT REPLACE('SQLServer', 'Server', 'Database') AS ReplaceString;

-- 日期时间函数
SELECT GETDATE() AS CurrentDateTime;
SELECT DATEPART(YEAR, GETDATE()) AS CurrentYear;
SELECT DATENAME(MONTH, GETDATE()) AS CurrentMonthName;
SELECT DATEADD(DAY, 7, GETDATE()) AS NextWeek;
SELECT DATEDIFF(DAY, '2023-01-01', GETDATE()) AS DaysSinceNewYear;

-- 数学函数
SELECT ABS(-10) AS AbsoluteValue;
SELECT ROUND(123.456, 2) AS RoundedValue;
SELECT CEILING(123.45) AS CeilingValue;
SELECT FLOOR(123.45) AS FloorValue;
SELECT RAND() AS RandomNumber;

-- 聚合函数
SELECT COUNT(*) AS TotalEmployees FROM Employees;
SELECT AVG(Salary) AS AvgSalary FROM Employees;
SELECT MAX(Salary) AS MaxSalary FROM Employees;
SELECT MIN(Salary) AS MinSalary FROM Employees;
SELECT SUM(Salary) AS TotalSalary FROM Employees;

函数最佳实践

  • 为函数添加注释,说明其功能、参数和返回值
  • 优先使用内联表值函数,因为它比多语句表值函数性能更好
  • 避免在函数中使用副作用操作,如修改表数据
  • 合理使用函数,避免在WHERE子句中频繁使用自定义函数,否则会影响查询性能
  • 对函数进行性能测试和优化

触发器

触发器是在特定事件(INSERT、UPDATE、DELETE)发生时自动执行的T-SQL语句集合,用于实现数据完整性、审计和业务规则。

触发器类型

DML触发器

DML触发器在数据表上的INSERT、UPDATE、DELETE操作时触发。

示例

sql
-- 创建审计表
CREATE TABLE EmployeeAudit (
    AuditID INT IDENTITY(1,1) PRIMARY KEY,
    EmployeeID INT,
    Action VARCHAR(10),
    ActionDate DATETIME DEFAULT GETDATE(),
    OldValues XML,
    NewValues XML
);

-- 创建INSERT触发器:记录员工插入操作
CREATE TRIGGER tr_Employees_AfterInsert
ON Employees
AFTER INSERT
AS
BEGIN
    SET NOCOUNT ON;
    
    INSERT INTO EmployeeAudit (EmployeeID, Action, NewValues)
    SELECT EmployeeID, 'INSERT', (SELECT * FROM inserted FOR XML AUTO, ELEMENTS)
    FROM inserted;
END;

-- 创建UPDATE触发器:记录员工更新操作
CREATE TRIGGER tr_Employees_AfterUpdate
ON Employees
AFTER UPDATE
AS
BEGIN
    SET NOCOUNT ON;
    
    INSERT INTO EmployeeAudit (EmployeeID, Action, OldValues, NewValues)
    SELECT i.EmployeeID, 'UPDATE', 
           (SELECT * FROM deleted FOR XML AUTO, ELEMENTS),
           (SELECT * FROM inserted FOR XML AUTO, ELEMENTS)
    FROM inserted i
    JOIN deleted d ON i.EmployeeID = d.EmployeeID;
END;

-- 创建DELETE触发器:记录员工删除操作
CREATE TRIGGER tr_Employees_AfterDelete
ON Employees
AFTER DELETE
AS
BEGIN
    SET NOCOUNT ON;
    
    INSERT INTO EmployeeAudit (EmployeeID, Action, OldValues)
    SELECT EmployeeID, 'DELETE', (SELECT * FROM deleted FOR XML AUTO, ELEMENTS)
    FROM deleted;
END;

-- 测试触发器
INSERT INTO Employees (EmployeeName, DepartmentID, Salary, HireDate) 
VALUES ('测试员工', 1, 5000, '2023-01-01');

UPDATE Employees SET Salary = 5500 WHERE EmployeeName = '测试员工';

DELETE FROM Employees WHERE EmployeeName = '测试员工';

-- 查看审计记录
SELECT * FROM EmployeeAudit;

DDL触发器

DDL触发器在数据库或服务器上的DDL操作(如CREATE、ALTER、DROP)时触发,用于审计和控制数据库架构变更。

示例

sql
-- 创建DDL触发器:审计表结构变更
CREATE TRIGGER tr_Database_AfterDDL
ON DATABASE
AFTER CREATE_TABLE, ALTER_TABLE, DROP_TABLE
AS
BEGIN
    SET NOCOUNT ON;
    
    DECLARE @EventData XML = EVENTDATA();
    
    INSERT INTO DDL_Audit (
        EventType,
        ObjectName,
        ObjectType,
        EventDate,
        LoginName,
        EventData
    )
    VALUES (
        @EventData.value('(/EVENT_INSTANCE/EventType)[1]', 'VARCHAR(50)'),
        @EventData.value('(/EVENT_INSTANCE/ObjectName)[1]', 'VARCHAR(100)'),
        @EventData.value('(/EVENT_INSTANCE/ObjectType)[1]', 'VARCHAR(50)'),
        GETDATE(),
        @EventData.value('(/EVENT_INSTANCE/LoginName)[1]', 'VARCHAR(50)'),
        @EventData
    );
END;

INSTEAD OF触发器

INSTEAD OF触发器在DML操作执行前触发,用于替换原始操作,常用于视图上的DML操作。

示例

sql
-- 创建视图
CREATE VIEW vw_EmployeeDepartment
AS
SELECT e.EmployeeID, e.EmployeeName, d.DepartmentName, e.Salary, e.HireDate
FROM Employees e
JOIN Departments d ON e.DepartmentID = d.DepartmentID;

-- 创建INSTEAD OF INSERT触发器:处理视图插入
CREATE TRIGGER tr_vw_EmployeeDepartment_InsteadOfInsert
ON vw_EmployeeDepartment
INSTEAD OF INSERT
AS
BEGIN
    SET NOCOUNT ON;
    
    DECLARE @DepartmentID INT;
    
    -- 获取部门ID
    SELECT @DepartmentID = DepartmentID
    FROM Departments
    WHERE DepartmentName = (SELECT DepartmentName FROM inserted);
    
    -- 插入员工
    INSERT INTO Employees (EmployeeName, DepartmentID, Salary, HireDate)
    SELECT EmployeeName, @DepartmentID, Salary, HireDate
    FROM inserted;
END;

触发器最佳实践

  • 为触发器添加注释,说明其功能和触发条件
  • 保持触发器简洁,避免复杂的业务逻辑
  • 使用SET NOCOUNT ON减少网络开销
  • 避免在触发器中修改触发它的表,否则可能导致递归触发
  • 考虑使用事务确保数据一致性
  • 定期审查和测试触发器,确保其正常工作

游标

游标是用于逐行处理结果集的数据库对象,虽然游标性能较低,但在某些复杂的数据处理场景中仍然有用。

示例

sql
-- 使用游标更新员工工资
DECLARE @EmployeeID INT;
DECLARE @Salary DECIMAL(10,2);

-- 声明游标
DECLARE EmployeeCursor CURSOR FOR
SELECT EmployeeID, Salary FROM Employees WHERE DepartmentID = 1;

-- 打开游标
OPEN EmployeeCursor;

-- 获取第一行数据
FETCH NEXT FROM EmployeeCursor INTO @EmployeeID, @Salary;

-- 循环处理
WHILE @@FETCH_STATUS = 0
BEGIN
    -- 更新工资(涨10%)
    UPDATE Employees SET Salary = @Salary * 1.1 WHERE EmployeeID = @EmployeeID;
    
    -- 获取下一行数据
    FETCH NEXT FROM EmployeeCursor INTO @EmployeeID, @Salary;
END;

-- 关闭游标
CLOSE EmployeeCursor;

-- 释放游标
DEALLOCATE EmployeeCursor;

游标最佳实践

  • 尽量避免使用游标,优先使用基于集合的操作
  • 如果必须使用游标,使用FAST_FORWARD和READ_ONLY选项提高性能
  • 及时关闭和释放游标,避免资源泄漏
  • 限制游标的结果集大小
  • 考虑使用WHILE循环和临时表替代游标

错误处理

错误处理用于捕获和处理T-SQL执行过程中的错误,确保系统的稳定性和可靠性。

TRY...CATCH语句

TRY...CATCH语句用于捕获TRY块中的错误,并在CATCH块中处理。

示例

sql
BEGIN TRY
    -- 尝试执行可能出错的操作
    INSERT INTO Employees (EmployeeName, DepartmentID, Salary, HireDate)
    VALUES ('测试员工', 999, 5000, '2023-01-01'); -- 部门ID 999不存在
END TRY
BEGIN CATCH
    -- 处理错误
    SELECT 
        ERROR_NUMBER() AS ErrorNumber,
        ERROR_SEVERITY() AS ErrorSeverity,
        ERROR_STATE() AS ErrorState,
        ERROR_PROCEDURE() AS ErrorProcedure,
        ERROR_LINE() AS ErrorLine,
        ERROR_MESSAGE() AS ErrorMessage;
    
    -- 回滚事务(如果有)
    IF @@TRANCOUNT > 0
        ROLLBACK TRANSACTION;
END CATCH;

事务处理

事务用于确保一组T-SQL语句要么全部成功执行,要么全部失败回滚,用于维护数据一致性。

示例

sql
BEGIN TRY
    BEGIN TRANSACTION;
    
    -- 插入订单
    INSERT INTO Orders (CustomerID, OrderDate, TotalAmount)
    VALUES (1, GETDATE(), 500.00);
    
    DECLARE @OrderID INT = SCOPE_IDENTITY();
    
    -- 插入订单详情
    INSERT INTO OrderDetails (OrderID, ProductID, Quantity, UnitPrice)
    VALUES (@OrderID, 1, 2, 100.00),
           (@OrderID, 2, 1, 200.00);
    
    -- 更新产品库存
    UPDATE Products SET Stock = Stock - 2 WHERE ProductID = 1;
    UPDATE Products SET Stock = Stock - 1 WHERE ProductID = 2;
    
    -- 提交事务
    COMMIT TRANSACTION;
    PRINT '订单创建成功!';
END TRY
BEGIN CATCH
    -- 回滚事务
    ROLLBACK TRANSACTION;
    PRINT '订单创建失败:' + ERROR_MESSAGE();
END CATCH;

错误处理最佳实践

  • 在所有存储过程和触发器中使用TRY...CATCH语句
  • 记录错误信息,便于调试和审计
  • 适当处理事务,确保数据一致性
  • 向应用程序返回有意义的错误信息
  • 避免在CATCH块中嵌套TRY...CATCH语句

版本差异

SQLServer 2008及以上版本

  • 支持基本的T-SQL功能,如存储过程、函数、触发器、游标
  • 支持TRY...CATCH错误处理
  • 支持XML数据类型和XML函数

SQLServer 2012及以上版本

  • 引入THROW语句,用于重新抛出错误
  • 支持SEQUENCE对象,用于生成序列值
  • 支持OFFSET FETCH子句用于分页
  • 支持FORMAT函数用于格式化数据

SQLServer 2016及以上版本

  • 支持JSON数据类型和JSON函数
  • 支持DROP IF EXISTS语句,用于条件删除数据库对象
  • 支持STRING_SPLIT函数,用于分割字符串
  • 支持ROW LEVEL SECURITY,用于行级权限控制

SQLServer 2017及以上版本

  • 支持STRING_AGG函数,用于字符串聚合
  • 支持CONCAT_WS函数,用于带分隔符的字符串连接
  • 支持TRANSLATE函数,用于字符替换
  • 支持BATCH MODE ON ROWSTORE,提高行存储表的查询性能

SQLServer 2019及以上版本

  • 支持表变量延迟编译,提高表变量的性能
  • 支持近似查询处理,用于大数据量的近似聚合
  • 支持内存优化表的更广泛使用

实际生产场景

场景1:员工考勤系统

业务需求

  • 记录员工考勤信息
  • 自动计算员工工资
  • 生成考勤报表

实现方案

  • 使用存储过程处理考勤数据
  • 使用函数计算工资和考勤统计
  • 使用触发器维护数据完整性和审计
  • 使用事务确保数据一致性

示例代码

sql
-- 创建考勤表
CREATE TABLE Attendance (
    AttendanceID INT IDENTITY(1,1) PRIMARY KEY,
    EmployeeID INT FOREIGN KEY REFERENCES Employees(EmployeeID),
    AttendanceDate DATE,
    CheckInTime TIME,
    CheckOutTime TIME,
    Status VARCHAR(10),
    HoursWorked DECIMAL(5,2)
);

-- 创建计算工时的函数
CREATE FUNCTION CalculateHoursWorked(@CheckInTime TIME, @CheckOutTime TIME)
RETURNS DECIMAL(5,2)
AS
BEGIN
    DECLARE @HoursWorked DECIMAL(5,2);
    SET @HoursWorked = DATEDIFF(MINUTE, @CheckInTime, @CheckOutTime) / 60.0;
    RETURN @HoursWorked;
END;

-- 创建更新考勤状态的存储过程
CREATE PROCEDURE UpdateAttendanceStatus
    @AttendanceID INT
AS
BEGIN
    SET NOCOUNT ON;
    
    DECLARE @CheckInTime TIME, @CheckOutTime TIME, @HoursWorked DECIMAL(5,2), @Status VARCHAR(10);
    
    SELECT @CheckInTime = CheckInTime, @CheckOutTime = CheckOutTime
    FROM Attendance
    WHERE AttendanceID = @AttendanceID;
    
    -- 计算工时
    SET @HoursWorked = dbo.CalculateHoursWorked(@CheckInTime, @CheckOutTime);
    
    -- 确定考勤状态
    IF @HoursWorked >= 8
        SET @Status = '正常';
    ELSE IF @HoursWorked >= 4
        SET @Status = '半天';
    ELSE
        SET @Status = '缺勤';
    
    -- 更新考勤记录
    UPDATE Attendance
    SET HoursWorked = @HoursWorked, Status = @Status
    WHERE AttendanceID = @AttendanceID;
END;

-- 创建生成工资的存储过程
CREATE PROCEDURE GenerateSalary
    @Month INT,
    @Year INT
AS
BEGIN
    SET NOCOUNT ON;
    
    -- 临时表存储工资计算结果
    CREATE TABLE #Salary (
        EmployeeID INT,
        EmployeeName VARCHAR(50),
        BasicSalary DECIMAL(10,2),
        AttendanceDays INT,
        OvertimeHours DECIMAL(5,2),
        TotalSalary DECIMAL(10,2)
    );
    
    -- 插入基本工资
    INSERT INTO #Salary (EmployeeID, EmployeeName, BasicSalary)
    SELECT EmployeeID, EmployeeName, Salary
    FROM Employees;
    
    -- 更新考勤天数和加班小时
    UPDATE #Salary
    SET AttendanceDays = (SELECT COUNT(*) FROM Attendance WHERE EmployeeID = #Salary.EmployeeID AND MONTH(AttendanceDate) = @Month AND YEAR(AttendanceDate) = @Year AND Status = '正常'),
        OvertimeHours = (SELECT SUM(CASE WHEN HoursWorked > 8 THEN HoursWorked - 8 ELSE 0 END) FROM Attendance WHERE EmployeeID = #Salary.EmployeeID AND MONTH(AttendanceDate) = @Month AND YEAR(AttendanceDate) = @Year);
    
    -- 计算总工资
    UPDATE #Salary
    SET TotalSalary = BasicSalary + (OvertimeHours * (BasicSalary / 160) * 1.5);
    
    -- 返回结果
    SELECT * FROM #Salary;
    
    -- 删除临时表
    DROP TABLE #Salary;
END;

常见问题(FAQ)

Q1:存储过程和函数有什么区别?

A:存储过程可以执行一系列T-SQL语句,不一定要返回值;函数必须返回值,可以在SELECT语句中使用。存储过程可以修改数据,函数不能修改数据。

Q2:什么时候应该使用存储过程,什么时候应该使用函数?

A:对于复杂的业务逻辑和数据修改操作,使用存储过程;对于需要返回单个值或表值的计算操作,使用函数。

Q3:如何提高存储过程的性能?

A:提高存储过程性能的方法包括:

  • 优化T-SQL语句,使用合适的索引
  • 避免使用SELECT *,只查询需要的列
  • 使用SET NOCOUNT ON减少网络开销
  • 避免在存储过程中使用游标
  • 合理使用参数,避免SQL注入
  • 定期更新统计信息

Q4:触发器和约束有什么区别?

A:约束用于维护数据完整性,如主键约束、外键约束、唯一约束、检查约束;触发器用于实现更复杂的业务规则、审计和数据同步。约束的性能通常比触发器好,但功能不如触发器灵活。

Q5:什么时候应该使用游标?

A:尽量避免使用游标,优先使用基于集合的操作。只有在必须逐行处理数据的复杂场景中,才考虑使用游标。

Q6:如何调试存储过程?

A:调试存储过程的方法包括:

  • 使用PRINT语句输出调试信息
  • 使用SELECT语句查询中间结果
  • 使用TRY...CATCH语句捕获错误
  • 使用SQL Server Management Studio的调试功能
  • 使用日志表记录执行过程

Q7:如何处理存储过程中的事务?

A:在存储过程中使用TRY...CATCH语句处理事务,确保在发生错误时回滚事务,在成功时提交事务。

Q8:如何优化触发器的性能?

A:优化触发器性能的方法包括:

  • 保持触发器简洁,避免复杂的业务逻辑
  • 使用SET NOCOUNT ON减少网络开销
  • 避免在触发器中修改触发它的表
  • 考虑使用异步处理,如消息队列
  • 定期审查和测试触发器

总结

T-SQL是SQLServer数据库开发的核心,掌握T-SQL开发对于SQLServer数据库开发人员至关重要。本文介绍了T-SQL中的变量、控制流语句、存储过程、函数、触发器、游标、错误处理等核心功能,并提供了实际示例和最佳实践。

在实际应用中,需要根据业务需求选择合适的T-SQL功能,并遵循最佳实践,以提高代码的性能、可读性和可维护性。同时,需要注意不同SQLServer版本之间的差异,确保代码的兼容性和可移植性。

随着SQLServer版本的不断更新,新的T-SQL功能不断涌现,数据库开发人员需要不断学习和掌握这些新功能,以适应业务发展的需求。