繼續來探究 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 資料查詢)