2017-05-28 78 views
0

我有3個非常簡單的表:複雜的SQL查詢的排名

用戶

user_id 
1 
2 
3 

radio_songs

song_id song 
1  SomeName 
2  OtherName 

radio_rates

user_id song_id rate (from 1 to 5) 
1  1  5 
2  1  4 
1  2  2 
2  2  2 

我已經寫了相當複雜的查詢打靶MySQL的那個cal culates當前「位置」的歌曲根據lower bound of Wilson score confidence interval for a Bernoulli parameter(秩)。

SELECT rank FROM(
    SELECT x.song AS song, x.ci_lower_bound AS ci_lower_bound, (@row:= @row + 1) AS rank FROM(
     SELECT song, ((SUM((rate - 1) * 0.25) + 1.9208)/(SUM((rate - 1) * 0.25) + SUM((5 - rate) * 0.25)) - 1.96 * SQRT((SUM((rate - 1) * 0.25) * SUM((5 - rate) * 0.25))/(SUM((rate - 1) * 0.25) + SUM((5 - rate) * 0.25)) + 0.9604)/(SUM((rate - 1) * 0.25) + SUM((5 - rate) * 0.25)))/(1 + 3.8416/(SUM((rate - 1) * 0.25) + SUM((5 - rate) * 0.25))) AS ci_lower_bound 
     FROM radio_rates 
     INNER JOIN radio_songs ON radio_rates.song_id = radio_songs.song_id 
     GROUP BY radio_rates.song_id 
     ORDER BY ci_lower_bound DESC 
    ) x, (SELECT @row := 0) r 
) xx WHERE xx.song = @song 

該查詢基本上接受@song參數:

  • 計算下界威爾遜的得分以降序通過它的訂單
  • 添加行號的每一行,因爲我沒有找到任何方式在MySQL
  • ROW_NUMBER()最後得到這首歌的排名,我們正在尋找

該查詢正常工作,對此我非常滿意,但是當我們有多個具有相同分數的歌曲時,由於對結果排序進行排序可能會因同一SQL查詢的執行而有所不同。我想,以避免由具有相同的分數作爲目標一個所有歌曲越來越MIN()排名,但查詢有這麼複雜,我掙扎怎麼做,沒有一個臨時表 - 它甚至可能嗎?

我會很感激的幫助,以及在上面查詢的性能/優化方面的任何建議。

我知道這將會是值得考慮只需添加另一個得分列歌曲表,計算它通過觸發器每個插入/更新,但我想避免,如果可能的和按需計算排名。因此SQL查詢本身對我來說最重要。

預先感謝您。

+1

'RANK()'是在MySQL中實現一個痛。 'DENSE_RANK()'更簡單一些。 –

回答

1

這可能會爲你工作:

SELECT rank FROM(
    SELECT x.song AS song, 
      (@row:= @row + 1) AS rn, 
      IF(@last_score = x.ci_lower_bound, @rank, @rank := @row) AS rank 
      (@last_score := x.ci_lower_bound) AS ci_lower_bound 
    FROM(
     SELECT song, ((SUM((rate - 1) * 0.25) + 1.9208)/(SUM((rate - 1) * 0.25) + SUM((5 - rate) * 0.25)) - 1.96 * SQRT((SUM((rate - 1) * 0.25) * SUM((5 - rate) * 0.25))/(SUM((rate - 1) * 0.25) + SUM((5 - rate) * 0.25)) + 0.9604)/(SUM((rate - 1) * 0.25) + SUM((5 - rate) * 0.25)))/(1 + 3.8416/(SUM((rate - 1) * 0.25) + SUM((5 - rate) * 0.25))) AS ci_lower_bound 
     FROM radio_rates 
     INNER JOIN radio_songs ON radio_rates.song_id = radio_songs.song_id 
     GROUP BY radio_rates.song_id 
     ORDER BY ci_lower_bound DESC 
    ) x, (SELECT @row := 0, @rank := null, @last_score := null) r 
) xx WHERE xx.song = @song 

的變化是:

SELECT x.song AS song, 
     (@row:= @row + 1) AS rn, 
     IF(@last_score = x.ci_lower_bound, @rank, @rank := @row) AS rank 
     (@last_score := x.ci_lower_bound) AS ci_lower_bound 

(SELECT @row := 0, @rank := null, @last_score := null) r 

在這一行

IF(@last_score = x.ci_lower_bound, @rank, @rank := @row) AS rank 

只有當分數與最後一行相比發生了變化時,纔將排名設置爲行號。如果得分相同,則使用最後一行的排名。

警告:以這種方式使用會話變量時,當您升級到新版本時,您的代碼始終有可能會返回意外結果。如果它有效,那麼這是因爲引擎是如何實現的。無法保證表達式將按預期順序執行。

作爲一般規則,比SET語句等,你永遠不應該 值分配給一個用戶變量和相同的 語句中讀出的值。例如,爲了增加一個變量,這是好的:

SET @a = @a + 1; 

對於其他的語句,如SELECT,你可能會得到你 預期的結果,但是這不能保證。在下面的語句,你 可能會認爲MySQL將評估@a第一和第二,然後做一個 分配:

SELECT @a, @a:[email protected]+1, ...; 

但是,評價涉及用戶 變量表達式的順序是不確定的。

User-Defined Variables

+0

我知道,我討厭自己從select中的用戶定義變量開始,但是沒有其他方法可以在MySQL中獲得行號功能 - 它已經可以在MariaDB 10.2中用作窗口函數了,所以我期望很快移動到它並且首先擺脫它。 當然你的解決方案的作品,我非常感謝。當我的服務器將它作爲一個blob返回時,我只需將等級轉換爲int。非常感謝您的幫助! – JustArchi

+1

您可以使用帶有AUTO_INCEREMNT列的臨時表獲取沒有用戶定義變量的行號。但要得到你需要的排名是更加複雜一點。然後,您只需要選擇具有相同分數的「min(row_number)」。 –

+1

@JustArchi試圖將'@rank:= null'更改爲'@rank:= 0' - 然後您可能不需要施放'rank'。 –