1. SQL Server实战语法速查手册概述
作为一名数据库工程师,我经常需要在不同项目间切换时快速查阅SQL Server的各种语法细节。市面上虽然有不少官方文档和教程,但在实际开发中往往需要更直接的语法参考和实战示例。这就是我整理这份《SQL Server实战语法速查手册》的初衷 - 一个真正从开发者角度出发,包含高频使用场景和常见坑点的实用指南。
这份手册特别适合以下场景:
- 开发过程中突然忘记某个语法细节需要快速确认
- 接手遗留项目时需要理解复杂的T-SQL代码
- 准备技术面试需要系统复习SQL Server知识点
- 从其他数据库(如MySQL)迁移到SQL Server时的语法转换
手册内容基于SQL Server 2019/2022版本,但大部分语法也兼容2008 R2及以上版本。我会按照DDL(数据定义语言)、DML(数据操作语言)、查询优化等模块组织内容,每个语法点都配有实际案例和性能注意事项。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. DDL(数据定义语言)实战语法
2.1 数据库与表操作
创建数据库时,我强烈建议明确指定文件组和日志配置,而不是使用默认设置:
sql复制CREATE DATABASE SalesDB
ON PRIMARY
(
NAME = SalesDB_Data,
FILENAME = 'D:\Data\SalesDB.mdf',
SIZE = 100MB,
MAXSIZE = UNLIMITED,
FILEGROWTH = 50MB
)
LOG ON
(
NAME = SalesDB_Log,
FILENAME = 'E:\Logs\SalesDB.ldf',
SIZE = 50MB,
MAXSIZE = 2GB,
FILEGROWTH = 25MB
);
注意:生产环境一定要将数据文件和日志文件放在不同的物理磁盘上,这对性能至关重要。
修改表结构是日常高频操作,以下是几个实用技巧:
sql复制-- 添加列时指定默认值并立即填充现有行
ALTER TABLE Orders
ADD OrderStatus VARCHAR(20) NOT NULL
CONSTRAINT DF_Orders_OrderStatus DEFAULT 'Pending'
WITH VALUES;
-- 修改列数据类型时的安全做法(避免数据截断)
BEGIN TRANSACTION;
-- 先创建临时列
ALTER TABLE Products ADD PriceNew DECIMAL(10,2) NULL;
-- 迁移数据
UPDATE Products SET PriceNew = TRY_CAST(Price AS DECIMAL(10,2));
-- 验证数据
IF EXISTS(SELECT 1 FROM Products WHERE PriceNew IS NULL AND Price IS NOT NULL)
BEGIN
ROLLBACK;
RAISERROR('数据转换失败,存在无法转换的值', 16, 1);
RETURN;
END
-- 删除原列并重命名新列
ALTER TABLE Products DROP COLUMN Price;
EXEC sp_rename 'Products.PriceNew', 'Price', 'COLUMN';
COMMIT;
2.2 索引与约束管理
创建索引时,我通常会考虑这些因素:
sql复制-- 包含性列索引(Covering Index)示例
CREATE NONCLUSTERED INDEX IX_Orders_CustomerID
ON Orders(CustomerID)
INCLUDE (OrderDate, TotalAmount)
WITH (ONLINE = ON); -- 在线创建减少锁等待
-- 筛选索引(Filtered Index)适合稀疏数据
CREATE INDEX IX_Products_Discontinued
ON Products(ProductID)
WHERE Discontinued = 1;
实操心得:在大型表上创建索引时,始终使用ONLINE=ON选项可以避免阻塞用户查询,但会增加索引创建时间约20-30%。
约束管理中的实用技巧:
sql复制-- 添加外键时指定级联操作
ALTER TABLE OrderDetails
ADD CONSTRAINT FK_OrderDetails_Products
FOREIGN KEY (ProductID) REFERENCES Products(ProductID)
ON DELETE CASCADE -- 产品删除时自动删除相关订单项
ON UPDATE NO ACTION;
-- 检查约束中使用函数
ALTER TABLE Employees
ADD CONSTRAINT CK_Employees_Email
CHECK (Email LIKE '%@%.%' AND Email NOT LIKE '% %');
3. DML(数据操作语言)高级技巧
3.1 高效的INSERT策略
批量插入数据时,这些方法可以显著提高性能:
sql复制-- 使用表值构造函数(SQL Server 2008+)
INSERT INTO Products(ProductName, CategoryID, Price)
VALUES
('Product A', 1, 19.99),
('Product B', 2, 29.99),
('Product C', 1, 14.99);
-- 使用SELECT INTO快速创建并填充临时表
SELECT CustomerID, COUNT(*) AS OrderCount, SUM(TotalAmount) AS TotalSpent
INTO #CustomerStats
FROM Orders
WHERE OrderDate >= DATEADD(month, -3, GETDATE())
GROUP BY CustomerID;
性能提示:单次INSERT语句包含100-1000行值时性能最佳,过多会导致编译时间增加。
3.2 UPDATE与DELETE优化
复杂的更新操作可以使用FROM子句:
sql复制-- 基于连接条件的更新
UPDATE p
SET p.StockQty = p.StockQty - od.Quantity
FROM Products p
JOIN OrderDetails od ON p.ProductID = od.ProductID
JOIN Orders o ON od.OrderID = o.OrderID
WHERE o.OrderDate > '2023-01-01';
-- 使用CTE进行复杂删除
WITH InactiveCustomers AS (
SELECT CustomerID
FROM Customers c
WHERE NOT EXISTS (
SELECT 1 FROM Orders
WHERE CustomerID = c.CustomerID
AND OrderDate > DATEADD(year, -1, GETDATE())
)
)
DELETE FROM CustomerNotes
WHERE CustomerID IN (SELECT CustomerID FROM InactiveCustomers);
3.3 MERGE语句实战
MERGE是处理"存在则更新,不存在则插入"场景的强大工具:
sql复制MERGE INTO ProductInventory AS target
USING (SELECT ProductID, Quantity FROM #NewInventory) AS source
ON target.ProductID = source.ProductID
WHEN MATCHED THEN
UPDATE SET
target.StockQty = target.StockQty + source.Quantity,
target.LastUpdated = GETDATE()
WHEN NOT MATCHED THEN
INSERT (ProductID, StockQty, LastUpdated)
VALUES (source.ProductID, source.Quantity, GETDATE())
OUTPUT $action, inserted.*; -- 返回操作结果
常见坑点:MERGE语句在SQL Server中需要分号(;)结尾,否则会报语法错误。
4. 查询优化与窗口函数
4.1 执行计划分析基础
理解执行计划是优化查询的第一步:
sql复制-- 获取实际执行计划
SET STATISTICS PROFILE ON;
SELECT * FROM Orders WHERE OrderDate > '2023-01-01';
SET STATISTICS PROFILE OFF;
-- 关键指标解读:
-- 实际行数 vs 估计行数(差异大说明统计信息可能过期)
-- 昂贵的键查找(Key Lookup)可能缺少覆盖索引
-- 排序(Spool)和哈希匹配(Hash Match)是高开销操作
4.2 窗口函数高级应用
窗口函数是SQL Server中最强大的分析工具之一:
sql复制-- 计算移动平均(3个月窗口)
SELECT
OrderID, OrderDate, TotalAmount,
AVG(TotalAmount) OVER (
PARTITION BY CustomerID
ORDER BY OrderDate
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
) AS MovingAvg
FROM Orders;
-- 处理排名与分页
WITH RankedProducts AS (
SELECT
ProductID, ProductName, Price,
ROW_NUMBER() OVER (ORDER BY Price DESC) AS PriceRank,
NTILE(4) OVER (ORDER BY Price DESC) AS PriceQuartile
FROM Products
)
SELECT * FROM RankedProducts
WHERE PriceRank BETWEEN 11 AND 20; -- 第二页数据
4.3 临时表与表变量选择
临时对象的选择对性能影响很大:
sql复制-- 临时表(适合大数据集、需要索引)
CREATE TABLE #TempOrders (
OrderID INT PRIMARY KEY,
CustomerID INT,
TotalAmount DECIMAL(10,2)
);
INSERT INTO #TempOrders
SELECT OrderID, CustomerID, TotalAmount FROM Orders
WHERE OrderDate > DATEADD(month, -1, GETDATE());
CREATE INDEX IX_TempOrders_CustomerID ON #TempOrders(CustomerID);
-- 表变量(适合小数据集、简单操作)
DECLARE @OrderStats TABLE (
CustomerID INT PRIMARY KEY,
OrderCount INT,
TotalSpent DECIMAL(10,2)
);
INSERT INTO @OrderStats
SELECT CustomerID, COUNT(*), SUM(TotalAmount)
FROM Orders
GROUP BY CustomerID;
性能对比:临时表支持统计信息,适合复杂查询;表变量没有统计信息,但减少重编译,适合简单场景。
5. 事务与错误处理最佳实践
5.1 健壮的事务处理
编写事务时需要考虑这些模式:
sql复制BEGIN TRY
BEGIN TRANSACTION;
-- 业务操作1
UPDATE Accounts SET Balance = Balance - 1000
WHERE AccountID = 123;
-- 业务操作2
UPDATE Accounts SET Balance = Balance + 1000
WHERE AccountID = 456;
-- 记录交易
INSERT INTO Transactions (FromAccount, ToAccount, Amount, TransactionDate)
VALUES (123, 456, 1000, GETDATE());
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
IF @@TRANCOUNT > 0
ROLLBACK TRANSACTION;
-- 记录错误详情
DECLARE @ErrorMessage NVARCHAR(4000) = ERROR_MESSAGE();
DECLARE @ErrorSeverity INT = ERROR_SEVERITY();
DECLARE @ErrorState INT = ERROR_STATE();
RAISERROR(@ErrorMessage, @ErrorSeverity, @ErrorState);
END CATCH
5.2 TRY_*函数处理数据转换
SQL Server提供了一系列TRY_*函数安全处理转换:
sql复制-- 安全转换示例
SELECT
OrderID,
TRY_CONVERT(DATE, OrderDateStr) AS OrderDate, -- 字符串转日期
TRY_CAST(TotalAmountStr AS DECIMAL(10,2)) AS Amount, -- 字符串转数值
TRY_PARSE(JSONData AS INT USING 'en-US') AS JSONValue -- 解析复杂格式
FROM StagingOrders
WHERE TRY_CONVERT(DATE, OrderDateStr) IS NOT NULL; -- 只选择能转换的行
5.3 动态SQL安全实践
动态SQL虽然强大但需要防范SQL注入:
sql复制DECLARE @TableName NVARCHAR(128) = 'Orders';
DECLARE @StartDate DATE = '2023-01-01';
DECLARE @SQL NVARCHAR(MAX);
DECLARE @Params NVARCHAR(MAX) = N'@StartDateParam DATE';
SET @SQL = N'
SELECT * FROM ' + QUOTENAME(@TableName) + '
WHERE OrderDate >= @StartDateParam
ORDER BY OrderID;';
EXEC sp_executesql @SQL, @Params, @StartDateParam = @StartDate;
安全准则:永远不要直接拼接用户输入到SQL中,始终使用参数化查询或至少使用QUOTENAME()函数处理对象名。
6. 系统函数与高级特性
6.1 JSON支持实战
SQL Server的JSON功能可以桥接关系型和文档型数据:
sql复制-- 从JSON提取数据
SELECT
OrderID,
JSON_VALUE(OrderDetails, '$.CustomerID') AS CustomerID,
JSON_VALUE(OrderDetails, '$.OrderDate') AS OrderDate,
JSON_QUERY(OrderDetails, '$.Items') AS Items
FROM OrdersWithJSON
WHERE ISJSON(OrderDetails) = 1;
-- 生成JSON数据
SELECT
CustomerID,
CustomerName,
(
SELECT OrderID, OrderDate, TotalAmount
FROM Orders
WHERE CustomerID = c.CustomerID
FOR JSON PATH
) AS Orders
FROM Customers c
FOR JSON PATH, ROOT('Customers');
6.2 时态表处理历史数据
时态表自动跟踪数据变化历史:
sql复制-- 创建时态表
CREATE TABLE Employees (
EmployeeID INT PRIMARY KEY,
Name NVARCHAR(100) NOT NULL,
Position NVARCHAR(100) NOT NULL,
Salary DECIMAL(10,2),
ValidFrom DATETIME2 GENERATED ALWAYS AS ROW START,
ValidTo DATETIME2 GENERATED ALWAYS AS ROW END,
PERIOD FOR SYSTEM_TIME (ValidFrom, ValidTo)
)
WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.EmployeesHistory));
-- 查询历史数据
SELECT * FROM Employees
FOR SYSTEM_TIME BETWEEN '2023-01-01' AND '2023-02-01'
WHERE EmployeeID = 1001;
6.3 字符串处理技巧
高效的字符串操作可以简化很多业务逻辑:
sql复制-- 字符串聚合(SQL Server 2017+)
SELECT
DepartmentID,
STRING_AGG(EmployeeName, ', ') WITHIN GROUP (ORDER BY EmployeeName) AS TeamMembers
FROM Employees
GROUP BY DepartmentID;
-- 复杂字符串解析
DECLARE @FullName NVARCHAR(200) = 'Smith, John (Marketing)';
SELECT
TRIM(SUBSTRING(@FullName, CHARINDEX(')', @FullName) + 1, LEN(@FullName))) AS LastName,
TRIM(SUBSTRING(
@FullName,
CHARINDEX(',', @FullName) + 1,
CHARINDEX('(', @FullName) - CHARINDEX(',', @FullName) - 1
)) AS FirstName,
TRIM(SUBSTRING(
@FullName,
CHARINDEX('(', @FullName) + 1,
CHARINDEX(')', @FullName) - CHARINDEX('(', @FullName) - 1
)) AS Department;
7. 性能监控与维护脚本
7.1 查询性能分析
这些脚本帮助识别性能瓶颈:
sql复制-- 查找最耗CPU的查询
SELECT TOP 10
qs.total_worker_time/qs.execution_count AS avg_cpu_time,
qs.total_logical_reads/qs.execution_count AS avg_logical_reads,
qs.execution_count,
SUBSTRING(qt.text, (qs.statement_start_offset/2)+1,
((CASE qs.statement_end_offset
WHEN -1 THEN DATALENGTH(qt.text)
ELSE qs.statement_end_offset
END - qs.statement_start_offset)/2)+1) AS query_text,
DB_NAME(qt.dbid) AS database_name
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) AS qt
ORDER BY avg_cpu_time DESC;
-- 查找缺失索引
SELECT
migs.avg_total_user_cost * (migs.avg_user_impact / 100.0) * (migs.user_seeks + migs.user_scans) AS improvement_measure,
'CREATE INDEX [IX_' + OBJECT_NAME(mid.object_id) + '_' + REPLACE(REPLACE(REPLACE(
ISNULL(mid.equality_columns,'') + ISNULL(',' + mid.inequality_columns,''),
'[', ''), ']', ''), ', ', '_') + ']' +
' ON ' + mid.statement + ' (' + ISNULL(mid.equality_columns,'') +
CASE WHEN mid.equality_columns IS NOT NULL AND mid.inequality_columns IS NOT NULL THEN ',' ELSE '' END +
ISNULL(mid.inequality_columns, '') + ')' +
ISNULL(' INCLUDE (' + mid.included_columns + ')', '') AS create_index_statement
FROM sys.dm_db_missing_index_groups mig
INNER JOIN sys.dm_db_missing_index_group_stats migs ON migs.group_handle = mig.index_group_handle
INNER JOIN sys.dm_db_missing_index_details mid ON mig.index_handle = mid.index_handle
WHERE migs.avg_total_user_cost * (migs.avg_user_impact / 100.0) * (migs.user_seeks + migs.user_scans) > 10
ORDER BY improvement_measure DESC;
7.2 数据库维护脚本
定期维护可以保持数据库性能:
sql复制-- 更新统计信息
EXEC sp_updatestats;
-- 重建碎片化严重的索引
SELECT
OBJECT_NAME(ind.object_id) AS TableName,
ind.name AS IndexName,
ips.avg_fragmentation_in_percent
FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, 'LIMITED') ips
INNER JOIN sys.indexes ind ON ips.object_id = ind.object_id AND ips.index_id = ind.index_id
WHERE ips.avg_fragmentation_in_percent > 30
ORDER BY ips.avg_fragmentation_in_percent DESC;
-- 生成重建索引脚本
SELECT
'ALTER INDEX [' + i.name + '] ON [' + SCHEMA_NAME(o.schema_id) + '].[' + OBJECT_NAME(i.object_id) + '] ' +
CASE WHEN ips.avg_fragmentation_in_percent > 30 THEN 'REBUILD' ELSE 'REORGANIZE' END +
' WITH (ONLINE = ON);' AS IndexMaintenanceCommand
FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, 'LIMITED') ips
INNER JOIN sys.indexes i ON ips.object_id = i.object_id AND ips.index_id = i.index_id
INNER JOIN sys.objects o ON i.object_id = o.object_id
WHERE ips.avg_fragmentation_in_percent > 10
AND i.name IS NOT NULL;
7.3 监控长时间运行的事务
这些查询帮助识别潜在问题:
sql复制-- 查找长时间运行的事务
SELECT
tat.transaction_id,
tat.name AS transaction_name,
tst.session_id,
DB_NAME(tdt.database_id) AS database_name,
tdt.database_transaction_begin_time,
DATEDIFF(minute, tdt.database_transaction_begin_time, GETDATE()) AS duration_minutes,
tdt.database_transaction_log_bytes_used,
tdt.database_transaction_log_bytes_reserved
FROM sys.dm_tran_active_transactions tat
INNER JOIN sys.dm_tran_database_transactions tdt ON tat.transaction_id = tdt.transaction_id
INNER JOIN sys.dm_tran_session_transactions tst ON tat.transaction_id = tst.transaction_id
WHERE tdt.database_transaction_begin_time < DATEADD(minute, -30, GETDATE())
ORDER BY duration_minutes DESC;
-- 查找阻塞链
SELECT
blocking.session_id AS blocking_session_id,
blocked.session_id AS blocked_session_id,
wait.wait_type AS blocking_wait_type,
wait.wait_time AS wait_time_ms,
blocking_text.text AS blocking_command,
blocked_text.text AS blocked_command
FROM sys.dm_exec_connections AS blocking
INNER JOIN sys.dm_exec_requests AS blocked ON blocking.session_id = blocked.blocking_session_id
INNER JOIN sys.dm_os_waiting_tasks AS wait ON blocked.session_id = wait.session_id
CROSS APPLY sys.dm_exec_sql_text(blocking.most_recent_sql_handle) AS blocking_text
CROSS APPLY sys.dm_exec_sql_text(blocked.sql_handle) AS blocked_text;
8. 安全与权限管理
8.1 细粒度权限控制
SQL Server提供丰富的权限控制选项:
sql复制-- 创建自定义数据库角色
CREATE ROLE OrderEntryRole;
GRANT SELECT, INSERT ON SCHEMA::Sales TO OrderEntryRole;
GRANT EXECUTE ON OBJECT::sp_PlaceOrder TO OrderEntryRole;
DENY DELETE ON SCHEMA::Sales TO OrderEntryRole;
-- 行级安全(SQL Server 2016+)
CREATE SECURITY POLICY SalesFilter
ADD FILTER PREDICATE dbo.fn_securitypredicate(EmployeeID)
ON dbo.SalesData
WITH (STATE = ON);
-- 函数定义示例
CREATE FUNCTION dbo.fn_securitypredicate(@EmployeeID AS int)
RETURNS TABLE
WITH SCHEMABINDING
AS RETURN
SELECT 1 AS access_result
WHERE @EmployeeID = USER_ID()
OR IS_ROLEMEMBER('SalesManager') = 1;
8.2 敏感数据保护
加密技术保护关键数据:
sql复制-- 始终加密(Always Encrypted)配置步骤
-- 1. 创建列主密钥
CREATE COLUMN MASTER KEY [CMK_Auto1]
WITH (
KEY_STORE_PROVIDER_NAME = N'MSSQL_CERTIFICATE_STORE',
KEY_PATH = N'CurrentUser/My/A91A5B5C8D9E0F1A2B3C4D5E6F7A8B9C'
);
-- 2. 创建列加密密钥
CREATE COLUMN ENCRYPTION KEY [CEK_Auto1]
WITH VALUES (
COLUMN_MASTER_KEY = [CMK_Auto1],
ALGORITHM = 'RSA_OAEP',
ENCRYPTED_VALUE = 0x01700000016C006F00630061006C006D0061006300680069006E0065002F006D0079002F003200660061003800640033003000380031003100320038003400340065003000320036003900320062003200310038003600646062313932343764...
);
-- 3. 创建加密列
CREATE TABLE Customers (
CustomerID INT PRIMARY KEY,
SSN VARCHAR(11) COLLATE Latin1_General_BIN2
ENCRYPTED WITH (
ENCRYPTION_TYPE = DETERMINISTIC,
ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256',
COLUMN_ENCRYPTION_KEY = CEK_Auto1
) NULL,
CreditCardNumber VARCHAR(19) COLLATE Latin1_General_BIN2
ENCRYPTED WITH (
ENCRYPTION_TYPE = RANDOMIZED,
ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256',
COLUMN_ENCRYPTION_KEY = CEK_Auto1
) NULL
);
8.3 审计与合规
满足合规要求的审计方案:
sql复制-- 创建服务器审计
CREATE SERVER AUDIT HIPAA_Audit
TO FILE (FILEPATH = 'D:\Audits\', MAXSIZE = 1 GB)
WITH (QUEUE_DELAY = 1000, ON_FAILURE = CONTINUE);
-- 启用审计规范
CREATE DATABASE AUDIT SPECIFICATION HIPAA_DB_Spec
FOR SERVER AUDIT HIPAA_Audit
ADD (SELECT, INSERT, UPDATE, DELETE ON SCHEMA::dbo BY public),
ADD (EXECUTE ON DATABASE::MedicalDB BY public);
ALTER SERVER AUDIT HIPAA_Audit WITH (STATE = ON);
ALTER DATABASE AUDIT SPECIFICATION HIPAA_DB_Spec WITH (STATE = ON);
-- 查询审计日志
SELECT
event_time,
action_id,
succeeded,
session_id,
server_principal_name,
database_principal_name,
object_name,
statement
FROM sys.fn_get_audit_file('D:\Audits\HIPAA_Audit*', NULL, NULL);
9. 实际案例解析
9.1 分页查询优化
从简单到复杂的三种分页方案:
sql复制-- 基础方法(SQL Server 2012前)
SELECT TOP 20 *
FROM Orders
WHERE OrderID NOT IN (
SELECT TOP 200 OrderID
FROM Orders
ORDER BY OrderDate DESC
)
ORDER BY OrderDate DESC;
-- 使用OFFSET-FETCH(SQL Server 2012+)
SELECT OrderID, OrderDate, CustomerID, TotalAmount
FROM Orders
ORDER BY OrderDate DESC
OFFSET 200 ROWS FETCH NEXT 20 ROWS ONLY;
-- 高性能分页(大型表适用)
DECLARE @PageSize INT = 20;
DECLARE @PageNumber INT = 11;
DECLARE @LastOrderDate DATETIME;
DECLARE @LastOrderID INT;
-- 先获取上一页的最后一条记录
SELECT TOP 1
@LastOrderDate = OrderDate,
@LastOrderID = OrderID
FROM Orders
ORDER BY OrderDate DESC
OFFSET ((@PageNumber-1)*@PageSize)-1 ROWS FETCH NEXT 1 ROWS ONLY;
-- 使用seek方法获取下一页
SELECT TOP (@PageSize) *
FROM Orders
WHERE
(OrderDate < @LastOrderDate) OR
(OrderDate = @LastOrderDate AND OrderID < @LastOrderID)
ORDER BY OrderDate DESC, OrderID DESC;
9.2 层级数据查询
处理组织结构、评论线程等层级数据:
sql复制-- 使用递归CTE查询组织结构
WITH OrgHierarchy AS (
-- 基础查询(顶级节点)
SELECT
EmployeeID,
Name,
Position,
ManagerID,
0 AS Level,
CAST(Name AS VARCHAR(1000)) AS HierarchyPath
FROM Employees
WHERE ManagerID IS NULL
UNION ALL
-- 递归部分
SELECT
e.EmployeeID,
e.Name,
e.Position,
e.ManagerID,
oh.Level + 1,
CAST(oh.HierarchyPath + ' > ' + e.Name AS VARCHAR(1000))
FROM Employees e
JOIN OrgHierarchy oh ON e.ManagerID = oh.EmployeeID
)
SELECT * FROM OrgHierarchy
ORDER BY HierarchyPath;
-- 使用hierarchyid数据类型(更高效)
SELECT
EmployeeID,
Name,
Position,
OrgNode.ToString() AS OrgPath,
OrgNode.GetLevel() AS Level
FROM EmployeesHierarchy
WHERE OrgNode.IsDescendantOf(
(SELECT OrgNode FROM EmployeesHierarchy WHERE EmployeeID = 101)
) = 1;
9.3 数据归档策略
大型表的历史数据归档方案:
sql复制-- 分区表归档方案
-- 1. 创建分区函数(按日期范围)
CREATE PARTITION FUNCTION pf_OrderDateRange(DATE)
AS RANGE RIGHT FOR VALUES (
'2020-01-01', '2021-01-01', '2022-01-01',
'2023-01-01', '2024-01-01'
);
-- 2. 创建分区方案
CREATE PARTITION SCHEME ps_OrderDateRange
AS PARTITION pf_OrderDateRange
TO (fg_Archive2019, fg_Archive2020, fg_Archive2021,
fg_Current2022, fg_Current2023, fg_Future);
-- 3. 创建分区表
CREATE TABLE Orders (
OrderID INT,
OrderDate DATE,
CustomerID INT,
TotalAmount DECIMAL(10,2)
) ON ps_OrderDateRange(OrderDate);
-- 4. 归档旧数据(切换分区)
ALTER TABLE Orders SWITCH PARTITION 1 TO OrdersArchive PARTITION 1;
-- 5. 压缩归档数据
ALTER PARTITION SCHEME ps_OrderDateRange
NEXT USED fg_ReadOnly;
ALTER PARTITION FUNCTION pf_OrderDateRange()
SPLIT RANGE('2025-01-01');
10. 跨数据库操作
10.1 链接服务器查询
安全地访问远程SQL Server实例:
sql复制-- 创建链接服务器
EXEC sp_addlinkedserver
@server = 'LINKED_SRV',
@srvproduct = '',
@provider = 'SQLNCLI',
@datasrc = 'remote_server_name';
-- 创建登录映射
EXEC sp_addlinkedsrvlogin
@rmtsrvname = 'LINKED_SRV',
@useself = 'false',
@locallogin = NULL,
@rmtuser = 'remote_user',
@rmtpassword = 'password';
-- 分布式查询示例
SELECT a.LocalData, r.RemoteData
FROM LocalTable a
JOIN LINKED_SRV.RemoteDB.dbo.RemoteTable r ON a.ID = r.ID;
-- 更安全的OPENQUERY方式
SELECT * FROM OPENQUERY(LINKED_SRV,
'SELECT * FROM RemoteDB.dbo.RemoteTable WHERE Date > ''2023-01-01''');
安全提示:避免在链接服务器上使用高权限账户,考虑使用仅具有必要权限的专用账户。
10.2 弹性查询(Azure SQL DB)
在Azure环境中跨数据库查询:
sql复制-- 1. 创建主密钥(如果不存在)
CREATE MASTER KEY ENCRYPTION BY PASSWORD = 'StrongPassword123!';
-- 2. 创建数据库范围的凭据
CREATE DATABASE SCOPED CREDENTIAL ElasticDBQueryCred
WITH IDENTITY = 'remote_user',
SECRET = 'password';
-- 3. 创建外部数据源
CREATE EXTERNAL DATA SOURCE RemoteDBDataSource WITH (
TYPE = RDBMS,
LOCATION = 'remote-server.database.windows.net',
DATABASE_NAME = 'RemoteDB',
CREDENTIAL = ElasticDBQueryCred
);
-- 4. 创建外部表
CREATE EXTERNAL TABLE [dbo].[RemoteOrders] (
[OrderID] INT NOT NULL,
[OrderDate] DATETIME NOT NULL,
[CustomerID] INT NOT NULL
)
WITH (
DATA_SOURCE = RemoteDBDataSource,
SCHEMA_NAME = 'dbo',
OBJECT_NAME = 'Orders'
);
-- 5. 查询外部表
SELECT * FROM LocalOrders lo
JOIN RemoteOrders ro ON lo.CustomerID = ro.CustomerID;
10.3 批量导入导出数据
高效的数据迁移方法:
sql复制-- 使用BCP导出数据
EXEC xp_cmdshell 'bcp "SELECT * FROM SalesDB.dbo.Orders" queryout "D:\Export\Orders.csv" -c -t, -T -S localhost';
-- 使用BULK INSERT导入
BULK INSERT Orders_Staging
FROM 'D:\Import\Orders.csv'
WITH (
FIELDTERMINATOR = ',',
ROWTERMINATOR = '\n',
FIRSTROW = 2,
TABLOCK,
BATCHSIZE = 10000
);
-- 使用OPENROWSET一次性加载
INSERT INTO Orders_Staging (OrderID, CustomerID, OrderDate)
SELECT OrderID, CustomerID, OrderDate
FROM OPENROWSET(
BULK 'D:\Import\Orders.json',
SINGLE_CLOB
) AS j
CROSS APPLY OPENJSON(BulkColumn)
WITH (
OrderID INT '$.id',
CustomerID INT '$.customer',
OrderDate DATETIME '$.date'
);
11. 高级T-SQL编程
11.1 存储过程最佳实践
编写可维护的存储过程:
sql复制CREATE PROCEDURE usp_ProcessMonthlyReport
@Year INT,
@Month INT,
@ForceReprocess BIT = 0,
@DebugMode BIT = 0
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRY
-- 参数验证
IF @Year < 2000 OR @Year > YEAR(GETDATE()) + 1
THROW 50001, '无效的年份参数', 1;
IF @Month < 1 OR @Month > 12
THROW 50002, '无效的月份参数', 1;
-- 检查是否已处理
IF EXISTS (
SELECT 1 FROM ReportProcessingLog
WHERE Year = @Year AND Month = @Month
AND Status = 'Completed'
) AND @ForceReprocess = 0
BEGIN
RAISERROR('该月份报表已处理过,如需重新处理请设置@ForceReprocess=1', 10, 1);
RETURN;
END
-- 开始事务
BEGIN TRANSACTION;
-- 记录处理开始
MERGE INTO ReportProcessingLog AS target
USING (SELECT @Year AS Year, @Month AS Month) AS source
ON target.Year = source.Year AND target.Month = source.Month
WHEN MATCHED THEN
UPDATE SET
target.StartTime = GETDATE(),
target.Status = 'Processing',
target.LastModified = GETDATE()
WHEN NOT MATCHED THEN
INSERT (Year, Month, StartTime, Status)
VALUES (source.Year, source.Month, GETDATE(), 'Processing');
-- 实际业务处理(示例)
-- 步骤1:准备临时数据
IF @DebugMode = 1
PRINT '准备临时数据...';
SELECT
CustomerID,
SUM(TotalAmount) AS MonthlyTotal
INTO #CustomerTotals
FROM Orders
WHERE YEAR(OrderDate) = @Year AND MONTH(OrderDate) = @Month
GROUP BY CustomerID;
-- 步骤2:生成报表记录
IF @DebugMode = 1
PRINT '生成报表记录...';
INSERT INTO MonthlyReports (ReportDate, CustomerID, Amount)
SELECT
DATEFROMPARTS(@Year, @Month, 1),
CustomerID,
MonthlyTotal
FROM #CustomerTotals;
-- 标记处理完成
UPDATE ReportProcessingLog
SET
EndTime = GETDATE(),
Status = 'Completed',
RowsProcessed = @@ROWCOUNT
WHERE Year = @Year AND Month = @Month;
COMMIT TRANSACTION;
IF @DebugMode = 1
PRINT '处理完成';
END TRY
BEGIN CATCH
IF @@TRANCOUNT > 0
ROLLBACK TRANSACTION;
-- 记录错误
INSERT INTO ErrorLog (ProcedureName, ErrorNumber, ErrorMessage, ErrorLine)
VALUES (
OBJECT_NAME(@@PROCID),
ERROR_NUMBER(),
ERROR_MESSAGE(),
ERROR_LINE()
);
-- 更新处理状态
UPDATE ReportProcessingLog
SET
EndTime = GETDATE(),
Status = 'Failed',
ErrorMessage = ERROR_MESSAGE()
WHERE Year = @Year AND Month = @Month;
-- 重新抛出错误
THROW;
END CATCH
END;
11.2 表值函数应用
表值函数可以封装复杂逻辑:
sql复制-- 内联表值函数(性能更好)
CREATE FUNCTION dbo.fn_GetCustomerOrders
(
@CustomerID INT,
@StartDate DATE,
@EndDate DATE
)
RETURNS TABLE
AS
RETURN
(
SELECT
o.OrderID,
o.OrderDate,
o.TotalAmount,
COUNT(od.ProductID) AS ProductCount
FROM Orders o
JOIN OrderDetails od ON o.OrderID = od.OrderID
WHERE o.CustomerID = @CustomerID
AND o.OrderDate BETWEEN @StartDate AND @EndDate
GROUP BY o.OrderID, o.OrderDate, o.TotalAmount
);
-- 多语句表值函数(更灵活)
CREATE FUNCTION dbo.fn_AnalyzeSalesTrends
(
@CategoryID INT,
@MonthsToAnalyze INT = 12
)
RETURNS @Results TABLE
(
MonthYear CHAR(7),
TotalSales DECIMAL(12,2),
AvgSalePerOrder DECIMAL(10,2),
CustomerCount INT,
TrendIndicator VARCHAR(10)
