逐步实践步骤(含 SQL)

说明:以下脚本在 SQL Server Management Studio(SSMS)中执行。请根据你本机的 .bak 文件路径调整 MOVE 和文件名。

步骤1:还原 AdventureWorks2019

-- 1) 查看备份中的逻辑文件名(先执行这句,记下 LogicalName) RESTORE FILELISTONLY -- 读取备份包内逻辑文件清单(用于后续 MOVE 映射) FROM DISK = N'C:\Users\27332\Desktop\数据库ppt\AdventureWorks2019.bak'; -- 指定备份文件物理路径 GO -- 结束当前批次并提交执行 -- 2) 还原数据库(将 LogicalName 替换为上一步结果) RESTORE DATABASE AdventureWorks2019 -- 将备份还原为目标数据库 AdventureWorks2019 FROM DISK = N'C:\Users\27332\Desktop\数据库ppt\AdventureWorks2019.bak' -- 指定还原来源备份文件 WITH -- 开始配置还原选项 MOVE N'AdventureWorks2017' TO N'C:\Program Files\Microsoft SQL Server\MSSQL16.MSSQLSERVER\MSSQL\DATA\AdventureWorks2019.mdf', -- 将逻辑数据文件映射到新的 .mdf 物理路径 MOVE N'AdventureWorks2017_log' TO N'C:\Program Files\Microsoft SQL Server\MSSQL16.MSSQLSERVER\MSSQL\DATA\AdventureWorks2019_log.ldf', -- 将逻辑日志文件映射到新的 .ldf 物理路径 REPLACE, -- 若目标库已存在则强制覆盖 STATS = 5; -- 每完成 5% 输出一次还原进度 GO -- 结束当前批次并提交执行

步骤2:创建数据仓库数据库与基础表

