繼續來探究 Active Record,前幾篇談論資料的關聯性,這次我們來點在 Active Record 怎麼抓取資料!
大家還記得當時提及 Active Record 的好處嗎?
如果沒印象,可以回頭看看:Day 19 - 理解 Ruby on Rails,ORM 與 Active Record 是什麼?
Active Record 是具體的 ORM 實現,提供一種方式來定義和操作 Model,隱藏了資料庫操作的細節,允許開發人員使用物件導向語法來處理資料。
一般我們可以使用原始的 SQL 查詢尋找資料庫記錄,但在 Active Record 裡可以用他提供的方法來操作,
現在讓我們來看看在 Active Record 是如何搜尋資料的吧!
Active Record 提供了多種不同的方式來找尋單一物件(資料表中的一筆記錄)。
方法:find
, find_by
, first
, last
, take
find
find
方法,根據 主鍵 (id) 來查找單一記錄。user = User.find(1)
SELECT * FROM users WHERE id = 1;
find_by
find_by
方法,根據指定的條件查找第一個匹配的記錄。user = User.find_by(username: 'viii')
SELECT * FROM users WHERE username = 'viii' LIMIT 1;
find_by!
與find_by
非常相似,都是用來查找符合條件的記錄。但是,find_by!
在找不到匹配的記錄時會引發ActiveRecord::RecordNotFound
的錯誤訊息,而find_by
僅返回nil
。這可以用來強制確保查詢會找到一條記錄,並在未找到時引發錯誤訊息。
first
first
方法,檢索資料表中的第一條記錄。first_user = User.first
SELECT * FROM users LIMIT 1;
last
last
方法,檢索資料表中的最後一條記錄。last_user = User.last
SELECT * FROM users ORDER BY id DESC LIMIT 1;
take
take
方法,取得資料表中的一條記錄。user = User.take
SELECT * FROM users LIMIT 1;
當取得多筆資料時,很直覺覺得可以使用
all
接著.each
方法,但是!這樣可能會導致一次將整個資料表的資料提取出來,並存放在記憶體中,這樣的做法當資料量很大時,很容易超過記憶體的負荷。
# 將整個資料表的資料全部加載到記憶體中
User.all.each do |user|
puts user.name
end
為了避免這些問題,Active Record 提供 find_each
或 find_in_batches
方法,可以批次處理記錄,
而不是一次性將所有記錄載入記憶體,以便更有效地處理大量數據。這樣可以減輕記憶體負荷並提高效能。
方法:find_each
, find_in_batches
find_eachfind_each
按批次檢索記錄,並遍歷紀錄。這對於處理大量記錄非常有用,因為每次只加載一批記錄,而不是全部加載到內存中。
# 檢索所有用戶記錄,每次處理 1000 條記錄
User.find_each(batch_size: 1000) do |user|
# 在這裡對每個用戶記錄進行操作
puts user.name
end
Options 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_batchesfind_in_batches
方法類似於 find_each
,按批次檢索記錄,但不提供遍歷功能。返回的是一個包含每批記錄的集合,可以透過這個集合並處理每批記錄。
# 檢索所有用戶記錄,每次處理 1000 條記錄
User.find_in_batches(batch_size: 1000) do |batch|
# 在這裡處理每批記錄
batch.each do |user|
puts user.name
end
end
Options for
find_in_batches
::batch_size
,:start
,:finish
,:error_on_ignore
這兩種方法都有助於減少大量數據查詢時的性能問題,特別是當記錄數量很龐大時,透過分批處理,可以更有效地管理記憶體使用,從而提高應用程式的效能。
where
方法接收一個條件可以使用 字串、陣列、物件(key : value)作為參數,並返回所有符合條件的查詢對象,是一個 ActiveRecord 查詢集合(Relation)。
如果找不到紀錄,會是一個空的 ActiveRecord 查詢結果 [ ]
。
方法: where
Pure String Conditions 純字串條件
用戶可以通過輸入關鍵字來搜索產品的名稱:
keyword = params[:keyword] # 使用者輸入的搜索關鍵字
Product.where("name LIKE '%#{keyword}%'") # 不推薦的寫法,容易有 SQL injection 的風險
將使用者輸入的 keyword 直接插入到 SQL 字符中,這樣的做法可能導致 SQL injection!
SQL injection 是一個安全漏洞,攻擊者可以通過在 SQL 查詢中插入惡意代碼,從而對數據庫進行未授權的操作。
在 Rails 中,防止 SQL injection 的方式可以透過使用 Array Conditions(陣列條件)
Array Conditions 陣列條件
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 來構建查詢條件:
conditions = "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 萬用字元),即
%
和_
下,不會轉義,這可能會導致意外行為。例如:
Book.where("title LIKE ?", params[:title] + "%")
可以通過 sanitize_sql_like
來解決:
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 (相等性)
User.where(name: "viii")
生成的 SQL 查詢:SELECT * FROM users WHERE (users.name = 'viii')
'name'
指定為 "viii"。
User.where('name' => "viii")
User
有一個 belongs_to
關係,指向 Country
模型,country = Country.find_by(name: "USA")
User.where(country: country)
User
表具有複合主鍵,由 country_id
和 user_id
兩個列組成,User.where([:country_id, :user_id] => [[1, 101], [2, 202]])
Range (範圍)
Product.where(price: 10..50) # 返回所有價格在 10 到 50 之間的產品記錄。
生成的 SQL 查詢:SELECT * FROM products WHERE (products.price BETWEEN 10 AND 50)
Subset (子集)
User.where(name: ["Alice", "Bob", "Charlie"])
生成的 SQL 查詢:SELECT * FROM users WHERE (users.name IN ('Alice', 'Bob', 'Charlie'))
NOT, OR, AND Conditions
Active Record 提供了多種方法來構建 SQL 查詢的 NOT
、OR
和 AND
。
where.not
User.where.not(name: "John")
生成的 SQL 查詢:SELECT * FROM users WHERE NOT (users.name = 'John')
or
User.where(name: "Alice").or(User.where(name: "Bob"))
生成的 SQL 查詢:SELECT * FROM users WHERE (users.name = 'Alice' OR users.name = 'Bob')
where
條件,多個條件之間是 AND 關係。
User.where(name: "Alice", age: 25)
生成的 SQL 查詢:SELECT * FROM users WHERE (users.name = 'Alice' AND users.age = 25)
User.order(age: :asc) # 升冪排序
User.order(age: :desc) # 降冪排序
- 多次使用 `order`
```
User.order(:name).order(age: :desc)
```
生成的 SQL 查詢:`SELECT * FROM users ORDER BY users.name ASC, users.age DESC`
可以指定想要從資料庫中檢索的特定欄位,而不是檢索整個資料表,達到效能優化。
方法:select
, distinct
- 假設有一個 `User` 模型,包含了 `name`、`email` 和 `age` ,但只想查 `name` 和 `email`:
```ruby
User.select(:name, :email)
```
生成的 SQL 查詢:`SELECT name, email FROM users`
>要小心使用 `select`。因為實體化出來的物件僅有所選欄位。
>如果試圖存取不存在的欄位,會得到 ActiveModel::MissingAttributeError 異常:
>`ActiveModel::MissingAttributeError: missing attribute: <attribute>`
- 如果想要僅選擇某個欄位中每個唯一值對應的一條記錄,可以使用 `distinct`
```ruby
User.select(:name).distinct
```
生成的 SQL 查詢:`SELECT DISTINCT name FROM users`
Limit
和 Offset
是用於在 SQL 查詢中控制結果集大小和選擇的兩個重要參數。
- Limit(限制):Limit
用於限制查詢結果集的大小,指定了要返回的記錄數量。
- Offset(偏移):Offset
用於指定從查詢結果集的開頭位置開始返回記錄。允許跳過前幾條記錄,以便從指定位置開始檢索記錄。
繼續用 `User` 模型來講解,想查詢所有用戶的名字,但每次僅返回前五個名字,並且跳過前十個名字,這樣你可以從第十一個名字開始繼續查詢,這就可以使用 `limit` 和 `offset` 選項來實現這個目的:
```ruby
User.select(:name).limit(5).offset(10)
```
這個查詢將選擇 `User` 表中的 `name` 字段,然後使用 `limit(5)` 來指定返回前五個名字,使用 `offset(10)` 來指定從第十一個名字開始返回。
生成的 SQL 查詢:`SELECT name FROM users LIMIT 5 OFFSET 10`
>這兩個通常在分頁(pagination)中使用,在列表中顯示一部分記錄,然後允許用戶查看更多的記錄。
find
、find_by
、first
、last
、take
。find_each
或 find_in_batches
以提高效能。where
方法可以建立條件搜尋,避免 SQL 注入攻擊。可以使用純字串、陣列、物件、或哈希來構建條件。 - NOT
、OR
、AND
條件也可輕鬆應用。order
、select
、distinct
、limit
和 offset
。瞭解這些方法可以更靈活地操作資料庫中的資料,今天先到這!明日再續,下篇見!
參考資料:
文章同步於個人部落格:Viiisit!(歡迎參觀 ୧ʕ•̀ᴥ•́ʔ୨)