多對多 N:N

關於多對多繼續用飲料店的例子舉例說明,可以想像我們很多間飲料店品牌,有很多飲料品項是相同的或不同,就會有需要有一張第三方表單來記錄:

  • 一間飲料店可以有很多種飲料
    • store has many drinks
  • 同一種飲料可以被很多間不同的飲料店販賣
    • drink has many stores

我們可以透過這張表單來知道,哪間店有賣哪些飲料,哪些飲料有被哪些店家販售。
他們之間的關係可以透過下面這張圖片看到,虛線的 has many 表示他們並不是真的直接有一對多的關聯,而是需要透過第三方表格來建立多對多關聯。

我們可以建立第三方的表格 drink_invest 來記錄飲料和店家之間的關係
rails g model drink_invest store:references drink:references

加上 reference 或是 belongs_to 都可以讓這個表格自動建立出對應的 store_id 及 drink_id 欄位,並且可以:

  • 自動加上索引(index),加快查詢
  • 自動幫 Model 加上 belongs_to

需要建立這樣的第三方表格來建立關聯,是屬於多對多的關聯
drink 和 store 之間沒有直接 has_many 的關聯, 但可以使用 has_many through 去透過 drink_invest 建立多對多的關聯。

接著可以把相關的 model 建立好關聯

store.rb

1
2
3
4
class Store < ApplicationRecord
has_many :drink_invests
has_many :drinks, through: :drink_invests
end

drink.rb

1
2
3
4
class Drink < ApplicationRecord
has_many :drink_invests
has_many :stores, through: :drink_invests
end

進入 rails console 試試看

先建立好我們需要的資料,店家、飲料、還有店家販售的飲料有哪些。

1
2
3
4
5
6
7
8
9
10
11
s1 = Store.create(name:"五石蘭", tel:"031234567", address:"新竹縣", owner_id:1)
s2 = Store.create(name:"渴不渴", tel:"03323232", address:"台北市", owner_id:1)

d1 = Drink.create(name: "紅茶", price: 30)
d2 = Drink.create(name: "綠茶", price: 30)
d3 = Drink.create(name: "珍珠奶茶", price: 60)

# 五石蘭 有賣 紅茶跟綠茶
s1.drinks = [d1, d2]
# 渴不渴 有賣 綠茶跟珍珠奶茶
s2.drinks = [d2, d3]

查詢相關的資訊之後會注意到 sql 語法的變化,開始透過第三方表格進行查詢。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 有販售 d1 綠茶有幾家
d1.stores.count
(0.6ms) SELECT COUNT(*) FROM "stores" INNER JOIN "drink_invests" ON "stores"."id" = "drink_invests"."store_id" WHERE "drink_invests"."drink_id" = ? [["drink_id", 1]]
=> 1

# 有販售 d2 紅茶有幾家
d2.stores.count
(0.5ms) SELECT COUNT(*) FROM "stores" INNER JOIN "drink_invests" ON "stores"."id" = "drink_invests"."store_id" WHERE "drink_invests"."drink_id" = ? [["drink_id", 2]]
=> 2

# 有販售 d3 奶茶有幾家
d3.stores.count
(0.4ms) SELECT COUNT(*) FROM "stores" INNER JOIN "drink_invests" ON "stores"."id" = "drink_invests"."store_id" WHERE "drink_invests"."drink_id" = ? [["drink_id", 3]]
=> 1

可以發現,這些查詢的 SQL 語法中,都已經不是直接跟 Store 或 Drink 要資料,而是向 drink_invest 進行查詢。例如第二項查詢在 s1 跟 s2 都有 d2 這個商品,所以是 2 家。

「多對多」關連,在使用上就跟一般的「一對多」很像,但實際上的資訊都是記錄在第三方資料表裡。


參考資料:

  1. 為你自己學 Ruby on Rails