-- 创建独立的数据仓库库 IF DB_ID('AdventureWorksDW_Lite') IS NULL -- 判断目标数据库是否不存在 CREATE DATABASE AdventureWorksDW_Lite; -- 不存在则创建数据仓库数据库 GO -- 结束当前批次并提交执行 USE AdventureWorksDW_Lite; -- 切换当前上下文到数据仓库数据库 GO -- 结束当前批次并提交执行 -- 维度表:日期 IF OBJECT_ID('dbo.DimDate', 'U') IS NOT NULL DROP TABLE dbo.DimDate; -- 若日期维表已存在则删除重建 CREATE TABLE dbo.DimDate ( -- 创建日期维度表 DateKey INT NOT NULL PRIMARY KEY, -- 日期代理键,格式 yyyymmdd FullDate DATE NOT NULL, -- 完整日期值 [Year] INT NOT NULL, -- 年 [Month] TINYINT NOT NULL, -- 月 [Day] TINYINT NOT NULL, -- 日 MonthName NVARCHAR(20) NOT NULL, -- 月份名称 QuarterNo TINYINT NOT NULL -- 季度编号 ); -- 结束日期维度表定义 -- 维度表:产品 IF OBJECT_ID('dbo.DimProduct', 'U') IS NOT NULL DROP TABLE dbo.DimProduct; -- 若产品维表已存在则删除重建 CREATE TABLE dbo.DimProduct ( -- 创建产品维度表 ProductKey INT IDENTITY(1,1) PRIMARY KEY, -- 产品代理键(自增主键) ProductID INT NOT NULL UNIQUE, -- 来源系统产品业务键 ProductName NVARCHAR(100) NOT NULL, -- 产品名称 ProductNumber NVARCHAR(25) NOT NULL, -- 产品编号 Color NVARCHAR(15) NULL, -- 产品颜色 StandardCost MONEY NULL, -- 标准成本 ListPrice MONEY NULL, -- 标价 ModifiedDate DATETIME NOT NULL -- 来源记录最后修改时间 ); -- 结束产品维度表定义 -- 维度表:客户 IF OBJECT_ID('dbo.DimCustomer', 'U') IS NOT NULL DROP TABLE dbo.DimCustomer; -- 若客户维表已存在则删除重建 CREATE TABLE dbo.DimCustomer ( -- 创建客户维度表 CustomerKey INT IDENTITY(1,1) PRIMARY KEY, -- 客户代理键(自增主键) CustomerID INT NOT NULL UNIQUE, -- 来源系统客户业务键 PersonName NVARCHAR(200) NULL, -- 个人客户姓名 StoreName NVARCHAR(200) NULL, -- 门店客户名称 CustomerType NVARCHAR(20) NOT NULL, -- 客户类型(Person/Store) ModifiedDate DATETIME NOT NULL -- 来源记录最后修改时间 ); -- 结束客户维度表定义 -- 维度表:区域 IF OBJECT_ID('dbo.DimTerritory', 'U') IS NOT NULL DROP TABLE dbo.DimTerritory; -- 若区域维表已存在则删除重建 CREATE TABLE dbo.DimTerritory ( -- 创建区域维度表 TerritoryKey INT IDENTITY(1,1) PRIMARY KEY, -- 区域代理键(自增主键) TerritoryID INT NOT NULL UNIQUE, -- 来源系统区域业务键 TerritoryName NVARCHAR(50) NOT NULL, -- 区域名称 CountryRegion NVARCHAR(50) NOT NULL, -- 国家/地区名称 [GroupName] NVARCHAR(50) NOT NULL -- 区域分组名称 ); -- 结束区域维度表定义 -- 事实表:销售 IF OBJECT_ID('dbo.FactSales', 'U') IS NOT NULL DROP TABLE dbo.FactSales; -- 若销售事实表已存在则删除重建 CREATE TABLE dbo.FactSales ( -- 创建销售事实表 FactSalesKey BIGINT IDENTITY(1,1) PRIMARY KEY, -- 事实表代理键(自增主键) SalesOrderID INT NOT NULL, -- 销售订单号 SalesOrderDetailID INT NOT NULL, -- 销售订单明细号 DateKey INT NOT NULL, -- 日期维外键 ProductKey INT NOT NULL, -- 产品维外键 CustomerKey INT NOT NULL, -- 客户维外键 TerritoryKey INT NOT NULL, -- 区域维外键 OrderQty SMALLINT NOT NULL, -- 订购数量 UnitPrice MONEY NOT NULL, -- 单价 UnitPriceDiscount MONEY NOT NULL, -- 单价折扣率 SalesAmount MONEY NOT NULL, -- 销售金额 DiscountAmount MONEY NOT NULL, -- 折扣金额 ModifiedDate DATETIME NOT NULL, -- 来源记录最后修改时间 CONSTRAINT UQ_FactSales_OrderLine UNIQUE (SalesOrderID, SalesOrderDetailID) -- 保证订单行唯一,支持增量合并 ); -- 结束销售事实表定义 -- ETL批次日志表(元数据) IF OBJECT_ID('dbo.ETLBatchLog', 'U') IS NOT NULL DROP TABLE dbo.ETLBatchLog; -- 若批次日志表已存在则删除重建 CREATE TABLE dbo.ETLBatchLog ( -- 创建 ETL 批次日志表 BatchID INT IDENTITY(1,1) PRIMARY KEY, -- 批次主键(自增) BatchType NVARCHAR(20) NOT NULL, -- 批次类型:FULL / INCREMENTAL StartTime DATETIME2 NOT NULL, -- 批次开始时间 EndTime DATETIME2 NULL, -- 批次结束时间 LastLoadTime DATETIME2 NULL, -- 增量装载的上次成功时间点 RowsInserted INT NULL, -- 本批次影响行数 Status NVARCHAR(20) NOT NULL, -- 批次状态:RUNNING/SUCCESS/FAILED ErrorMessage NVARCHAR(4000) NULL -- 失败时的错误消息 ); -- 结束 ETL 批次日志表定义 GO -- 结束当前批次并提交执行

步骤3:初始化日期维度

