SQL 数据库中反规范化的实际例子?
A practical example of denormalization in a SQL database?
过去 20 分钟我一直在阅读有关反规范化的内容,但无法获得带有代码的简明示例。
这就是反规范化吗?
1.我们有一个规范化的数据库:
Table_1:
customer_id(主键)
国家
城市
街道
house_number
Table_2:
product_id(主键)
customer_id(外键)
product_storage_building
Table_3:
product_id(外键)
product_name
product_color
product_origin
但是,加入所有三个 table 花费的时间太长 运行 让我们说
SELECT a.*, b.*, c.*
FROM
TABLE_1 AS a
LEFT JOIN TABLE_2 AS b
ON a.customer_id = b.customer_id
LEFT JOIN TABLE_3 AS c
ON b.product_id = c.product_id
所以我从 Table_1
和 Table_2
中创建了一个新的 table
CREATE OR REPLACE TABLE Denormalized_Data AS
(
SELECT customer_id,
country,
city,
street,
house_number,
product_id,
product_storage_building
FROM Table_1
LEFT JOIN Table_2
ON Table_1.cusomter_id = Table_2.customer_id
)
然后加入Table_3如下
SELECT customer_id,
country,
city,
street,
house_number,
product_storage_building,
Denormalized_Data.product_id
product_name,
product_color,
FROM Denormalized_Data
LEFT JOIN Table_3
ON Denormalized_Data.product_id = Table_3.product_id
现在这将使我的查询 运行 更快 - 上面的整个过程可以描述为反规范化吗?
谢谢
是的,您展示了一种反规范化。
反规范化分为三种类型:
- 连接来自不同 table 的行,因此您不必使用带有
JOIN
的查询。
- 执行聚合计算,如
SUM()
或 COUNT()
或 MAX()
或其他,因此您不必使用 GROUP BY
. 的查询
- 预先计算昂贵的计算,因此您不必在 select-列表中使用复杂表达式的查询。
您展示的是第一种类型的示例。至少你可以避免你打算做的两个连接之一。
为什么不让非规范化 table 存储连接所有三个 table 的结果?
使用非规范化的缺点是什么?您现在正在冗余地存储数据:一次在规范化 table 中,一个副本在非规范化 table 中。假设你明天上班,发现这些不同的 table 中的数据并不完全匹配。发生什么事了?
- 也许有人 向规范化 table 插入了一行 而没有将相应的数据添加到非规范化 table。
- 也许有人 删除了 规范化 table 中的一行,而没有从非规范化 table 中删除相应的行。
- 也许有人在非规范化 table 中插入或删除了一行,但在规范化 table 中没有相应的更改。
你怎么知道发生了什么事?哪个 table 是 "correct"?这就是反规范化的风险。
是的,这就是规范化的基础:为重复数据提供单独的 table 并使用外键引用新的 table 的主键。我可能会使用 CREATE VIEW 而不是 CREATE TABLE 进行查询,但一般来说,使用视图而不是 tables 来获取只读数据更好。我可能会创建这样的视图:
CREATE VIEW view_2 AS
SELECT ...
FROM table_2 t2
LEFT JOIN table_1 t1 ON t1.customer_id = t2.customer_id
LEFT JOIN table_3 t3 ON t3.product_id = t2.product_id;
考虑下图。顶部包含几个不同的 tables,它们封装了逻辑上独立的信息位。底部显示了那些 table 连接在一起的结果。这就是反规范化。
就 BigQuery 而言,尤其是使用 BQ 作为 BI 平台的后端时,非规范化数据提供了更快的用户体验,因为它不必在用户点击时进行连接 'run' .
如果您按原样保留 tables,如果用户需要多个字段,您最终可能会进行最多 7 次连接,然后进行聚合(求和、计数等)。但是,如果您进行所有 7 次连接并将其存储在 1 table 中,那么用户将只查询 1 table 并且只进行聚合。这就是 BigQuery 的强大之处。它是可扩展的,因此与联接相比,对大量数据进行分组和聚合相对 'easy',从而使最终用户体验更快。
People/companies 通常在 ETL 过程中(通常在一夜之间)朝这个方向进行,因此连接只需发生 1 次(当用户通常不使用数据库时),然后在白天进行,用户和 BI 工具只是在没有连接的情况下聚合和切片数据!这确实会产生 'redundant' 数据并产生额外的存储成本,但对于下游用户体验来说通常是值得的
下面是BigQuery的具体答案!
当您的数据被非规范化时,BigQuery 的性能最佳。您可以通过非规范化数据并利用嵌套和重复字段来提高性能,而不是保留星型或雪花型等关系模式。嵌套和重复的字段可以维护关系,而不会因保留关系(规范化)模式而对性能产生影响。
在现代系统中,规范化数据节省的存储空间已不再是一个大问题。存储成本的增加值得通过反规范化数据获得性能提升。加入需要数据协调(通信带宽)。非规范化将数据本地化到单独的槽中,因此可以并行执行。
如果您需要在对数据进行反规范化的同时保持关系,请使用嵌套和重复的字段,而不是完全扁平化数据。当关系数据完全扁平化时,网络通信(洗牌)会对查询性能产生负面影响。
例如,在不使用嵌套和重复字段的情况下对订单模式进行非规范化可能需要您按 order_id 这样的字段进行分组(当存在一对多关系时)。由于涉及改组,对数据进行分组的性能不如使用嵌套和重复字段对数据进行非规范化。
注意:在某些情况下,对数据进行非规范化以及使用嵌套和重复字段可能不会提高性能。
您可以在 BigQuery 文档的 Denormalize data whenever possible 部分看到更多信息
最后:BigQuery 不需要完全平坦的非规范化。您可以使用嵌套和重复的字段来维护关系。
下面是一个在你的问题
中从最初的三个规范化 table 中产生非规范化 table 的例子
#standardSQL
SELECT ANY_VALUE(c).*,
ARRAY_AGG((SELECT AS STRUCT p.*, s.product_storage_building)) products
FROM `project.dataset.customers` c
LEFT JOIN `project.dataset.storage` s USING (customer_id)
LEFT JOIN `project.dataset.products` p USING (product_id)
GROUP BY FORMAT('%t', c)
这将生成具有以下架构的 table
显然,这是更加以客户为中心的模式。根据您的需要,您可以类似地创建以产品为中心的产品。或者实际上两者并根据用例使用适当的
你可以像下面的例子那样使用虚拟数据来测试和玩上面的游戏
#standardSQL
WITH `project.dataset.customers` AS (
SELECT 1 customer_id, 'country 1' country, 'city 1' city, 'street 1' street, 1 house_number UNION ALL
SELECT 2, 'country 1', 'city 2', 'street 2', 2 UNION ALL
SELECT 3, 'country 1', 'city 3', 'street 3', 3 UNION ALL
SELECT 4, 'country 2', 'city 4', 'street 4', 4 UNION ALL
SELECT 5, 'country 2', 'city 5', 'street 5', 5
), `project.dataset.products` AS (
SELECT 1 product_id, 'product 1' product_name, 'color 1' product_color, 'origin 1' product_origin UNION ALL
SELECT 2, 'product 2', 'color 2', 'origin 2' UNION ALL
SELECT 3, 'product 3', 'color 3', 'origin 3' UNION ALL
SELECT 4, 'product 4', 'color 4', 'origin 4'
), `project.dataset.storage` AS (
SELECT 1 product_id, 1 customer_id, 'building 1' product_storage_building UNION ALL
SELECT 2, 1, 'building 1' UNION ALL
SELECT 3, 1, 'building 1' UNION ALL
SELECT 2, 2, 'building 2' UNION ALL
SELECT 3, 2, 'building 3' UNION ALL
SELECT 4, 2, 'building 3' UNION ALL
SELECT 1, 3, 'building 1' UNION ALL
SELECT 3, 3, 'building 1'
)
SELECT ANY_VALUE(c).*,
ARRAY_AGG((SELECT AS STRUCT p.*, s.product_storage_building)) products
FROM `project.dataset.customers` c
LEFT JOIN `project.dataset.storage` s USING (customer_id)
LEFT JOIN `project.dataset.products` p USING (product_id)
GROUP BY FORMAT('%t', c)
输出
过去 20 分钟我一直在阅读有关反规范化的内容,但无法获得带有代码的简明示例。
这就是反规范化吗?
1.我们有一个规范化的数据库:
Table_1:
customer_id(主键)
国家
城市
街道
house_number
Table_2:
product_id(主键)
customer_id(外键)
product_storage_building
Table_3:
product_id(外键)
product_name
product_color
product_origin
但是,加入所有三个 table 花费的时间太长 运行 让我们说
SELECT a.*, b.*, c.* FROM TABLE_1 AS a LEFT JOIN TABLE_2 AS b ON a.customer_id = b.customer_id LEFT JOIN TABLE_3 AS c ON b.product_id = c.product_id
所以我从 Table_1
和 Table_2
CREATE OR REPLACE TABLE Denormalized_Data AS
(
SELECT customer_id,
country,
city,
street,
house_number,
product_id,
product_storage_building
FROM Table_1
LEFT JOIN Table_2
ON Table_1.cusomter_id = Table_2.customer_id
)
然后加入Table_3如下
SELECT customer_id, country, city, street, house_number, product_storage_building, Denormalized_Data.product_id product_name, product_color, FROM Denormalized_Data LEFT JOIN Table_3 ON Denormalized_Data.product_id = Table_3.product_id
现在这将使我的查询 运行 更快 - 上面的整个过程可以描述为反规范化吗?
谢谢
是的,您展示了一种反规范化。
反规范化分为三种类型:
- 连接来自不同 table 的行,因此您不必使用带有
JOIN
的查询。 - 执行聚合计算,如
SUM()
或COUNT()
或MAX()
或其他,因此您不必使用GROUP BY
. 的查询
- 预先计算昂贵的计算,因此您不必在 select-列表中使用复杂表达式的查询。
您展示的是第一种类型的示例。至少你可以避免你打算做的两个连接之一。
为什么不让非规范化 table 存储连接所有三个 table 的结果?
使用非规范化的缺点是什么?您现在正在冗余地存储数据:一次在规范化 table 中,一个副本在非规范化 table 中。假设你明天上班,发现这些不同的 table 中的数据并不完全匹配。发生什么事了?
- 也许有人 向规范化 table 插入了一行 而没有将相应的数据添加到非规范化 table。
- 也许有人 删除了 规范化 table 中的一行,而没有从非规范化 table 中删除相应的行。
- 也许有人在非规范化 table 中插入或删除了一行,但在规范化 table 中没有相应的更改。
你怎么知道发生了什么事?哪个 table 是 "correct"?这就是反规范化的风险。
是的,这就是规范化的基础:为重复数据提供单独的 table 并使用外键引用新的 table 的主键。我可能会使用 CREATE VIEW 而不是 CREATE TABLE 进行查询,但一般来说,使用视图而不是 tables 来获取只读数据更好。我可能会创建这样的视图:
CREATE VIEW view_2 AS
SELECT ...
FROM table_2 t2
LEFT JOIN table_1 t1 ON t1.customer_id = t2.customer_id
LEFT JOIN table_3 t3 ON t3.product_id = t2.product_id;
考虑下图。顶部包含几个不同的 tables,它们封装了逻辑上独立的信息位。底部显示了那些 table 连接在一起的结果。这就是反规范化。
就 BigQuery 而言,尤其是使用 BQ 作为 BI 平台的后端时,非规范化数据提供了更快的用户体验,因为它不必在用户点击时进行连接 'run' .
如果您按原样保留 tables,如果用户需要多个字段,您最终可能会进行最多 7 次连接,然后进行聚合(求和、计数等)。但是,如果您进行所有 7 次连接并将其存储在 1 table 中,那么用户将只查询 1 table 并且只进行聚合。这就是 BigQuery 的强大之处。它是可扩展的,因此与联接相比,对大量数据进行分组和聚合相对 'easy',从而使最终用户体验更快。
People/companies 通常在 ETL 过程中(通常在一夜之间)朝这个方向进行,因此连接只需发生 1 次(当用户通常不使用数据库时),然后在白天进行,用户和 BI 工具只是在没有连接的情况下聚合和切片数据!这确实会产生 'redundant' 数据并产生额外的存储成本,但对于下游用户体验来说通常是值得的
下面是BigQuery的具体答案!
当您的数据被非规范化时,BigQuery 的性能最佳。您可以通过非规范化数据并利用嵌套和重复字段来提高性能,而不是保留星型或雪花型等关系模式。嵌套和重复的字段可以维护关系,而不会因保留关系(规范化)模式而对性能产生影响。
在现代系统中,规范化数据节省的存储空间已不再是一个大问题。存储成本的增加值得通过反规范化数据获得性能提升。加入需要数据协调(通信带宽)。非规范化将数据本地化到单独的槽中,因此可以并行执行。
如果您需要在对数据进行反规范化的同时保持关系,请使用嵌套和重复的字段,而不是完全扁平化数据。当关系数据完全扁平化时,网络通信(洗牌)会对查询性能产生负面影响。
例如,在不使用嵌套和重复字段的情况下对订单模式进行非规范化可能需要您按 order_id 这样的字段进行分组(当存在一对多关系时)。由于涉及改组,对数据进行分组的性能不如使用嵌套和重复字段对数据进行非规范化。
注意:在某些情况下,对数据进行非规范化以及使用嵌套和重复字段可能不会提高性能。
您可以在 BigQuery 文档的 Denormalize data whenever possible 部分看到更多信息
最后:BigQuery 不需要完全平坦的非规范化。您可以使用嵌套和重复的字段来维护关系。
下面是一个在你的问题
中从最初的三个规范化 table 中产生非规范化 table 的例子#standardSQL
SELECT ANY_VALUE(c).*,
ARRAY_AGG((SELECT AS STRUCT p.*, s.product_storage_building)) products
FROM `project.dataset.customers` c
LEFT JOIN `project.dataset.storage` s USING (customer_id)
LEFT JOIN `project.dataset.products` p USING (product_id)
GROUP BY FORMAT('%t', c)
这将生成具有以下架构的 table
显然,这是更加以客户为中心的模式。根据您的需要,您可以类似地创建以产品为中心的产品。或者实际上两者并根据用例使用适当的
你可以像下面的例子那样使用虚拟数据来测试和玩上面的游戏
#standardSQL
WITH `project.dataset.customers` AS (
SELECT 1 customer_id, 'country 1' country, 'city 1' city, 'street 1' street, 1 house_number UNION ALL
SELECT 2, 'country 1', 'city 2', 'street 2', 2 UNION ALL
SELECT 3, 'country 1', 'city 3', 'street 3', 3 UNION ALL
SELECT 4, 'country 2', 'city 4', 'street 4', 4 UNION ALL
SELECT 5, 'country 2', 'city 5', 'street 5', 5
), `project.dataset.products` AS (
SELECT 1 product_id, 'product 1' product_name, 'color 1' product_color, 'origin 1' product_origin UNION ALL
SELECT 2, 'product 2', 'color 2', 'origin 2' UNION ALL
SELECT 3, 'product 3', 'color 3', 'origin 3' UNION ALL
SELECT 4, 'product 4', 'color 4', 'origin 4'
), `project.dataset.storage` AS (
SELECT 1 product_id, 1 customer_id, 'building 1' product_storage_building UNION ALL
SELECT 2, 1, 'building 1' UNION ALL
SELECT 3, 1, 'building 1' UNION ALL
SELECT 2, 2, 'building 2' UNION ALL
SELECT 3, 2, 'building 3' UNION ALL
SELECT 4, 2, 'building 3' UNION ALL
SELECT 1, 3, 'building 1' UNION ALL
SELECT 3, 3, 'building 1'
)
SELECT ANY_VALUE(c).*,
ARRAY_AGG((SELECT AS STRUCT p.*, s.product_storage_building)) products
FROM `project.dataset.customers` c
LEFT JOIN `project.dataset.storage` s USING (customer_id)
LEFT JOIN `project.dataset.products` p USING (product_id)
GROUP BY FORMAT('%t', c)
输出