【问题标题】:A practical example of denormalization in a SQL database?SQL 数据库中非规范化的实际示例?
【发布时间】:2020-03-22 08:49:36
【问题描述】:

在过去的 20 分钟里,我一直在阅读有关非规范化的内容,但无法获得一个简洁的代码示例。

这就是非规范化吗?


1. 我们有一个标准化的数据库:

表_1:
customer_id(主键)
国家
城市
街道
门牌号

表_2:
product_id(主键)
customer_id(外键)
product_storage_building

表_3:
product_id(外键)
产品名称
产品颜色
product_origin

  1. 但是,假设加入所有三个表需要很长时间才能运行

        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_1Table_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
    )
  1. 然后加入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
    

现在这将使我的查询运行得更快 - 可以将上述整个过程描述为非规范化吗?

谢谢

【问题讨论】:

  • 顺便说一句,您很可能应该在表 2 和表 3 中为 product_id 交换键标签
  • @pentium10 - 虽然键标签的存在可能表明该问题与 BigQuery 无关(因为它没有索引) - 另一方面,反规范化是一个与 BigQuery 类似的话题。我会把这个标签加回来!
  • 您好,基本上我是想在脑海中了解非规范化的概念。这些表只是表模式的说明。非规范化本质上是否只是创建一个新的数据表来减少复杂连接的工作量?
  • @cget 你运行什么数据库引擎?
  • 我使用 BigQuery,标准 SQL

标签: sql google-bigquery denormalization


【解决方案1】:

请看下面的图片。顶部包含几个不同的表格,这些表格封装了逻辑上独立的信息位。底部显示了这些表连接在一起的结果。这是非规范化。

对于 BigQuery,尤其是使用 BQ 作为 BI 平台的后端,非规范化数据提供了更快的用户体验,因为它不必在用户点击“运行”时进行连接。

如果您按原样保留表,如果用户需要多个字段,您最终可能会进行多达 7 次连接,然后进行聚合(总和、计数等)。但是,如果您执行所有 7 个连接并将其存储在 1 个表中,那么用户将只查询 1 个表并且只进行聚合。这就是 BigQuery 的强大之处。它是可扩展的,因此与连接相比,对大量数据列进行分组和聚合相对“容易”,从而使最终用户体验更快。

朝这个方向发展的人员/公司通常在 ETL 流程中执行此操作(通常是一夜之间),因此连接只需发生 1 次(当用户通常不使用数据库时),然后在白天,用户和 BI工具只是在没有连接的情况下聚合和切片数据!这确实会导致“冗余”数据并产生额外的存储成本,但对于下游用户体验来说通常是值得的

【讨论】:

    【解决方案2】:

    以下是 BigQuery 的具体答案!

    当您的数据非规范化时,BigQuery 的效果最佳。您可以通过非规范化数据并利用嵌套和重复字段来提高性能,而不是保留星型或雪花模式等关系模式。嵌套和重复的字段可以保持关系,而不会影响保留关系(规范化)模式的性能。

    标准化数据所节省的存储空间在现代系统中已不再那么重要。存储成本的增加值得通过非规范化数据获得性能提升。连接需要数据协调(通信带宽)。非规范化将数据本地化到各个插槽,因此可以并行执行。

    如果您需要在非规范化数据的同时维护关系,请使用嵌套和重复的字段,而不是完全扁平化您的数据。当关系数据完全扁平化时,网络通信(洗牌)会对查询性能产生负面影响。

    例如,在不使用嵌套和重复字段的情况下对订单模式进行非规范化可能需要您按诸如 order_id 之类的字段进行分组(当存在一对多关系时)。由于涉及改组,对数据进行分组的性能​​不如使用嵌套和重复字段对数据进行非规范化。

    注意:在某些情况下,对数据进行非规范化以及使用嵌套和重复字段可能不会提高性能。

    您可以在 BigQuery 文档的 Denormalize data whenever possible 部分查看更多信息

    最后:BigQuery 不需要完全平坦的非规范化。您可以使用嵌套和重复字段来维护关系。

    以下是从问题中最初的三个规范化表中生成非规范化表的示例

    #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)
    

    这将生成具有以下架构的表

    显然,这是更以客户为中心的架构。根据您的需求,您可以类似地创建以产品为中心的产品。或者实际上两者并根据用例适当使用

    你可以像下面的例子一样使用虚拟数据测试,玩上面的例子

    #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)    
    

    有输出

    【讨论】:

      【解决方案3】:

      是的,您正在展示一种类型的非规范化。

      反规范化分为三种类型:

      • 连接来自不同表的行,因此您不必使用带有JOIN 的查询。
      • 执行聚合计算,例如 SUM()COUNT()MAX() 或其他,因此您不必使用带有 GROUP BY 的查询。
      • 预先计算昂贵的计算,因此您不必在选择列表中使用具有复杂表达式的查询。

      您正在展示第一种类型的示例。至少你可以避免你打算做的两个连接之一。

      为什么不让非规范化表存储连接所有三个表的结果?

      使用非规范化有什么缺点?您现在正在冗余存储数据:一次在规范化表中,一个副本在非规范化表中。假设您明天开始工作,发现这些不同表中的数据并不完全匹配。发生了什么?

      • 可能有人插入行到规范化表中,而没有将相应的数据添加到非规范化表中。
      • 也许有人删除了规范化表中的一行,而没有从非规范化表中删除相应的行。
      • 可能有人在非规范化表中插入或删除了一行,而规范化表中没有相应的更改。

      你怎么知道发生了什么?哪个表是“正确的”?这就是非规范化的风险。

      【讨论】:

        【解决方案4】:

        是的,这就是规范化的基础:为重复数据提供单独的表,并使用外键来引用新表的主键。我可能会使用 CREATE VIEW 而不是 CREATE TABLE 进行查询,但一般来说,使用视图而不是表来获取只读数据会更好。我可能会创建一个这样的视图:

        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;
        

        【讨论】:

        • FWIW,使用视图不会使查询更快,这听起来像是 OP 的目标。
        • 我不习惯看到 CREATE TABLE AS ... SELECT 并认为这可能是一个错误。还是那是 BigQuery 的事情? “更好”是指让数据库执行连接而不是使用原始表并指定连接的代码以编程方式更好。当然,使用视图不会使您的查询运行得更快,除非原始表有一些导致更多获取的关联。
        • 所有流行的 SQL 实现都支持 CREATE TABLE AS ... SELECT。我检查了 MySQL、PostgreSQL、SQLite、Oracle、Microsoft 和 IBM DB2,它们都支持它。我还在 ISO SQL 2011 标准的副本中找到了这种语法。
        • CREATE VIEW 可能有助于在临时场景(想想分析师或数据科学家)编写查询。视图将阻止他们在每次编写查询时都必须写出连接。 CREATE TABLE 在 ETL 等场景中提供更多帮助,您需要具体化一个表以进行重复查询,例如 Looker、Tableau 等的后端,它有许多用户都在查询相同的数据集。预连接表的执行速度要快得多。为此目的使用视图仍然会在每次用户查询数据时执行所有连接。
        猜你喜欢
        • 2012-11-18
        • 2014-12-15
        • 1970-01-01
        • 2016-07-23
        • 2013-01-18
        • 1970-01-01
        • 2011-11-17
        • 1970-01-01
        • 2017-10-05
        相关资源
        最近更新 更多