1 什麼是複合主鍵?
有時單一欄位的值不足以唯一識別資料表的每一列,需要兩個或更多欄位的組合。這可能是使用沒有單一 id
欄位作為主鍵的舊式資料庫架構,或在分片或多租戶中變更架構時發生的情況。
複合主鍵會增加複雜性,而且比單一主鍵欄位慢。在使用複合主鍵之前,請確保你的使用案例需要複合主鍵。
2 複合主鍵遷移
你可以透過將 :primary_key
選項傳遞給 create_table
,並使用陣列值來建立具有複合主鍵的資料表
class CreateProducts < ActiveRecord::Migration[7.1]
def change
create_table :products, primary_key: [:store_id, :sku] do |t|
t.integer :store_id
t.string :sku
t.text :description
end
end
end
3 查詢模型
3.1 使用 #find
如果你的資料表使用複合主鍵,則在使用 #find
尋找記錄時,你需要傳遞一個陣列
# Find the product with store_id 3 and sku "XYZ12345"
irb> product = Product.find([3, "XYZ12345"])
=> #<Product store_id: 3, sku: "XYZ12345", description: "Yellow socks">
上述內容的 SQL 等效項為
SELECT * FROM products WHERE store_id = 3 AND sku = "XYZ12345"
若要使用複合 ID 尋找多筆記錄,請將陣列陣列傳遞給 #find
# Find the products with primary keys [1, "ABC98765"] and [7, "ZZZ11111"]
irb> products = Product.find([[1, "ABC98765"], [7, "ZZZ11111"]])
=> [
#<Product store_id: 1, sku: "ABC98765", description: "Red Hat">,
#<Product store_id: 7, sku: "ZZZ11111", description: "Green Pants">
]
上述內容的 SQL 等效項為
SELECT * FROM products WHERE (store_id = 1 AND sku = 'ABC98765' OR store_id = 7 AND sku = 'ZZZ11111')
具有複合主鍵的模型在排序時也會使用完整的複合主鍵
irb> product = Product.first
=> #<Product store_id: 1, sku: "ABC98765", description: "Red Hat">
上述內容的 SQL 等效項為
SELECT * FROM products ORDER BY products.store_id ASC, products.sku ASC LIMIT 1
3.2 使用 #where
#where
的雜湊條件可以用類似元組的語法指定。這對於查詢複合主鍵關係很有用
Product.where(Product.primary_key => [[1, "ABC98765"], [7, "ZZZ11111"]])
3.2.1 具有 :id
的條件
在指定 find_by
和 where
等方法的條件時,使用 id
將與模型上的 :id
屬性相符。這與 find
不同,其中傳入的 ID 應該是主鍵值。
在 :id
不是主鍵的模型(例如複合主鍵模型)上使用 find_by(id:)
時請小心。請參閱 Active Record 查詢 指南以了解更多資訊。
4 具有複合主鍵的模型之間的關聯
Rails 通常能夠推論具有複合主鍵關聯模型之間的主鍵 - 外來鍵資訊,而不需要額外的資訊。請看以下範例
class Order < ApplicationRecord
self.primary_key = [:shop_id, :id]
has_many :books
end
class Book < ApplicationRecord
belongs_to :order
end
在這裡,Rails 假設 :id
欄位應當用作訂單與其書籍之間關聯的主鍵,就像一般 has_many
/ belongs_to
關聯一樣。它會推論 books
表格上的外來鍵欄位是 :order_id
。存取書籍的訂單
order = Order.create!(id: [1, 2], status: "pending")
book = order.books.create!(title: "A Cool Book")
book.reload.order
將會產生以下 SQL 來存取訂單
SELECT * FROM orders WHERE id = 2
這僅在模型的複合主鍵包含 :id
欄位時才有效,而且 該欄位對於所有記錄都是唯一的。若要使用關聯中的完整複合主鍵,請設定關聯上的 query_constraints
選項。此選項指定關聯上的複合外來鍵,表示外來鍵中的所有欄位都將用於查詢關聯的記錄。例如
class Author < ApplicationRecord
self.primary_key = [:first_name, :last_name]
has_many :books, query_constraints: [:first_name, :last_name]
end
class Book < ApplicationRecord
belongs_to :author, query_constraints: [:author_first_name, :author_last_name]
end
存取書籍的作者
author = Author.create!(first_name: "Jane", last_name: "Doe")
book = author.books.create!(title: "A Cool Book")
book.reload.author
將在 SQL 查詢中使用 :first_name
和 :last_name
SELECT * FROM authors WHERE first_name = 'Jane' AND last_name = 'Doe'
5 個複合主鍵模型的表單
也可以為複合主鍵模型建立表單。請參閱 表單輔助程式 指南,以取得有關表單建構器語法的更多資訊。
給定具有複合鍵 [:author_id, :id]
的 @book
模型物件
@book = Book.find([2, 25])
# => #<Book id: 25, title: "Some book", author_id: 2>
以下表單
<%= form_with model: @book do |form| %>
<%= form.text_field :title %>
<%= form.submit %>
<% end %>
輸出
<form action="/books/2_25" method="post" accept-charset="UTF-8" >
<input name="authenticity_token" type="hidden" value="..." />
<input type="text" name="book[title]" id="book_title" value="My book" />
<input type="submit" name="commit" value="Update Book" data-disable-with="Update Book">
</form>
請注意,產生的 URL 包含以底線分隔的 author_id
和 id
。提交後,控制器可以從參數中擷取主鍵值並更新記錄。請參閱下一部分以取得更多詳細資訊。
6 複合鍵參數
複合鍵參數在一個參數中包含多個值。因此,我們需要能夠擷取每個值並將它們傳遞給 Active Record。我們可以利用 extract_value
方法來進行此用例。
給定以下控制器
class BooksController < ApplicationController
def show
# Extract the composite ID value from URL parameters.
id = params.extract_value(:id)
# Find the book using the composite ID.
@book = Book.find(id)
# use the default rendering behaviour to render the show view.
end
end
以及下列路由
get '/books/:id', to: 'books#show'
當使用者開啟 URL /books/4_2
時,控制器會萃取複合鍵值 ["4", "2"]
並將其傳遞給 Book.find
,以在檢視中呈現正確的記錄。extract_value
方法可用於從任何分隔參數中萃取陣列。
7. 複合主鍵固定裝置
複合主鍵資料表的固定裝置與一般資料表相當類似。使用 id 欄位時,該欄位可以照常省略
class Book < ApplicationRecord
self.primary_key = [:author_id, :id]
belongs_to :author
end
# books.yml
alices_adventure_in_wonderland:
author_id: <%= ActiveRecord::FixtureSet.identify(:lewis_carroll) %>
title: "Alice's Adventures in Wonderland"
然而,為了支援複合主鍵關係,您必須使用 composite_identify
方法
class BookOrder < ApplicationRecord
self.primary_key = [:shop_id, :id]
belongs_to :order, query_constraints: [:shop_id, :order_id]
belongs_to :book, query_constraints: [:author_id, :book_id]
end
# book_orders.yml
alices_adventure_in_wonderland_in_books:
author: lewis_carroll
book_id: <%= ActiveRecord::FixtureSet.composite_identify(
:alices_adventure_in_wonderland, Book.primary_key)[:id] %>
shop: book_store
order_id: <%= ActiveRecord::FixtureSet.composite_identify(
:books, Order.primary_key)[:id] %>
回饋
我們鼓勵您協助提升本指南的品質。
如果您發現任何錯字或事實錯誤,請務必提供協助。首先,您可以閱讀我們的 文件貢獻 區段。
您也可能會發現不完整或過時的內容。請務必為 main 新增任何遺漏的文件。請務必先查看 Edge Guides,以驗證問題是否已在主分支中修復。查看 Ruby on Rails 指南準則 以了解風格和慣例。
如果您發現需要修復但無法自行修補的任何問題,請 開啟問題。
最後但並非最不重要的一點是,我們非常歡迎在 官方 Ruby on Rails 論壇 上針對 Ruby on Rails 文件進行任何類型的討論。