了解对称汇总

Looker 中的对称汇总是一项非常强大的功能。不过,由于对称聚合看起来有点令人望而生畏,并且大多是在后台进行的,因此遇到它们时可能会感到有点困惑。本页面提供有关对称聚合的以下信息:

为什么需要对称汇总

SQL 是数据分析的语言,功能非常强大。但能力越强,责任也就越大,而分析人员有责任避免意外计算的汇总值,例如求和、平均值和计数。

错误地执行这些计算是出人意料的,这些类型的错误计算可能会给分析人员带来极大的困扰。以下示例说明了可能会出错的情况。

假设您有两个表:ordersorder_itemsorder_items 表会为订单中的每项记录一行,因此这两个表之间的关系是一对多关系。这是一种一对多关系,因为一个订单可以包含多个商品,但每个商品只能属于一个订单。如需获得有关如何确定联接的正确关系的指导,请参阅正确设置关系参数“最佳做法”页面。

在此示例中,假设 orders 表如下所示:

order_id user_id total order_date
1 100 50.36 美元 2017-12-01
2 101 24.12 美元 2017-12-02
3 137 50.36 美元 2017-12-02

在这个 orders 表中,total 列中值 (SUM(total)) 的值的总和等于 124.84

假设 order_items 表包含六行:

order_id item_id quantity unit_price
1 50 1 23.00 美元
1 63 2 13.68 美元
2 63 1 13.68 美元
2 72 1 5.08 美元
2 79 1 5.36 美元
3 78 1 50.36 美元

获取订购商品数量是很容易的。quantity 列中值的总和 (SUM(quantity)) 是 7

现在,假设您使用 orders 表和 order_items 表的共享列 order_id 进行联接。这会生成以下表格:

order_id user_id total order_date item_id quantity unit_price
1 100 50.36 美元 2017-12-01 50 1 23.00 美元
1 100 50.36 美元 2017-12-01 63 2 13.68 美元
2 101 24.12 美元 2017-12-02 63 1 13.68 美元
2 101 24.12 美元 2017-12-02 72 1 5.08 美元
2 101 24.12 美元 2017-12-02 79 1 5.36 美元
3 137 50.36 美元 2017-12-02 78 1 50.36 美元

上表提供了新信息,例如 12 月 1 日订购了 2 件商品(order_date 列中的 2017-12-01),12 月 2 日订购了 4 件商品(2017-12-02)。之前的一些计算(例如 SUM(quantity) 计算)仍然有效。不过,如果您尝试计算总支出金额,则会遇到问题。

如果您使用之前的计算方式 SUM(total),则新表中 order_id 值为 1 的行对应的总值 50.36 将被统计两次,因为该订单包含两个不同的商品(item_id 值分别为 5063)。order_id 值为 2 的行对应的 24.12 总和将被统计三次,因为此订单包含三个不同的商品。因此,此表格的 SUM(total) 计算结果为 223.44,而不是正确答案 124.84

虽然在使用两个小型示例表时很容易避免这种错误,但在现实生活中,由于拥有大量表和大量数据,解决这个问题会远远复杂得多。这就是人们在没有意识到的情况下可能会出现的错误计算。这就是对称聚合解决的问题。

对称汇总的运作方式

对称汇总可防止分析师和任何其他使用 Looker 的用户意外错误计算总和、平均值和计数等汇总值。对称汇总有助于减轻分析师的负担,因为分析师可以信任用户不会使用不正确的数据。对称汇总通过确保正确统计计算中的每个事实的次数,以及跟踪您正在计算的内容,来实现这一点。

在前面的示例中,对称汇总函数会识别出 totalorders(而非 order_items)的属性,因此它只需统计每个订单的总金额一次,即可得出正确答案。该函数通过使用分析师在 Looker 中定义的唯一主键来实现此目的。这意味着,当 Looker 对联接表进行计算时,它会发现,即使有两行 order_id 的值为 1,也不应将总计数算两次,因为该总计数已包含在计算中,并且对于 order_id 的值为 2 的三行,也应只将总计数算一次。

