v7.1.3.2
更多資訊請至 rubyonrails.org: 更多 Ruby on Rails

複合主鍵

本指南是資料庫表格複合主鍵的簡介。

閱讀本指南後,您將能夠

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_bywhere 等方法的條件時,使用 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_idid。提交後,控制器可以從參數中擷取主鍵值並更新記錄。請參閱下一部分以取得更多詳細資訊。

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 文件進行任何類型的討論。