ruby on rails 根據特定字段對查詢結果做自定義排序

前幾天在工作中遇到要根據多個國家的code查詢出對應的國家并將結果按code的順序來排序的需求,但我們在rails中做查詢時通常得到的結果都是有順序的(下面都會選擇id來做自定義排序)。

Role.where(id: [2, 1, 5]).map(&:id) #=> [1, 2, 5]

這里就和我上面所講的需求不一致了,我們希望查詢結果的順序是[2, 1, 5]。想了一下沒找到什么優雅的解決方式就在dash和某中文網站(可能是這個需求用中文不太好描述。。)搜索沒結果后,轉而在google上搜了下,挑了靠前的五到六個網頁看了下后,發現方法基本都是那幾種,這里就做一個小整合。

一.case...when相關方法或同類原理

這是看到的最多的方法,有很多變種。這里先做一個記錄。

  • case...when的思路原型是這樣:
case :b
when :a then 1
when :b then 2
when :c then 3
end 
#=> 2

這里第一種是應用sql語句的一種寫法和order by連用,在查詢完后做不規則排序。sql語句原型是

SELECT * FROM roles WHERE id IN (1, 2, 5)
  ORDER BY CASE id
  WHEN 2 THEN 0
  WHEN 1 THEN 1
  WHEN 5 THEN 2
  ELSE 3 END; 
#=> 2, 1, 5

下面的方法就是將該段sql語句用ruby表示出來

def find_ordered(ids)
  order_clause = "CASE id "
  ids.each_with_index do |id, index|
     order_clause << sanitize_sql_array(["WHEN ? THEN ? ", id, index])
  end
  order_clause << sanitize_sql_array(["ELSE ? END", ids.length])
  where(id: ids).order(order_clause)
end
Role.find_ordered([2, 1, 5]).map(&:id) 
#=> [2, 1, 5]
  • 第二種是同樣的思想,先查詢然后將查詢結果的id與要求的id順序做比較。
ids = [2, 1, 5]
records = Role.find(ids)
result = ids.collect {|id| records.detect {|x| x.id == id}}.map(&:id)   
#=> [2, 1, 5]

也可以將id存為key,所在的那條記錄存為value(這個也有多種方法,下面寫較簡單的兩種),再進行比較。

Role.find(ids).index_by(&:id).slice(*ids).values.map(&:id)
#=> [2, 1, 5]
ids = [2, 1, 5]
records = Role.find(ids).group_by(&:id)
result = ids.map {|id| records[id].first}.map(&:id)
#=> [2, 1, 5]

二.mysql的特殊方法

  • mysql有個特性是可以按字段排序ORDER BY FIELD
    具體語法是:
SELECT id FROM roles WHERE id IN (1, 2, 5)
ORDER BY FIELD(id, 2, 1, 5);
#=> 2, 1, 5

用rails轉化后就是

Role.where(id: ids).order("FIELD(id, #{ids.join(',')})").map(&:id)
#=> [2, 1, 5]

三.postgresql的特殊方法

  • 那在pg中就無法使用mysql的field特性了,但是pg也有自己的方式來自定義排序。
    position(substring in string)可以返回指定子字符串的位置
    eg. position('om' in 'Thomas') #=> 3
ids = [2, 1, 5]
Role.where(id: ids).order("position(id::text in '#{ids.join(',')}')").map(&:id)
#=> [2, 1, 5]

ps: 通過這樣的方式做自定義排序也存在問題,如果ids = [12, 2, 1, 5], 那么結果就會出現

User.where(id: ids).order("position(id::text in '#{ids.join(',')}')").map(&:id)
#=> [1, 12, 2, 5]
  • 在postgresql 9.4版本開始,我們可以利用WITH ORDINALITY來設置返回值。那這里我們配合unnest(將一個數組擴展為多行記錄)和JOIN使用,可以通過先新建行記錄確定排序順序然后通過表連接查詢出對應順序的記錄
ids = [12, 2, 1, 5]
User.joins("JOIN unnest(array#{ids}) WITH ORDINALITY t(id, ord) USING (id) ORDER BY t.ord").map(&:id)
#=> [12, 2, 1, 5]

四.gem包:order_as_specified

  • 通過order_as_specified也可以根據字段自定義排序
    github地址
    簡單介紹:在要做查詢的model中添加extend OrderAsSpecified,
Role.where(id: [2, 1, 5]).order_as_specified(id: [2, 1, 5]).map(&:id) #=> [2, 1, 5]

也可以在此基礎上嵌套排序,具體可以直接看該gem包。

五.如果你沒很多數據要查那最直接的方法。。

[2, 1, 5].map{|id| Role.find(id)}.map(&:id) #=> [2, 1, 5]

***
### 總結
從以上幾種方法里可以看出,當你想要根據特定順序查詢數據時,除了通過ruby的方法來進行排序外,還可以通過各個數據庫的特性來完成排序,當然還需要根據實際情況再做決定,我這里因為繼承關系只能在pg的方法的基礎上再做修改了。。

scope :get_and_order_supplier_cal_popular_country, -> (codes) {
sql = sanitize_sql_array(
["JOIN unnest(array[?]) WITH ORDINALITY t(code, ord) USING (code)
WHERE type = 'Country' ORDER BY t.ord", codes] )
joins(sql)
}

最后,可能有些特殊情況沒有考慮進去,歡迎討論。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,401評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,011評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,263評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,543評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,323評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,874評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,968評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,095評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,605評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,551評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,720評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,242評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,961評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,358評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,612評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,330評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,690評論 2 370

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,775評論 18 139
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,705評論 18 399
  • 一. Java基礎部分.................................................
    wy_sure閱讀 3,823評論 0 11
  • 原文:https://my.oschina.net/liuyuantao/blog/751438 查詢集API 參...
    陽光小鎮少爺閱讀 3,834評論 0 8
  • 落葉惹秋風 秋雨吹臉龐 九月秋夜涼 晨曦在他鄉
    晨曦山雞閱讀 200評論 2 0