USE AdventureWorksDW_Lite; -- 切换当前上下文到数据仓库数据库 GO -- 结束当前批次并提交执行 DECLARE @StartDate DATE = '2011-01-01'; -- 定义日期维生成起始日期 DECLARE @EndDate DATE = '2014-12-31'; -- 定义日期维生成结束日期 ;WITH d AS ( -- 递归 CTE:逐日生成日期序列 SELECT @StartDate AS dt -- 锚点:从起始日期开始 UNION ALL -- 递归并集,拼接后续日期 SELECT DATEADD(DAY, 1, dt) -- 每次在上一天基础上加 1 天 FROM d -- 递归引用 CTE 自身 WHERE dt < @EndDate -- 直到小于结束日期为止 ) -- 结束 CTE 定义 INSERT INTO dbo.DimDate (DateKey, FullDate, [Year], [Month], [Day], MonthName, QuarterNo) -- 指定目标列并写入日期维 SELECT -- 从日期序列投影出维度字段 CONVERT(INT, FORMAT(dt, 'yyyyMMdd')), -- 将日期转换为 yyyymmdd 整型键 dt, -- 完整日期 YEAR(dt), -- 年 MONTH(dt), -- 月 DAY(dt), -- 日 DATENAME(MONTH, dt), -- 月份名称 DATEPART(QUARTER, dt) -- 季度编号 FROM d -- 数据来源为递归 CTE OPTION (MAXRECURSION 0); -- 取消默认递归层数限制 GO -- 结束当前批次并提交执行

步骤4:全量装载维度表(技术点 5.8)

USE AdventureWorksDW_Lite; -- 切换当前上下文到数据仓库数据库 GO -- 结束当前批次并提交执行 -- DimProduct INSERT INTO dbo.DimProduct (ProductID, ProductName, ProductNumber, Color, StandardCost, ListPrice, ModifiedDate) -- 向产品维写入全量数据 SELECT -- 选择源系统产品字段 p.ProductID, -- 产品业务键 p.Name, -- 产品名称 p.ProductNumber, -- 产品编号 p.Color, -- 颜色 p.StandardCost, -- 标准成本 p.ListPrice, -- 标价 p.ModifiedDate -- 来源修改时间 FROM AdventureWorks2019.Production.Product p; -- 来源表:生产模块产品表 -- DimCustomer INSERT INTO dbo.DimCustomer (CustomerID, PersonName, StoreName, CustomerType, ModifiedDate) -- 向客户维写入全量数据 SELECT -- 选择并转换客户相关字段 c.CustomerID, -- 客户业务键 CONCAT(pp.FirstName, N' ', pp.LastName) AS PersonName, -- 拼接个人客户姓名 s.Name AS StoreName, -- 门店名称 CASE WHEN c.PersonID IS NOT NULL THEN N'Person' ELSE N'Store' END AS CustomerType, -- 根据 PersonID 判断客户类型 c.ModifiedDate -- 来源修改时间 FROM AdventureWorks2019.Sales.Customer c -- 主表:客户表 LEFT JOIN AdventureWorks2019.Person.Person pp -- 左连接个人信息表(个人客户) ON c.PersonID = pp.BusinessEntityID -- 关联条件:PersonID 对应实体 ID LEFT JOIN AdventureWorks2019.Sales.Store s -- 左连接门店信息表(门店客户) ON c.StoreID = s.BusinessEntityID; -- 关联条件:StoreID 对应实体 ID -- DimTerritory INSERT INTO dbo.DimTerritory (TerritoryID, TerritoryName, CountryRegion, [GroupName]) -- 向区域维写入全量数据 SELECT -- 选择区域与国家信息字段 st.TerritoryID, -- 区域业务键 st.Name, -- 区域名称 cr.Name AS CountryRegion, -- 国家/地区名称 st.[Group] -- 区域分组 FROM AdventureWorks2019.Sales.SalesTerritory st -- 来源表:销售区域表 JOIN AdventureWorks2019.Person.CountryRegion cr -- 连接国家地区维表 ON st.CountryRegionCode = cr.CountryRegionCode; -- 按国家地区代码关联 GO -- 结束当前批次并提交执行

步骤5:全量装载事实表(技术点 5.8)

