说明:以下脚本在 SQL Server Management Studio(SSMS)中执行。请根据你本机的 .bak 文件路径调整 MOVE 和文件名。
-- 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 -- 结束当前批次并提交执行
-- 创建独立的数据仓库库
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 -- 结束当前批次并提交执行
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 -- 结束当前批次并提交执行
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 -- 结束当前批次并提交执行
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 -- 结束当前批次并提交执行
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 -- 结束当前批次并提交执行
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 -- 结束当前批次并提交执行