值得注意的是,对称聚合依赖于唯一 主键以及模型中指定的正确联接关系。因此,如果您得到的结果有误,请与分析师联系,确保所有设置正确无误。

为什么对称汇总看起来很复杂

对称聚合的外观有点神秘。如果不使用对称汇总,Looker 通常会编写良好的 SQL,例如以下示例所示:

SELECT
  order_items.order_id AS "order_items.order_id",
  order_items.sale_price AS "order_items.sale_price"
FROM order_items AS order_items

GROUP BY 1,2
ORDER BY 1
LIMIT 500

使用对称聚合时,SQL Looker 写入内容可能如以下示例所示:

SELECT
  order_items.order_id AS "order_items.order_id",
  order_items.sale_price AS "order_items.sale_price",
  (COALESCE(CAST( ( SUM(DISTINCT (CAST(FLOOR(COALESCE(users.age ,0)
  *(1000000*1.0)) AS DECIMAL(38,0))) +
  CAST(STRTOL(LEFT(MD5(CONVERT(VARCHAR,users.id )),15),16) AS DECIMAL(38,0))
  * 1.0e8 + CAST(STRTOL(RIGHT(MD5(CONVERT(VARCHAR,users.id )),15),16) AS DECIMAL(38,0)) )
  - SUM(DISTINCT CAST(STRTOL(LEFT(MD5(CONVERT(VARCHAR,users.id )),15),16) AS DECIMAL(38,0))
  * 1.0e8 + CAST(STRTOL(RIGHT(MD5(CONVERT(VARCHAR,users.id )),15),16) AS DECIMAL(38,0))) ) AS DOUBLE PRECISION)
  / CAST((1000000*1.0) AS DOUBLE PRECISION), 0)
  / NULLIF(COUNT(DISTINCT CASE WHEN users.age IS NOT NULL THEN users.id
  ELSE NULL END), 0)) AS "users.average_age
FROM order_items AS order_items
LEFT JOIN users AS users ON order_items.user_id = users.id

GROUP BY 1,2
ORDER BY 3 DESC
LIMIT 500

对称聚合采用的具体格式取决于 Looker 编写的 SQL 方言,但所有格式都执行相同的基本操作:如果多行具有相同的主键,则对称聚合函数只会将其统计一次。它通过使用 SQL 标准中鲜为人知的 SUM DISTINCTAVG DISTINCT 函数来实现这一点。

如需了解具体原因,您可以使用对称汇总来完成之前的计算。在联接表中的七个列中,您只需要两个:要汇总的列 (total) 和订单的唯一主键 (order_id)。

order_id total
1 50.36 美元
1 50.36 美元
2 24.12 美元
2 24.12 美元
2 24.12 美元
3 50.26 美元

对称汇总会采用主键(在本例中为 order_id),并为每个主键创建一个非常大的数字,该数字保证是唯一的,并且对于相同的输入,始终会给出相同的输出。(通常,它会使用哈希函数来实现此目的,但具体细节超出了本页的讨论范围。)结果应如下所示:

big_unique_number total
802959190063912 50.36 美元
802959190063912 50.36 美元
917651724816292 24.12 美元
917651724816292 24.12 美元
917651724816292 24.12 美元
110506994770727 50.36 美元

然后,对于每行,Looker 会执行以下操作:

SUM(DISTINCT big_unique_number + total) - SUM(DISTINCT big_unique_number)

这可靠地为您提供正确的汇总总计,对每个总计进行精确的计数次数。Looker 对称汇总函数不会被重复行或总金额相同的多个订单所迷惑。您可以尝试自行计算,以便更好地了解对称聚合的工作原理。

执行此操作所需的 SQL 并非最美观:使用 CAST()md5()SUM(DISTINCT)STRTOL(),您肯定不希望手动编写 SQL。不过,幸运的是,您无需这样做,Looker 可以为您编写 SQL。

如果某个汇总无需使用对称汇总即可正常运行,Looker 会自动检测到这一点,并不会使用该函数。由于对称汇总会带来一些性能开销,因此 Looker 能够区分何时使用对称汇总,何时不使用对称汇总,这进一步优化了 Looker 生成的 SQL,使其尽可能高效,同时仍能保证正确的答案。