【问题标题】:Simpler way to do a SUM with a fanout on a join在连接上使用扇出进行 SUM 的更简单方法
【发布时间】:2022-01-20 20:08:19
【问题描述】:

注意:SQL后端无所谓,任何主流关系型数据库都可以(postgres、mysql、oracle、sqlserver)

有一个有趣的article on Looker 讲述了当 JOIN 导致扇出时他们用来提供正确总计的技术,大致如下:

# In other words, using a hash to remove any potential duplicates (assuming a Primary Key).
SUM(DISTINCT big_unique_number + total) - SUM(DISTINCT big_unique_number)

一种模拟扇出的好方法,只需执行以下操作:

WITH Orders AS (
    SELECT 10293 AS id, 2.5 AS rate UNION ALL 
    SELECT 210293 AS id, 3.5
),
     Other AS (
    SELECT 1 UNION ALL SELECT 2
)
SELECT SUM(rate) FROM Orders CROSS JOIN Other
-- Returns 12.0 instead of 6.0

他们的例子做了这样的事情,我认为这只是一种通过所有花哨的步法来获取md5(PK) 以绕过 8 字节限制的长形式(所以他们先是 LEFT(...) 然后是 @987654328 @:

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

还有其他通用方法可以做到这一点吗?也许使用相关的子查询或其他东西?或者上述方式是最知名的方式吗?

两个相关的答案:


无需担心通用散列函数(例如,可能采用字符串),以下工作:

WITH Orders AS (
    SELECT 10293 AS id, 2.5 AS rate UNION ALL 
    SELECT 210293 AS id, 3.5
),
Other AS (
    SELECT 1 UNION ALL SELECT 2
)
SELECT SUM(DISTINCT id + rate) - SUM(DISTINCT id) FROM Orders CROSS JOIN Other
-- 6.0

但这仍然引出了一个问题:是否有另一种/更好的方法可以以非常通用的方式做到这一点?

【问题讨论】:

  • 一些示例数据会对您的问题有所帮助,因为它不会强迫读者阅读外部文章来理解。
  • @TimBiegeleisen 我刚刚用一个有效的查询更新了它,你想让我知道它是否有效吗?
  • 为什么不在第一个子查询WITH Orders AS(...)中直接计算sum(rate)
  • 我删除了冲突的 DBMS 标签。请为您真正使用的数据库产品添加一个标签(您的查询一开始在 Postgres 或 Oracle 中不起作用)。如果你想要一个独立于 DBMS 的答案,那么 sql 标签就足够了。
  • @David542 - 是的,我同意你的观点,这是一个 CTE 而不是子查询......但我的问题仍然有效:你为什么不直接在 CTE @987654335 中计算 sum(rate) @?这将使查询更加简单、可读和高效,不是吗?

标签: sql


【解决方案1】:

连接破坏聚合的典型示例如下:

select
  posts.id,
  count(likes.id) as likes_total,
  count(dislikes.id) as dislikes_total
from posts
left join likes on likes.post_id = posts.post_id
left join dislikes on dislikes.post_id = posts.post_id
group by posts.id;

两个计数结果相同,因为每个计数都被另一个相乘。有 2 个喜欢和 3 个不喜欢,两个计数都是 6。

简单的解决方案是:加入前聚合。如果您想知道每个帖子的喜欢和不喜欢计数,请将喜欢和不喜欢计数加入帖子。

select posts.id, l.likes_total, d.dislikes_total
from posts
left join
(
  select post_id, count(*) as likes_total
  from likes
  group by post_id
) l on l.post_id = posts.post_id
left join
(
  select post_id, count(*) as dislikes_total
  from dislikes
  group by post_id
) d on d.post_id = posts.post_id
group by posts.id;

如果您想看到零而不是空值,请使用 COALESCE

不要试图蒙混过关。只需聚合,然后加入。如果 DBMS 支持,您当然可以将连接替换为横向连接(它们是相关的子查询)。或者对于示例中的单个聚合,甚至将相关的子查询移动到 select 子句。这主要是个人喜好,但取决于 DBMS 的优化器,一种解决方案可能比另一种更快。 (理想情况下,优化器当然会为所有这些查询提供相同的执行计划。)

【讨论】:

  • @thorstern -- 感谢您的回答。出于好奇,你为什么认为 Looker 使用了复杂的技巧,而不是使用类似于上面的方法,这对我来说似乎更易于维护?
  • Looker 似乎是一个试图帮助您检索 BI 数据的工具。他们注意到,许多用户在涉及多个聚合时对查询编写缺乏经验,因此往往会陷入乘法陷阱。因此,开发人员让 Looker 在幕后更改用户的 SQL,以便正确编写的查询仍然有效,而不正确的聚合查询可能会得到拯救,尽管编写不正确,但仍然显示所需的结果。因此,他们让客户对该工具感到满意。如果客户编写了正确的查询,那么这个技巧就没有必要了。
【解决方案2】:

使用更大的数据类型将值移开。这与第一个示例类似,没有发生冲突的可能性。由于不必执行两个不同的不同求和,它可能还具有较小的性能优势。

 sum(distinct id * 1000000000 + value) % 1000000000

原则是将值打包成一个单元。为了获得最大的灵活性,您希望将其转换为宽十进制类型以适应整个范围。使用字符串很容易通过 dense_rank() 生成新的代理 id 这也可以让您根据预期键值的数量折叠键宽度。

不过,我认为最终的答案是否定的。没有一种万能的方法,尤其是在各种聚合函数的范围内,超出了混合数据类型的变化范围。

【讨论】:

  • 当然,这行得通,但我的意思是用于通用 PK,例如,如果 id 是字符串。也对大量不能溢出吗?
  • 当然。但这已经是对他们的答案的改进,允许总和发生冲突。
【解决方案3】:

我认为最好的选择始终是 SUM 数据,然后再通过子查询或 cte 加入

WITH 
Orders AS (
    SELECT 10293 AS id, 2.5 AS rate 
    UNION ALL 
    SELECT 210293 AS id, 3.5
),
Other AS (
    SELECT 1 other
    UNION ALL 
    SELECT 2
)
select *
from (
    SELECT SUM(rate) rate
    FROM Orders 
) OrdersSummed 
CROSS JOIN Other

WITH 
Orders AS (
    SELECT 10293 AS id, 2.5 AS rate 
    UNION ALL 
    SELECT 210293 AS id, 3.5
),
Other AS (
    SELECT 1 other
    UNION ALL 
    SELECT 2
),
OrdersSummed AS (
    SELECT SUM(rate) rate
    FROM Orders 
) s
select *
from OrdersSummed 
CROSS JOIN Other

【讨论】:

    【解决方案4】:

    --接近解决方案,使扇出现象成为交叉连接的自然结果。

        ;WITH Orders AS (
            SELECT 10293 AS id, 2.5 AS rate UNION ALL 
            SELECT 210293 AS id, 3.5
        ),  Other AS (
            SELECT 1 as oth_id UNION ALL SELECT 2 as oth_id
        )
        ,   FanDepth AS (
            SELECT count(*) as depth from Other
        )
        SELECT  SUM(rate) / depth 
        FROM 
        Orders CROSS JOIN Other CROSS JOIN FanDepth 
        Group by depth 
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-01-27
      • 2020-11-06
      • 1970-01-01
      • 1970-01-01
      • 2013-09-06
      • 1970-01-01
      • 1970-01-01
      • 2019-05-23
      相关资源
      最近更新 更多