USE AdventureWorksDW_Lite; -- 切换当前上下文到数据仓库数据库 GO -- 结束当前批次并提交执行 DECLARE @BatchStart DATETIME2 = SYSDATETIME(); -- 记录当前全量批次开始时间 INSERT INTO dbo.ETLBatchLog (BatchType, StartTime, LastLoadTime, Status) -- 写入一条运行中批次日志 VALUES (N'FULL', @BatchStart, NULL, N'RUNNING'); -- 标记为 FULL 批次且状态为 RUNNING DECLARE @CurrentBatchID INT = SCOPE_IDENTITY(); -- 获取刚插入日志行的批次 ID BEGIN TRY -- 开始异常捕获块(成功路径) INSERT INTO dbo.FactSales ( -- 向销售事实表装载全量数据 SalesOrderID, SalesOrderDetailID, DateKey, ProductKey, CustomerKey, TerritoryKey, -- 业务键与各维外键列 OrderQty, UnitPrice, UnitPriceDiscount, SalesAmount, DiscountAmount, ModifiedDate -- 度量值与修改时间列 ) -- 结束目标列定义 SELECT -- 从源系统订单明细与维表映射得到事实行 sod.SalesOrderID, -- 销售订单号 sod.SalesOrderDetailID, -- 销售订单明细号 CONVERT(INT, FORMAT(soh.OrderDate, 'yyyyMMdd')) AS DateKey, -- 订单日期转日期键 dp.ProductKey, -- 映射产品代理键 dc.CustomerKey, -- 映射客户代理键 dt.TerritoryKey, -- 映射区域代理键 sod.OrderQty, -- 订购数量 sod.UnitPrice, -- 单价 sod.UnitPriceDiscount, -- 单价折扣率 sod.LineTotal AS SalesAmount, -- 行总额作为销售金额 sod.UnitPrice * sod.OrderQty * sod.UnitPriceDiscount AS DiscountAmount, -- 按公式计算折扣金额 sod.ModifiedDate -- 来源修改时间 FROM AdventureWorks2019.Sales.SalesOrderDetail sod -- 主事实来源:订单明细 JOIN AdventureWorks2019.Sales.SalesOrderHeader soh -- 连接订单头获取订单日期和客户 ON sod.SalesOrderID = soh.SalesOrderID -- 按订单号关联 JOIN dbo.DimProduct dp -- 连接产品维映射 ProductKey ON sod.ProductID = dp.ProductID -- 按产品业务键关联 JOIN dbo.DimCustomer dc -- 连接客户维映射 CustomerKey ON soh.CustomerID = dc.CustomerID -- 按客户业务键关联 JOIN dbo.DimTerritory dt -- 连接区域维映射 TerritoryKey ON soh.TerritoryID = dt.TerritoryID; -- 按区域业务键关联 UPDATE dbo.ETLBatchLog -- 更新当前批次日志为成功 SET EndTime = SYSDATETIME(), -- 记录结束时间 RowsInserted = @@ROWCOUNT, -- 记录上一条 DML 影响行数 Status = N'SUCCESS' -- 状态置为成功 WHERE BatchID = @CurrentBatchID; -- 仅更新当前批次 END TRY -- 成功路径结束 BEGIN CATCH -- 异常路径开始 UPDATE dbo.ETLBatchLog -- 更新当前批次日志为失败 SET EndTime = SYSDATETIME(), -- 记录失败结束时间 Status = N'FAILED', -- 状态置为失败 ErrorMessage = ERROR_MESSAGE() -- 写入错误信息 WHERE BatchID = @CurrentBatchID; -- 仅更新当前批次 THROW; -- 将错误继续抛出给上层 END CATCH; -- 异常路径结束 GO -- 结束当前批次并提交执行

步骤6:索引优化与查询验证(技术点 5.9、5.14)

