【问题标题】:Multiple Left Joins - how to?多个左连接 - 如何?
【发布时间】:2012-11-19 00:59:42
【问题描述】:

我有一个在 Heroku 上运行的 Rails 应用程序,我正在尝试计算用户在高分列表中的排名(位置)。

该应用程序是用户相互下注的地方,他们可以开始下注(创建一个选择),或者他们可以对一个已经创建的选择下注(通过下注)。

我有以下 SQL,它应该根据他们在选择和赌注上的总奖金为我提供一组用户。但它给了我一些错误的总奖金,我认为问题出在左连接,因为如果我重写SQL 只包含 Choice 或 Bet 表,然后我就可以正常工作了..

任何人都知道如何重写 SQL 以使其正常工作:)

SELECT users.id, sum(COALESCE(bets.profitloss, 0) + COALESCE(choices.profitloss, 0)) as total_pl
FROM users
LEFT JOIN bets ON bets.user_id = users.id
LEFT JOIN choices ON choices.user_id = users.id
GROUP BY users.id
ORDER BY total_pl DESC

结果:

+---------------+
| id | total_pl |
+---------------+
|  1 |      830 |
|  4 |      200 |
|  3 |      130 |
|  7 |     -220 |
|  5 |    -1360 |
|  6 |    -4950 |
+---------------+

下面是我只连接到一个表的两个 SQL 字符串和两个结果.. 看到下面的总和与上面的结果不匹配.. 下面是正确的总和。

SELECT users.id, sum(COALESCE(bets.profitloss, 0)) as total_pl 
FROM users 
LEFT JOIN bets ON bets.user_id = users.id 
GROUP BY users.id 
ORDER BY total_pl DESC

SELECT users.id, sum(COALESCE(choices.profitloss, 0)) as total_pl 
FROM users 
LEFT JOIN choices ON choices.user_id = users.id 
GROUP BY users.id 
ORDER BY total_pl DESC

+---------------+
| id | total_pl | 
+---------------+
|  3 |      170 |
|  1 |      150 |
|  4 |      100 |
|  5 |       80 |
|  7 |       20 |
|  6 |      -30 |
+---------------+

+---------------+
| id | total_pl |
+---------------+
|  1 |       20 |
|  4 |        0 |
|  3 |      -10 |
|  7 |      -30 |
|  5 |     -110 |
|  6 |     -360 |
+---------------+

【问题讨论】:

    标签: sql ruby-on-rails join sum left-join


    【解决方案1】:

    这是因为两个 LEFT JOINed 表之间的关系 - 也就是说,如果 betschoices 中都有(多)行,则看到的总行数乘以单个行计数,而不是加法。
    如果你有

    choices
    id   profitloss
    ================
    1    20
    1    30
    
    bets
    id   profitloss
    ================
    1    25
    1    35
    

    join的结果其实是:

    bets/choices
    id   bets.profitloss   choices.profitloss
    1    20                25
    1    20                35
    1    30                25
    1    30                35
    

    (看看这是怎么回事?)

    解决这个问题实际上相当简单。您尚未指定 RDBMS,但这应该适用于其中任何一个(或稍作调整)。

    SELECT users.id, COALESCE(bets.profitloss, 0) 
                         + COALESCE(choices.profitloss, 0) as total_pl
    FROM users
    LEFT JOIN (SELECT user_id, SUM(profitloss) as profitloss
               FROM bets
               GROUP BY user_id) bets
    ON bets.user_id = users.id
    LEFT JOIN (SELECT user_id, SUM(profitloss) as profitloss
               FROM choices
               GROUP BY user_id) choices
    ON choices.user_id = users.id
    ORDER BY total_pl DESC
    

    (另外,我相信惯例是命名表单数,而不是复数。)

    【讨论】:

    • 这正是我所需要的。非常感谢 :)
    【解决方案2】:

    你的问题是你的数据集被炸毁了。如果您执行了 SELECT *,您将能够看到它。尝试这个。我无法测试它,因为我没有你的表,但它应该可以工作

    SELECT
     totals.id
     ,SUM(totals.total_pl) total_pl
    FROM
    (
      SELECT users.id, sum(COALESCE(bets.profitloss, 0)) as total_pl 
      FROM users 
      LEFT JOIN bets ON bets.user_id = users.id 
      GROUP BY users.id 
    
      UNION ALL SELECT users.id, sum(COALESCE(choices.profitloss, 0)) as total_pl 
      FROM users 
      LEFT JOIN choices ON choices.user_id = users.id 
      GROUP BY users.id 
     ) totals
    GROUP BY  totals.id
    ORDER BY total_pl DESC
    

    【讨论】:

    • -1 - 什么?这不仅不必要地复杂,而且并不总是给出正确的答案 - UNION 意味着如果特定的 user 与投注有相同数量的选择,则只会考虑一个,从而导致总数为正确数量的一半(因为UNION 忽略重复项)。
    • 我只是忘记添加 ALL 以使其成为 UNION ALL。这不可能是不必要的复杂。这是一个简单的子查询。事实上,我只是用 UNION ALL 命令替换了您的左连接。因此,如果这很复杂,那么您的答案也很复杂。
    • ... 一个额外的GROUP BY,一个对usersCOALESCE() 的额外引用可能是一个性能较差的地方。哦,这在很大程度上取决于他的数据集(和索引),但是这里使用UNION ALL 可能意味着生成了一个实际的临时表,可能不会被索引(尽管ORDER BY total_pl DESC 无论如何都会很贵)。这个版本似乎也奇怪地将问题概念化——“将这些赌注和选择加在一起”,而不是“对于每个用户,将这些赌注和选择加在一起”。更正了错误,因此删除了 -1。
    • 我概念化它的方式是将赌注作为它的表,然后将选择作为它自己的。然后,根据用户将两者聚合在一起。它基本上就像他们所有操作的一个列表,而不是将其分解为一个单独的列。我更喜欢 DRapp 的解决方案,因为它使用左连接将数据添加到客户。我的方式只会为那些有赌注或选择的用户带来结果。如果我可以访问该表,我将能够比较性能,但那不可用。
    • .... 不,它不会。您仍在使用LEFT JOIN(和COALESCE()),所以它仍然导致每个表都有一行(在最后一个SUM()之前),无论他们是否有任何值。您需要使用常规加入来仅返回至少拥有一个的用户。
    【解决方案3】:

    在与 Clockwork 类似的解决方案中,由于每个表的列相同,因此我会将它们预先联合起来,然后将它们相加。因此,在大多数情况下,内部查询将为每个用户提供两条记录……一条用于赌注,一条用于选择——每条记录都是在执行 UNION ALL 后分别预先求和的。然后,简单的 join/sum 得到结果

    select 
          U.userid,
          sum( coalesce( PreSum.profit, 0) ) as TotalPL
       from
          Users U
             LEFT JOIN
                ( select user_id, sum( profitloss ) as Profit
                     from bets
                     group by user_id
                  UNION ALL
                  select user_id, sum( profitloss ) as Profit
                     from choices
                     group by user_id ) PreSum
                on U.ID = PreSum.User_ID
       group by
          U.ID
    

    【讨论】:

    • ...是的,这也是@Neil 的发展方向,应该可以。但是,我个人不喜欢它,因为betschoices 可能 是不同的实体(不太了解问题域),如果是这样,应该这样处理。此外,大多数 RDBMS 上的 SUM() 函数会忽略空行,因此应该将COALESCE() 拉到外部(以获得潜在的性能提升)。
    猜你喜欢
    • 1970-01-01
    • 2010-12-31
    • 2021-10-21
    • 2019-01-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多