繼續來探究 Active Record,前幾篇談論資料的關聯性,這次我們來點在 Active Record 怎麼抓取資料!
前情提要
大家還記得當時提及 Active Record 的好處嗎?
如果沒印象,可以回頭看看:Day 19 - 理解 Ruby on Rails,ORM 與 Active Record 是什麼?
Active Record 是具體的 ORM 實現,提供一種方式來定義和操作 Model,隱藏了資料庫操作的細節,允許開發人員使用物件導向語法來處理資料。
一般我們可以使用原始的 SQL 查詢尋找資料庫記錄,但在 Active Record 裡可以用他提供的方法來操作,
現在讓我們來看看在 Active Record 是如何搜尋資料的吧!
如何抓到資料!
Retrieving a Single Object 找尋單一物件
Active Record 提供了多種不同的方式來找尋單一物件(資料表中的一筆記錄)。
方法:find, find_by, first, last, take
find
- Active Record: 使用
find方法,根據 主鍵 (id) 來查找單一記錄。1
user = User.find(1)
- SQL 語法: 查找主鍵值為 1 的記錄。
1
SELECT * FROM users WHERE id = 1;
- Active Record: 使用
find_by
- Active Record: 使用
find_by方法,根據指定的條件查找第一個匹配的記錄。1
user = User.find_by(username: 'viii')
- SQL 語法: 查找用戶名為 ‘viii’ 的第一條記錄。
1
SELECT * FROM users WHERE username = 'viii' LIMIT 1;
find_by!與find_by非常相似,都是用來查找符合條件的記錄。但是,find_by!在找不到匹配的記錄時會引發ActiveRecord::RecordNotFound的錯誤訊息,而find_by僅返回nil。這可以用來強制確保查詢會找到一條記錄,並在未找到時引發錯誤訊息。- Active Record: 使用
first
- Active Record: 使用
first方法,檢索資料表中的第一條記錄。1
first_user = User.first
- SQL 語法: 檢索資料表中的第一條記錄。
1
SELECT * FROM users LIMIT 1;
- Active Record: 使用
last
- Active Record: 使用
last方法,檢索資料表中的最後一條記錄。1
last_user = User.last
- SQL 語法: 檢索資料表中的最後一條記錄。
1
SELECT * FROM users ORDER BY id DESC LIMIT 1;
- Active Record: 使用
take
- Active Record: 使用
take方法,取得資料表中的一條記錄。1
user = User.take
- SQL 語法: 檢索資料表中的一條記錄。
1
SELECT * FROM users LIMIT 1;
- Active Record: 使用
Retrieving Multiple Objects in Batches 批次找尋物件
當取得多筆資料時,很直覺覺得可以使用
all接著.each方法,但是!這樣可能會導致一次將整個資料表的資料提取出來,並存放在記憶體中,這樣的做法當資料量很大時,很容易超過記憶體的負荷。
1
2
3
4 # 將整個資料表的資料全部加載到記憶體中
User.all.each do |user|
puts user.name
end
為了避免這些問題,Active Record 提供 find_each 或 find_in_batches 方法,可以批次處理記錄,
而不是一次性將所有記錄載入記憶體,以便更有效地處理大量數據。這樣可以減輕記憶體負荷並提高效能。
方法:find_each, find_in_batches
find_each
find_each按批次檢索記錄,並遍歷紀錄。這對於處理大量記錄非常有用,因為每次只加載一批記錄,而不是全部加載到內存中。1
2
3
4
5# 檢索所有用戶記錄,每次處理 1000 條記錄
User.find_each(batch_size: 1000) do |user|
# 在這裡對每個用戶記錄進行操作
puts user.name
endOptions for
find_each::batch_size,:start,:finish,:error_on_ignore,:order:batch_size:指定每個批次中要檢索的記錄數量,用於控制每次處理的記錄數。:start:配置序列的第一個 ID,可用於中斷的批次處理,指定從哪個 ID 開始。:finish:配置序列的最後一個 ID,可用於檢索特定範圍內的記錄。:error_on_ignore:用於控制當查詢操作遇到問題時應該發生什麼。如果在發現問題時立即中止操作,可以設置為 true;如果操作繼續進行,即使有問題也不中斷,可以保持默認值 false。:order:指定主鍵 (id) 的順序,可以是升序 (:asc) 或降序 (:desc),默認為升序。
find_in_batches
find_in_batches方法類似於find_each,按批次檢索記錄,但不提供遍歷功能。返回的是一個包含每批記錄的集合,可以透過這個集合並處理每批記錄。1
2
3
4
5
6
7# 檢索所有用戶記錄,每次處理 1000 條記錄
User.find_in_batches(batch_size: 1000) do |batch|
# 在這裡處理每批記錄
batch.each do |user|
puts user.name
end
endOptions for
find_in_batches::batch_size,:start,:finish,:error_on_ignore
這兩種方法都有助於減少大量數據查詢時的性能問題,特別是當記錄數量很龐大時,透過分批處理,可以更有效地管理記憶體使用,從而提高應用程式的效能。
Conditions 條件搜尋
where 方法接收一個條件可以使用 字串、陣列、物件(key : value)作為參數,並返回所有符合條件的查詢對象,是一個 ActiveRecord 查詢集合(Relation)。
如果找不到紀錄,會是一個空的 ActiveRecord 查詢結果 [ ]。
方法: where
Pure String Conditions 純字串條件
用戶可以通過輸入關鍵字來搜索產品的名稱:1
2keyword = params[:keyword] # 使用者輸入的搜索關鍵字
Product.where("name LIKE '%#{keyword}%'") # 不推薦的寫法,容易有 SQL injection 的風險將使用者輸入的 keyword 直接插入到 SQL 字符中,這樣的做法可能導致 SQL injection!
SQL injection 是一個安全漏洞,攻擊者可以通過在 SQL 查詢中插入惡意代碼,從而對數據庫進行未授權的操作。如何防止 SQL injection?
在 Rails 中,防止 SQL injection 的方式可以透過使用 Array Conditions(陣列條件)
Array Conditions 陣列條件
1
Product.where("name LIKE ?", params[:keyword]) # 推薦的寫法,使用 Array Conditions
陣列的第一個元素是一個 SQL 條件字串
"name LIKE ?",可以包含在 SQL 查詢的 WHERE 子句中。這個字串中可以包含佔位符,通常用問號?來表示,Active Record 會將?換成params[:keyword]做查詢,確保資料被安全地處理,以防止任何 SQL injection。Placeholder Conditions
Placeholder conditions 具有類似於使用問號?的 params 替換特性,這種風格通常用於傳遞參數值,以防止 SQL 注入攻擊。
除了使用問號之外,還可以在查詢條件字串中指定 keys/values 用Hash方式!這種方式的好處是,若條件中有許多參數,這種寫法不僅提高了可讀性,傳遞起來也更方便。用戶可以搜索擁有特定名稱和年齡的用戶,使用 Placeholder conditions 來構建查詢條件:
1
2
3
4
5
6
7conditions = "name = :user_name AND age > :min_age"
values = { user_name: "viii", min_age: 30 }
User.where(conditions, values)
# 寫在一起
User.where("name = :user_name AND age > :min_age", { user_name: "viii", min_age: 30 })儘管條件參數會自動轉義以防止 SQL 注入,但 SQL LIKE 在遇到 SQL Wildcards (SQL 萬用字元),即
%和_下,不會轉義,這可能會導致意外行為。例如:
1
Book.where("title LIKE ?", params[:title] + "%")
可以通過sanitize_sql_like來解決:
1
Book.where("title LIKE ?", Book.sanitize_sql_like(params[:title]) + "%")
Hash Conditions
前情提要:
Only equality, range, and subset checking are possible with Hash conditions.
只有 Equality (相等性)、Range (範圍)、Subset (子集) 可用這種形式來寫條件。Equality (相等性)
- 查找名字是 “viii” 的用戶。 生成的 SQL 查詢:
1
User.where(name: "viii")
SELECT * FROM users WHERE (users.name = 'viii') - 使用字串
'name'指定為 “viii”。1
User.where('name' => "viii")
- 使用關聯模型的實例作為值,並使用關聯名作為鍵,以查找符合特定關聯的記錄。
假設User有一個belongs_to關係,指向Country模型,
並且想查找居住在美國的用戶。1
2country = Country.find_by(name: "USA")
User.where(country: country) - tuple-like 序組構造可用於處理具有複合主鍵的表格。
假設User表具有複合主鍵,由country_id和user_id兩個列組成,
可以查找具有特定組合的用戶。1
User.where([:country_id, :user_id] => [[1, 101], [2, 202]])
- 查找名字是 “viii” 的用戶。
Range (範圍)
- 查詢在一個特定範圍內的記錄。 生成的 SQL 查詢:
1
Product.where(price: 10..50) # 返回所有價格在 10 到 50 之間的產品記錄。
SELECT * FROM products WHERE (products.price BETWEEN 10 AND 50)
- 查詢在一個特定範圍內的記錄。
Subset (子集)
- 可以一次查找多個特定值的記錄
查找名字為 “Alice”、”Bob” 和 “Charlie” 的用戶:生成的 SQL 查詢:1
User.where(name: ["Alice", "Bob", "Charlie"])
SELECT * FROM users WHERE (users.name IN ('Alice', 'Bob', 'Charlie'))
- 可以一次查找多個特定值的記錄
NOT, OR, AND Conditions
Active Record 提供了多種方法來構建 SQL 查詢的NOT、OR和AND。- NOT
where.not生成的 SQL 查詢:1
User.where.not(name: "John")
SELECT * FROM users WHERE NOT (users.name = 'John')
- OR
or生成的 SQL 查詢:1
User.where(name: "Alice").or(User.where(name: "Bob"))
SELECT * FROM users WHERE (users.name = 'Alice' OR users.name = 'Bob')
- AND
- 使用多個
where條件,多個條件之間是 AND 關係。生成的 SQL 查詢:1
User.where(name: "Alice", age: 25)
SELECT * FROM users WHERE (users.name = 'Alice' AND users.age = 25)
- 使用多個
- NOT
Ordering
1
2User.order(age: :asc) # 升冪排序
User.order(age: :desc) # 降冪排序- 多次使用
order生成的 SQL 查詢:1
User.order(:name).order(age: :desc)
SELECT * FROM users ORDER BY users.name ASC, users.age DESC
- 多次使用
Selecting Specific Fields
可以指定想要從資料庫中檢索的特定欄位,而不是檢索整個資料表,達到效能優化。
方法:select,distinct- 假設有一個
User模型,包含了name、email和age,但只想查name和email:生成的 SQL 查詢:1
User.select(:name, :email)
SELECT name, email FROM users
要小心使用
select。因為實體化出來的物件僅有所選欄位。
如果試圖存取不存在的欄位,會得到 ActiveModel::MissingAttributeError 異常:ActiveModel::MissingAttributeError: missing attribute: <attribute>- 如果想要僅選擇某個欄位中每個唯一值對應的一條記錄,可以使用
distinct生成的 SQL 查詢:1
User.select(:name).distinct
SELECT DISTINCT name FROM users
- 假設有一個
Limit(限制)Offset(偏移)
Limit和Offset是用於在 SQL 查詢中控制結果集大小和選擇的兩個重要參數。- Limit(限制):
Limit用於限制查詢結果集的大小,指定了要返回的記錄數量。 - Offset(偏移):
Offset用於指定從查詢結果集的開頭位置開始返回記錄。允許跳過前幾條記錄,以便從指定位置開始檢索記錄。
繼續用
User模型來講解,想查詢所有用戶的名字,但每次僅返回前五個名字,並且跳過前十個名字,這樣你可以從第十一個名字開始繼續查詢,這就可以使用limit和offset選項來實現這個目的:1
User.select(:name).limit(5).offset(10)
這個查詢將選擇
User表中的name字段,然後使用limit(5)來指定返回前五個名字,使用offset(10)來指定從第十一個名字開始返回。
生成的 SQL 查詢:SELECT name FROM users LIMIT 5 OFFSET 10這兩個通常在分頁(pagination)中使用,在列表中顯示一部分記錄,然後允許用戶查看更多的記錄。
- Limit(限制):
Brief Summary
- 要查找單一記錄,你可以使用
find、find_by、first、last、take。 - 要批次處理多條記錄,可使用
find_each或find_in_batches以提高效能。 - 使用
where方法可以建立條件搜尋,避免 SQL 注入攻擊。可以使用純字串、陣列、物件、或哈希來構建條件。 -NOT、OR、AND條件也可輕鬆應用。 - 進一步控制查詢結果可以使用
order、select、distinct、limit和offset。
參考資料:
➫ Active Record Query Interface
➫ Active Record 查詢
➫ PJCHENder - [Rails] Active Record Query(SQL Query & Model 資料查詢)