USE AdventureWorksDW_Lite; -- 切换当前上下文到数据仓库数据库 GO -- 结束当前批次并提交执行 -- 常见分析路径:时间 + 产品 CREATE NONCLUSTERED INDEX IX_FactSales_Date_Product -- 创建非聚集索引:按日期和产品查询优化 ON dbo.FactSales (DateKey, ProductKey); -- 索引键列为 DateKey 与 ProductKey -- 覆盖索引:区域分析 CREATE NONCLUSTERED INDEX IX_FactSales_Territory_Date -- 创建非聚集索引:按区域和日期查询优化 ON dbo.FactSales (TerritoryKey, DateKey) -- 索引键列为 TerritoryKey 与 DateKey INCLUDE (SalesAmount, OrderQty, CustomerKey); -- 覆盖常用返回列,减少回表 -- 覆盖索引:客户分析 CREATE NONCLUSTERED INDEX IX_FactSales_Customer_Date -- 创建非聚集索引:按客户和日期查询优化 ON dbo.FactSales (CustomerKey, DateKey) -- 索引键列为 CustomerKey 与 DateKey INCLUDE (SalesAmount, ProductKey, OrderQty); -- 覆盖常用返回列,减少回表 GO -- 结束当前批次并提交执行 -- 示例查询1:月度销售趋势 SELECT -- 查询每月销售总额 d.[Year], -- 年 d.[Month], -- 月 SUM(f.SalesAmount) AS TotalSales -- 月销售汇总 FROM dbo.FactSales f -- 来源:销售事实表 JOIN dbo.DimDate d ON f.DateKey = d.DateKey -- 连接日期维用于按年月分组 GROUP BY d.[Year], d.[Month] -- 按年、月聚合 ORDER BY d.[Year], d.[Month]; -- 按时间顺序输出 -- 示例查询2:区域销售排行 SELECT TOP 10 -- 取销售额前 10 的区域 t.CountryRegion, -- 国家/地区 t.TerritoryName, -- 区域名称 SUM(f.SalesAmount) AS TotalSales -- 区域销售汇总 FROM dbo.FactSales f -- 来源:销售事实表 JOIN dbo.DimTerritory t ON f.TerritoryKey = t.TerritoryKey -- 连接区域维获取区域属性 GROUP BY t.CountryRegion, t.TerritoryName -- 按国家/区域分组 ORDER BY TotalSales DESC; -- 按销售额降序排名 GO -- 结束当前批次并提交执行

步骤7:增量刷新(技术点 5.23)

