查询数据
数据仓库中的维度和事实数据表填充数据后,可以使用 T-SQL 来查询和分析它们。 T-SQL 支持 SELECT、 JOIN聚合函数、窗口函数等 - 提供用于提取、筛选、分组和汇总数据的工具。
按维度属性聚合度量值
常见的模式是将事实数据表联接到一个或多个维度表,然后聚合按维度属性分组的数字度量值。
以下查询根据 FactSales 和 DimDate 表按年份和季度聚合销售额:
SELECT dates.CalendarYear,
dates.CalendarQuarter,
SUM(sales.SalesAmount) AS TotalSales
FROM dbo.FactSales AS sales
JOIN dbo.DimDate AS dates ON sales.OrderDateKey = dates.DateKey
GROUP BY dates.CalendarYear, dates.CalendarQuarter
ORDER BY dates.CalendarYear, dates.CalendarQuarter;
结果类似于下表:
| 日历年 | CalendarQuarter | 总销售额 |
|---|---|---|
| 2024 | 1 | 25980.16 |
| 2024 | 2 | 27453.87 |
| 2024 | 3 | 28527.15 |
| 2024 | 4 | 31083.45 |
| 2025 | 1 | 34562.96 |
| 2025 | 2 | 36162.27 |
| ... | ... | ... |
可以将多个维度表连接起来,以便通过其他属性对结果进行切片。 以下查询扩展了前面的示例,以使用 DimCustomer 表按城市细分季度销售额:
SELECT dates.CalendarYear,
dates.CalendarQuarter,
custs.City,
SUM(sales.SalesAmount) AS TotalSales
FROM dbo.FactSales AS sales
JOIN dbo.DimDate AS dates ON sales.OrderDateKey = dates.DateKey
JOIN dbo.DimCustomer AS custs ON sales.CustomerKey = custs.CustomerKey
GROUP BY dates.CalendarYear, dates.CalendarQuarter, custs.City
ORDER BY dates.CalendarYear, dates.CalendarQuarter, custs.City;
这次的结果包括每个城市的季度销售额。
| 日历年 | CalendarQuarter | 城市 | 总销售额 |
|---|---|---|---|
| 2024 | 1 | 阿姆斯特丹 | 5982.53 |
| 2024 | 1 | 柏林 | 2826.98 |
| 2024 | 1 | 芝加哥 | 5372.72 |
| ... | ... | ... | .. |
| 2024 | 2 | 阿姆斯特丹 | 7163.93 |
| 2024 | 2 | 柏林 | 8191.12 |
| 2024 | 2 | 芝加哥 | 2428.72 |
| ... | ... | ... | .. |
| 2024 | 3 | 阿姆斯特丹 | 7261.92 |
| 2024 | 3 | 柏林 | 4202.65 |
| 2024 | 3 | 芝加哥 | 2287.87 |
| ... | ... | ... | .. |
| 2024 | 4 | 阿姆斯特丹 | 8262.73 |
| 2024 | 4 | 柏林 | 5373.61 |
| 2024 | 4 | 芝加哥 | 7726.23 |
| ... | ... | ... | .. |
| 2025 | 1 | 阿姆斯特丹 | 7261.28 |
| 2025 | 1 | 柏林 | 3648.28 |
| 2025 | 1 | 芝加哥 | 1027.27 |
| ... | ... | ... | .. |
雪花型架构中的联接
使用雪花架构时,维度可能部分规范化,要求多个联接将事实数据表与雪花维度相关联。 例如,假设你的数据仓库包含一个 DimProduct 维度表,其中的产品类别已经被规范化为一个单独的 DimCategory 表。 按产品类别聚合已销售商品的查询可能类似于以下示例:
SELECT cat.ProductCategory,
SUM(sales.OrderQuantity) AS ItemsSold
FROM dbo.FactSales AS sales
JOIN dbo.DimProduct AS prod ON sales.ProductKey = prod.ProductKey
JOIN dbo.DimCategory AS cat ON prod.CategoryKey = cat.CategoryKey
GROUP BY cat.ProductCategory
ORDER BY cat.ProductCategory;
此查询的结果包括每个产品类别销售的商品数:
| 产品类别 | 售出商品 |
|---|---|
| 配件 | 28271 |
| 服装 | 5368 |
| ... | ... |
注意
这两个JOIN子句都需要遍历关系链,从FactSales经过DimProduct到DimCategory,即使结果中没有显示DimProduct中的字段。
使用排名函数
另一个常见的分析查询按维度属性对结果进行分区,并在每个分区中对其进行排名。 例如,你可能需要按销售收入每年对商店进行排名。 为了实现此目标,可以使用 Transact-SQL 排名函数,例如 、ROW_NUMBER、RANK 和 DENSE_RANK。NTILE 这些函数使你可以跨类别对数据进行分区,每个类别返回一个值,该值指示每个行在分区中的相对位置:
- ROW_NUMBER:返回行在分区中的序号位置。 例如,第一行的编号为 1,第二行的编号为 2,以此类推。
- RANK:返回排序结果中每行的排名位置。 例如,在按销售量排序的商店分区中,销售量最高的商店排名为 1。 如果多个商店的销售量相同,那么它们的排名也相同,分配给后续商店的排名反映了具有较高销售量(包括并列)的商店数。
- DENSE_RANK 按与 RANK 相同的方式对分区中的行进行排名,但当多个行具有相同的排名时,后续行的排名没有差距 ,因此关系不会占用额外的位置。
- NTILE:返回行所在的指定百分位数。 例如,在按销售量排序的商店分区中,
NTILE(4)将返回一个商店的销售量所在的四分位数。
例如,考虑以下查询:
SELECT ProductCategory,
ProductName,
ListPrice,
ROW_NUMBER() OVER
(PARTITION BY ProductCategory ORDER BY ListPrice DESC) AS RowNumber,
RANK() OVER
(PARTITION BY ProductCategory ORDER BY ListPrice DESC) AS Rank,
DENSE_RANK() OVER
(PARTITION BY ProductCategory ORDER BY ListPrice DESC) AS DenseRank,
NTILE(4) OVER
(PARTITION BY ProductCategory ORDER BY ListPrice DESC) AS Quartile
FROM dbo.DimProduct
ORDER BY ProductCategory;
查询按类别对产品进行分区,按标价对其分区中的每个产品进行排名。 结果类似于下表:
| 产品类别 | ProductName | ListPrice | 行号 | 排名 | DenseRank | Quartile |
|---|---|---|---|---|---|---|
| 配件 | 水瓶 | 8.99 | 1 | 1 | 1 | 1 |
| 配件 | 头巾 | 8.49 | 2 | 2 | 2 | 1 |
| 配件 | 臂暖器 | 5.99 | 3 | 3 | 3 | 2 |
| 配件 | 脚踝带 | 5.99 | 4 | 3 | 3 | 2 |
| 配件 | 鞋垫 | 2.99 | 5 | 5 | 4 | 3 |
| 配件 | 鞋带 | 0.25 | 6 | 6 | 5 | 4 |
| 服装 | 运行容量上限 | 7.49 | 1 | 1 | 1 | 1 |
| 服装 | 压缩护袖 | 6.99 | 2 | 2 | 2 | 1 |
| 服装 | 运动包 | 4.25 | 3 | 3 | 3 | 2 |
| ... | ... | ... | ... | ... | ... | ... |
注意
示例结果显示了 RANK 和 DENSE_RANK 之间的差异。 请注意,在配件类别中,臂暖器和脚踝带产品具有相同的标价,并且均排名第三高的产品。 下一个高价产品的 RANK 为 5(有四个产品比它贵),DENSE_RANK 为 4(有三个产品的价格比它高)。
若要了解有关排名函数的详细信息,请参阅在 Transact-SQL 中使用内置函数和 GROUP BY 模块。
检索近似计数
虽然数据仓库的目的是主要支持企业的分析数据模型和报表,但数据分析师和数据科学家通常需要执行一些初始数据探索来确定数据的基本规模和分布。
例如,以下查询使用 COUNT 函数检索每年的销售额:
SELECT dates.CalendarYear AS CalendarYear,
COUNT(DISTINCT sales.OrderNumber) AS Orders
FROM FactSales AS sales
JOIN DimDate AS dates ON sales.OrderDateKey = dates.DateKey
GROUP BY dates.CalendarYear
ORDER BY CalendarYear;
此查询的结果可能与下表中的内容类似:
| 日历年 | 订单 |
|---|---|
| 2023 | 239870 |
| 2024 | 284741 |
| 2025 | 309272 |
| ... | ... |
数据仓库中的数据量可能意味着即使是简单的查询来计算满足指定条件的记录数,也需花费相当长的时间才能运行。 在许多情况下,不需要精确计数 - 近似估计就足够了。 在这类情况下,可以使用 APPROX_COUNT_DISTINCT 函数,如以下示例中所示:
SELECT dates.CalendarYear AS CalendarYear,
APPROX_COUNT_DISTINCT(sales.OrderNumber) AS ApproxOrders
FROM FactSales AS sales
JOIN DimDate AS dates ON sales.OrderDateKey = dates.DateKey
GROUP BY dates.CalendarYear
ORDER BY CalendarYear;
APPROX_COUNT_DISTINCT 函数使用 HyperLogLog 算法来检索近似计数。 结果保证最大误差率为 2%,概率为 97%,因此此查询的结果可能类似于下表:
| 日历年 | 近似排序 |
|---|---|
| 2023 | 235552 |
| 2024 | 290436 |
| 2025 | 304633 |
| ... | ... |
计数不是那么准确,但用来大致比较年度销售额已经足够了。 在大量数据的情况下,使用 APPROX_COUNT_DISTINCT 函数的查询可以更快地完成,而且在基本数据探索过程中,准确性降低可能是一个可接受的折衷。
注意
有关更多详细信息,请参阅 APPROX_COUNT_DISTINCT 函数文档。