[Rails] Active Record Queries

資料來源:Rails Guide

Guide

-“查詢”就是根據(jù)條件查找記錄杀饵。
-“查詢操作”多種多樣:select, where, order, etc
-“方法鏈”:將一系列查詢操作的方法通過.串鏈在一起
-“預載入”eager load通過加載關(guān)聯(lián)對象莽囤,以減少查詢次數(shù)
-如何檢查查詢的記錄是否存在,或關(guān)聯(lián)的對象是否存在

1. Why

為什么要使用關(guān)聯(lián)切距?
-(1) 可增強代碼可讀性
-(2) 不依賴于特定的數(shù)據(jù)庫
-(3) 嘿嘿最爽的朽缎,不用寫復雜的 SQL
Tips: 某些特殊情況為了提高效率你還是得寫 :(

2. What

查詢 通過指定的條件得到你想要的結(jié)果

2.1 查詢步驟

-(1) 將查詢方法轉(zhuǎn)化為 SQL 查詢語句
-(2) 執(zhí)行 SQL 查詢語句谜悟,返回數(shù)據(jù)庫中相關(guān)記錄
-(3) 將相關(guān)記錄轉(zhuǎn)化為關(guān)聯(lián)模型的 Ruby 對象
-(4) 執(zhí)行after_findafter_initialize 回調(diào)

2.2 查詢結(jié)果通郴靶ぃ可分為兩類

-(1) 若查找一條記錄,返回的是模型對象的一個實例葡幸;
-(2) 若查找結(jié)果為多個模型對象實例的集合最筒,返回ActiveRecord::Relation對象
Tip: 區(qū)分··關(guān)聯(lián)查找返回的是ActiveRecord::Association::CollectionProxy對象
Tip: 若是通過關(guān)聯(lián)在進行查詢,返回ActiveRecord::AssociationRelation對象

2.2.1 返回單個對象

查詢方法:find, find_by, take, first, last
Tip1: find找不到時拋出ActiveRecord::RecordNotFound蔚叨,其他方法返回nil
Tip2: 若想讓其他方法也拋出ActiveRecord::RecordNotFound床蜘,則使用爆破方法
Tip3: 這些方法也可以返回多條記錄(數(shù)組),find后面跟一組id組成的數(shù)組可返回多個記錄蔑水,其他方法加數(shù)量參數(shù)也可返回多個記錄邢锯;注意find_by方法無法返回數(shù)組,屬性對應數(shù)組的話只是增加查詢所要滿足的條件

2.2.2 返回多個對象(不含條件)

-(1) all載入所有記錄到內(nèi)存中搀别,當數(shù)據(jù)量很大時會爆炸丹擎!
-(2) find_each將記錄分成大小相等的塊,一塊一塊地載入领曼,代碼塊中處理單個記錄
-(3) find_in_batch同上鸥鹉,分塊載入,代碼塊中處理整個載入的塊 (數(shù)組)
-分塊載入方法添加選項 batch_size, start, finish 分別指定大小庶骄,開始結(jié)束位置

Tip: the 'find_each' and 'find_in_batch' methods are intended for use in the batch processing of large number of records that wouldn't fit in memory all at once. if you just need to loop over a thousand records the regular find methods (all, where...) are the preferred option.

2.2.3 返回多個對象(含條件)

2.2.3.1 where

條件可以是純字符串 (存在SQL注入)毁渗,數(shù)組,占位符单刁,哈希鍵值對
一般情況下建議使用哈希的形式 (Rails way)灸异,若不能滿足使用數(shù)組或占位符
哈希鍵值對的使用可分為三種情況: (1) Equality (2) Range (3) Subset
Tip: not跟在where后面是用表示不等于府适。

2.2.3.2 order

order對查找的結(jié)果排序 (:asc 升序, :desc 降序),默認為升序

2.2.3.3 select

默認情況下肺樟,會查詢記錄中所有字段檐春,使用select可以指定要查找的字段
distinct query = Client.select(:id, :name).distinct <~> query.distinct(false)
Tip1: 后面接distinct方法查找單個記錄,也可使用distinct(false)去除限制
Tip2: 當要查找的字段不存在時會拋出ActiveRecord::MissingAttributeError
Tip3: 若指定的字段不含id則關(guān)聯(lián)將會失效
Tip4: 只查找個別字段會提高查詢的效率

2.2.3.4 limit & offset

limit 來指定你想得到多少條記錄
offset 來制定你想跳過多少條記錄后開始查找
query = Client.limit(5).offset(30) # start with 31st get 5 client records

2.2.3.5 group & having

group 用來把屬性相同的記錄組織在一起,用于計算等
having 用來對使用group之后計算得到的值來限制條件

.group("date(created_at)").having("sum(price) > ?", 100)```
**Tip**: ```group```方法后面加```count```可以的到每組對應的記錄個數(shù) (Hash)
```Order.group(:status).count # => { 'awaiting_approval' => 7, 'paid' => 12 }```

#### 2.2.3.6 Overriding Conditions
```unscope``` 移除之前定義過的某個查詢方法或某個條件
```Article.where('id > 10').limit(20).order('id asc').unscope(:order)```
```Article.where(id: 10, trashed: false).unscope(where: :id)```
```only``` 用來限制指定的查詢方法是有效的,其他無效
```reorder``` 用來重新指定排序的字段
```reverse_order``` 與之前的排序相反
```rewhere``` 重新指定查詢的條件

#### 2.2.3.7 none
只要查詢方法鏈中包含```none```就返回空的查詢結(jié)果 ```#<ActiveRecord::Relation []>```
```User.none if user.in_blacklist? # => #<ActiveRecord::Relation []> ```
**Tip**: 當你在特定情況下驼修,如該用戶被拉入黑名單渐裸,他的查詢操作結(jié)果應該為空

#### 2.2.3.8 readonly
一個查詢結(jié)果后添加```readonly```用來明確指出該記錄是不可被修改的
若你強行修改它系統(tǒng)會拋出```ActiveRecord::ReadOnlyRecord```.

#### 2.2.3.9 joins
```joins``` 后面可直接加 SQL 語句來進行查詢毫炉,或接關(guān)聯(lián)
```joins``` 還可以連接多個關(guān)聯(lián),可以連接嵌套關(guān)聯(lián)
```left_outer_joins``` 用來處理左連接,即使關(guān)聯(lián)為空,也會放入查詢結(jié)果
```ruby
Author.left_outer_joins(:posts).distinct
      .select('authors.*, COUNT(posts.*) AS posts_count')
      .group('authors.id')

=> (sql) SELECT DISTINCT authors.*, COUNT(posts.*) AS posts_count FROM "authors"
         LEFT OUTER JOIN posts ON posts.author_id = authors.id GROUP BY authors.id

3. Existence of Objects

比較這些方法:present?, exists?, any?, many?
-(1) 避免使用 present? 查看結(jié)果是否存在欣舵,效率較低:
SELECT "posts".* FROM "posts" WHERE "posts"."user_id" = ? [["user_id", 1]]
-(2) exists?, any?查看結(jié)果是否存在,效率較高 (查到一條記錄存在就停止)
SELECT 1 AS one FROM "posts" WHERE "posts"."user_id"=? LIMIT ? [..., limit 1]
-(3) 使用 many? 查看結(jié)果是否有不止一個 (查詢語句使用count):
SELECT COUNT(*) FROM "posts" WHERE "posts"."user_id" = ? [["user_id", 1]]]
Tip: 和any?相比缀磕,exists?后面可以加條件參數(shù)缘圈,查看滿足條件的記錄是否存在
Client.exists?(name: ['Wende', 'Zhaobo', 'Junda'])
Client.where(name: ['Wende', 'Zhaobo', 'Junda']).exists?

4. Locking

給記錄上鎖是為了避免多人同時操作同一條記錄可能產(chǎn)生的沖突。

4.1 Optimistic Locking

  • 樂觀鎖允許多個用戶獲取并操作同一記錄袜蚕,在操作完成并提交更新時通過查看該記錄的版本來判斷是否會發(fā)生沖突糟把,若版本不一致拋出ActiveRecord::StaleObjectError并默認無視所更新的內(nèi)容。對于失敗的情況我們要處理這種異常廷没,回滾垂寥,合并或是進行其他邏輯操作來彌補。(實現(xiàn)方式通過添加lock_version自動實現(xiàn))
  • 樂觀鎖用來處理競爭關(guān)系滞项,誰都可以獲取記錄的操作權(quán),誰先完成文判,誰就可以更新記錄过椎,其他的更新將會在本次競爭中失敗疚宇。所以少了等待的過程。

4.2 Pessimistic Locking

  • 悲觀鎖是當某個用戶準備操作某條記錄之前赏殃,給這個記錄或表上鎖,這樣其他人在他沒完成操作之前都無法打開這個鎖仁热,不存在競爭關(guān)系,必定是這個上鎖的人完成更新操作。(悲觀鎖是數(shù)據(jù)庫底層自帶的鎖機制思劳,使用lock, lock!, with_lock來上鎖)
  • 悲觀鎖可確保第一個獲取記錄操作權(quán)的人完成更新。并且避免大量用戶操作同個記錄中只有一人更新其他則做無用功的情況妨猩,在操作完成時更新比較記錄鎖的版本號也是要耗費性能的潜叛。

5. Eager Loading Association

通過使用includes方法解決 n +1 queries 問題
Category.includes(articles: [ { comments: :guest}, :tags ]).find(1)
上面的例子會找到 id 為 1 的類別記錄,并且加載他所有關(guān)聯(lián)的文章壶硅,文章相關(guān)的標簽和評論钠导,還有相關(guān)評論人,這樣你在對這些數(shù)據(jù)進行操作時就不必在進行其他查詢了

6. Scopes

Scope 可以讓你把常用的關(guān)聯(lián)查詢語句構(gòu)建成為一個類方法森瘪,供類和關(guān)聯(lián)對象使用
它的返回值統(tǒng)一為relation這樣便于調(diào)用鏈方法牡属,就好像是自己構(gòu)建的一個查詢方法
Tips: scope 和類方法的區(qū)別?scope 返回一個ActiveRecord::Relation對象扼睬,即使當查詢條件不滿足時也會返回一個空的relation逮栅。反觀類方法當結(jié)果不滿足時你可以返回任何你想返回的值,如nil窗宇,而這就會導致查詢鏈斷裂措伐,因為無法對一個空的對象在進行更多查詢了。

7. Enums

enum availability: [:available, :unavailable]
使用enum宏方法來匹配類型為integer的字段军俊,已達到枚舉不同狀態(tài)的效果侥加。
使用它非常方便清晰,會自動擁有許多幫助方法粪躬,當情況更加復雜時可以使用狀態(tài)機担败。

8. Dynamic Finders

find_by_first_name('wende'), find_by_last_name('lu'), find_by_age(25)
find_by_first_name_and_last_name_and_age('wende', 'lu', 25)

9. Method Chain

在方法鏈末尾使用where來過濾得到一組記錄。
在方法鏈末尾使用find_by來過濾得到一條記錄镰官。

10. Find or Build a new Object

find_or_create_by, find_or_initialize_by 創(chuàng)建或初始化一條記錄如果它不存在提前。
Tip1: 使用created_with或代碼塊在創(chuàng)建時賦值,注意在查詢時該語句將會被忽略泳唠。
Tip2: 使用persisted?, new_record?來判斷記錄是否存在在數(shù)據(jù)庫中狈网。

11. Find by SQL

find_by_sql 使用自定義的 SQL 將查詢結(jié)果初始化為對象存到數(shù)組中。
connection#select_all 自定義查詢笨腥,獲取由哈希鍵值對所組成的數(shù)組 (不初始化對象)拓哺。
pluck 接收一系列列名作為參數(shù)并且返回包含記錄中該列的值的數(shù)組。
Client.pluck(:id) 等價于 Client.select(:id).map(&:id)
Client.pluck(:id, :name) 等價于 Client.select(:id, :name).map {|c| [c.id, c.name]}
Tips: 在一個已查詢的relation上使用pluck并不會重新去查詢數(shù)據(jù)庫脖母。
Tips: ids 相當于使用pluck獲得所有主鍵id的數(shù)組士鸥。
Tips: select_all, pluck 直接把數(shù)據(jù)庫查詢的結(jié)果轉(zhuǎn)化為數(shù)組,并不會創(chuàng)建模型對象镶奉。對于操作量打并且查詢次數(shù)頻繁的查詢操作础淤,可以提高效率崭放。

12. Calculations

count, average, minimum, maximum, sum
Tips: count后可以添加列名,表示查找該列名存在的記錄個數(shù)鸽凶。
Tips: 這些計算操作都是在數(shù)據(jù)庫層執(zhí)行 SQL 計算得到的币砂,需要對數(shù)據(jù)庫操作。
為提高效率玻侥,我么希望先獲得查詢結(jié)果,轉(zhuǎn)化為普通數(shù)組在進行操作掌桩,以減少查詢姑食。

13. Explain

relation使用explain方法可以查看該查詢語句的詳細信息音半,用于調(diào)優(yōu)。
不同數(shù)據(jù)庫都有自己的EXPLAIN方法曹鸠,查看并掌握他們十分有用彻桃。
Tips: 對relation使用to_sql可以查看該查詢方法鏈所生成的 SQL 語句。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末眠屎,一起剝皮案震驚了整個濱河市组力,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌腥椒,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,490評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件洒放,死亡現(xiàn)場離奇詭異往湿,居然都是意外死亡,警方通過查閱死者的電腦和手機他膳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評論 3 395
  • 文/潘曉璐 我一進店門些膨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來订雾,“玉大人,你說我怎么就攤上這事误甚∑拙唬” “怎么了壕探?”我有些...
    開封第一講書人閱讀 165,830評論 0 356
  • 文/不壞的土叔 我叫張陵李请,是天一觀的道長。 經(jīng)常有香客問我较幌,道長白翻,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,957評論 1 295
  • 正文 為了忘掉前任岛琼,我火速辦了婚禮槐瑞,結(jié)果婚禮上困檩,老公的妹妹穿的比我還像新娘。我一直安慰自己悼沿,他們只是感情好,可當我...
    茶點故事閱讀 67,974評論 6 393
  • 文/花漫 我一把揭開白布软瞎。 她就那樣靜靜地躺著涤浇,像睡著了一般只锭。 火紅的嫁衣襯著肌膚如雪院尔。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,754評論 1 307
  • 那天纵顾,我揣著相機與錄音施逾,去河邊找鬼例获。 笑死,一個胖子當著我的面吹牛蠕搜,可吹牛的內(nèi)容都是我干的妓灌。 我是一名探鬼主播啼器,決...
    沈念sama閱讀 40,464評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼端壳,長吁一口氣:“原來是場噩夢啊……” “哼损谦!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起照捡,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤栗精,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后鹿寨,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體薪夕,經(jīng)...
    沈念sama閱讀 45,847評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡原献,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,995評論 3 338
  • 正文 我和宋清朗相戀三年姑隅,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片慕趴。...
    茶點故事閱讀 40,137評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡秩贰,死狀恐怖柔吼,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情愈魏,我是刑警寧澤,帶...
    沈念sama閱讀 35,819評論 5 346
  • 正文 年R本政府宣布溪厘,位于F島的核電站畸悬,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏蹋宦。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,482評論 3 331
  • 文/蒙蒙 一守屉、第九天 我趴在偏房一處隱蔽的房頂上張望拇泛。 院中可真熱鬧,春花似錦思灌、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽甜奄。三九已至,卻和暖如春课兄,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背烟阐。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評論 1 272
  • 我被黑心中介騙來泰國打工蜒茄, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人檀葛。 一個月前我還...
    沈念sama閱讀 48,409評論 3 373
  • 正文 我出身青樓屿聋,卻偏偏與公主長得像,于是被迫代替她去往敵國和親转锈。 傳聞我的和親對象是個殘疾皇子楚殿,可洞房花燭夜當晚...
    茶點故事閱讀 45,086評論 2 355

推薦閱讀更多精彩內(nèi)容