2012-11-30 100 views
1

我有一個在Heroku上運行的Rails應用程序,我試圖計算用戶的排名(位置)到一個高分列表。多個左連接 - 如何?

該應用程序是用戶互相下注和可以開始下注(創建一個選擇)的地方,或者他們可以對已經創建的選擇(通過下注)進行​​下注。

我有以下的SQL應該給我一個基於他們在選擇和投注上的總獎金的用戶數組..但它給了我一些錯誤的總贏,我認爲問題是在左聯接,因爲如果我重寫SQL只包含要麼選擇或者下注表,然後我的工作就好了..

任何關於如何重寫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 | 
+---------------+ 

回答

1

這是因爲這兩個LEFT JOIN ED表之間的關係的發生 - 也就是說,如果有兩個betschoices(多個)行,一看到的總數量從各個行計數乘以,而不是增加。
如果你有

choices 
id profitloss 
================ 
1 20 
1 30 

bets 
id profitloss 
================ 
1 25 
1 35 

的加入,結果居然是:

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 

(另外,我認爲該公約是在命名錶奇異,不是複數)。

+0

這正是我需要的..非常感謝:) – Twiddr

1

您的問題是,你吹你的數據集。如果你做了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 
+0

-1 - 哇?這不僅是不必要的複雜,它也不會總是給出正確的答案 - 'UNION'意味着如果一個特定的'用戶'具有與投注相同數量的選擇,則只會考慮一個選項,導致總數爲正確數量的一半(因爲'UNION'忽略重複)。 –

+0

我只是忘了添加全部以使其成爲UNION ALL。沒有辦法,這是不必要的複雜。這是一個簡單的子查詢。事實上,我只是用一個UNION ALL命令替換你的左連接。所以,如果這很複雜,你的答案也是如此。 – Neil

+0

...在_potentially_性能較差的地方,一個額外的'GROUP BY',對'users'的額外引用,'COALESCE()'。哦,這取決於他的數據集(和索引)太多了,但是在這裏使用'UNION ALL'可能意味着生成了一個實際的臨時表,這可能不會被編入索引(儘管'ORDER BY無論如何,total_pl DESC'將會很昂貴)。這個版本似乎也奇怪地將這個問題概念化 - 「將這些投注和選擇加在一起」,而不是「爲每個用戶添加這些投注和選擇」。但是,更正了錯誤,因此刪除了-1。 –

0

在一個類似的解決方案作爲發條,因爲列是相同的每個表,我會預工會和他們只是總結他們。所以,在AT MOST,內部查詢每個用戶會有兩個記錄...一個用於投注,一個用於選擇 - 每個用戶分別在進行UNION ALL之後進行預先求和。然後,簡單加入/總和得到結果

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 
+0

......是的,這就是@Neil似乎也在朝着這個方向發展,而且應該有效。然而,我個人並不喜歡這樣做,因爲「賭注」和「選擇」是潛在的不同實體(不太瞭解問題域),如果這樣的話應該這樣處理。而且,大多數RDBMS上的SUM()函數都會忽略空行,所以'COALESCE()'應該被拉到外側(以獲得潛在的性能增益)。 –