USE AdventureWorksDW_Lite; -- 切换当前上下文到数据仓库数据库 GO -- 结束当前批次并提交执行 DECLARE @LastSuccessTime DATETIME2 = ( -- 读取历史成功批次的最近结束时间 SELECT MAX(EndTime) -- 取最大结束时间作为增量水位 FROM dbo.ETLBatchLog -- 来源:ETL 批次日志表 WHERE Status = N'SUCCESS' -- 仅统计成功批次 ); -- 结束子查询赋值 IF @LastSuccessTime IS NULL -- 若从未成功装载过(无水位) SET @LastSuccessTime = '1900-01-01'; -- 回退到极早时间,保证首次增量等同全量 DECLARE @IncBatchStart DATETIME2 = SYSDATETIME(); -- 记录当前增量批次开始时间 INSERT INTO dbo.ETLBatchLog (BatchType, StartTime, LastLoadTime, Status) -- 写入一条增量运行中日志 VALUES (N'INCREMENTAL', @IncBatchStart, @LastSuccessTime, N'RUNNING'); -- 标记增量批次与上次水位 DECLARE @IncBatchID INT = SCOPE_IDENTITY(); -- 获取刚插入日志行的批次 ID BEGIN TRY -- 开始异常捕获块(成功路径) ;WITH src AS ( -- 构造增量源数据集 SELECT -- 选择增量装载所需字段 sod.SalesOrderID, -- 销售订单号 sod.SalesOrderDetailID, -- 销售订单明细号 CONVERT(INT, FORMAT(soh.OrderDate, 'yyyyMMdd')) AS DateKey, -- 订单日期转日期键 dp.ProductKey, -- 映射产品代理键 dc.CustomerKey, -- 映射客户代理键 dt.TerritoryKey, -- 映射区域代理键 sod.OrderQty, -- 订购数量 sod.UnitPrice, -- 单价 sod.UnitPriceDiscount, -- 单价折扣率 sod.LineTotal AS SalesAmount, -- 行总额作为销售金额 sod.UnitPrice * sod.OrderQty * sod.UnitPriceDiscount AS DiscountAmount, -- 按公式计算折扣金额 sod.ModifiedDate -- 来源修改时间 FROM AdventureWorks2019.Sales.SalesOrderDetail sod -- 主事实来源:订单明细 JOIN AdventureWorks2019.Sales.SalesOrderHeader soh -- 连接订单头用于获取订单信息 ON sod.SalesOrderID = soh.SalesOrderID -- 按订单号关联 JOIN dbo.DimProduct dp -- 连接产品维映射 ProductKey ON sod.ProductID = dp.ProductID -- 按产品业务键关联 JOIN dbo.DimCustomer dc -- 连接客户维映射 CustomerKey ON soh.CustomerID = dc.CustomerID -- 按客户业务键关联 JOIN dbo.DimTerritory dt -- 连接区域维映射 TerritoryKey ON soh.TerritoryID = dt.TerritoryID -- 按区域业务键关联 WHERE sod.ModifiedDate > @LastSuccessTime -- 过滤明细表中新变更数据 OR soh.ModifiedDate > @LastSuccessTime -- 或订单头表中新变更数据 ) -- 结束增量源 CTE 定义 MERGE dbo.FactSales AS tgt -- 将增量源合并到目标事实表 USING src -- 使用 CTE 源数据 ON tgt.SalesOrderID = src.SalesOrderID -- 匹配条件1:订单号 AND tgt.SalesOrderDetailID = src.SalesOrderDetailID -- 匹配条件2:订单明细号 WHEN MATCHED THEN -- 已存在则执行更新 UPDATE SET -- 更新目标事实记录 tgt.DateKey = src.DateKey, -- 更新日期键 tgt.ProductKey = src.ProductKey, -- 更新产品键 tgt.CustomerKey = src.CustomerKey, -- 更新客户键 tgt.TerritoryKey = src.TerritoryKey, -- 更新区域键 tgt.OrderQty = src.OrderQty, -- 更新数量 tgt.UnitPrice = src.UnitPrice, -- 更新单价 tgt.UnitPriceDiscount = src.UnitPriceDiscount, -- 更新折扣率 tgt.SalesAmount = src.SalesAmount, -- 更新销售金额 tgt.DiscountAmount = src.DiscountAmount, -- 更新折扣金额 tgt.ModifiedDate = src.ModifiedDate -- 更新修改时间 WHEN NOT MATCHED THEN -- 不存在则执行插入 INSERT ( -- 指定新插入列 SalesOrderID, SalesOrderDetailID, DateKey, ProductKey, CustomerKey, TerritoryKey, -- 业务键与维外键列 OrderQty, UnitPrice, UnitPriceDiscount, SalesAmount, DiscountAmount, ModifiedDate -- 度量值与修改时间列 ) -- 结束插入列定义 VALUES ( -- 填充源数据对应值 src.SalesOrderID, src.SalesOrderDetailID, src.DateKey, src.ProductKey, src.CustomerKey, src.TerritoryKey, -- 业务键与维外键值 src.OrderQty, src.UnitPrice, src.UnitPriceDiscount, src.SalesAmount, src.DiscountAmount, src.ModifiedDate -- 度量值与修改时间值 ); -- 结束 MERGE 语句 UPDATE dbo.ETLBatchLog -- 更新当前增量批次日志为成功 SET EndTime = SYSDATETIME(), -- 记录结束时间 RowsInserted = @@ROWCOUNT, -- 记录上一条 DML 影响行数 Status = N'SUCCESS' -- 状态置为成功 WHERE BatchID = @IncBatchID; -- 仅更新当前批次 END TRY -- 成功路径结束 BEGIN CATCH -- 异常路径开始 UPDATE dbo.ETLBatchLog -- 更新当前增量批次日志为失败 SET EndTime = SYSDATETIME(), -- 记录失败结束时间 Status = N'FAILED', -- 状态置为失败 ErrorMessage = ERROR_MESSAGE() -- 写入错误信息 WHERE BatchID = @IncBatchID; -- 仅更新当前批次 THROW; -- 将错误继续抛出给上层 END CATCH; -- 异常路径结束 GO -- 结束当前批次并提交执行