Rails: joins的使用

李威辰
李威辰
Sep 2, 2018 · 6 min read

在Rails專案中常常需要撈關聯的資料,或者是用關聯資料的欄位當成判斷條件來撈需要的資料。ActiveRecord的joins方法將SQL的INNER JOIN包裝起來,讓我們能夠用DSL的方式方便的使用關聯的語法來操作。本篇文章介紹使用joins的一些小訣竅以及需要注意的地方。本篇文章大部分的程式碼來自Rails Guide的官方文件。

使用純SQL語法

joins方法可以直接在參數裡面使用SQL的語法,當某些query條件太複雜的時候就得寫純SQL來處理。

Author.joins("INNER JOIN posts ON posts.author_id = authors.id AND posts.published = 't'")# => SELECT authors.* FROM authors INNER JOIN posts ON posts.author_id = authors.id AND posts.published = 't'

使用在model定義好的關聯方法

下面設定的關聯是本篇文章假設存在的table間的關聯。

class Category < ApplicationRecord
has_many :articles
end

class Article < ApplicationRecord
belongs_to :category
has_many :comments
has_many :tags
end

class Comment < ApplicationRecord
belongs_to :article
has_one :guest
end

class Guest < ApplicationRecord
belongs_to :comment
end

class Tag < ApplicationRecord
belongs_to :article
end

設定好之後,我們就可以利用ActiveRecord提供的DSL來做join table。如果我們想要將categories table 與 articles table 做INNER JOIN,可以這樣寫:

categories = Category.joins(:articles)
# => SELECT categories.* FROM categories
INNER JOIN articles ON articles.category_id = categories.id

要特別注意的地方有幾個:

  1. 當我們join的table是屬於has_many關係的時候,join出來的表裡面是會有重複的row存在的。以上面的例子來說,當category有十筆articles屬於它的時候,上面的程式碼便會撈出十筆重複的資料出來。如果要避免撈出重複的資料的話,我們可以使用distinct方法: Category.joins(:articles).distinct
  2. 使用joins方法並不會將join的表格的資料也撈出來(看產生的SQL就可略知一二: SELECT categories.*),只會撈原本的table資料,如果你需要另一個表的資料的話,請使用includes
  3. 如果沒有任何關聯資料存在的話,便不會有任何資料被撈出來。以上面的例子來說,如果某筆category沒有任何一筆article的話便不會被撈出來,如果不管有沒有任何關聯資料都要撈出category的話請使用left_outer_joins

一次join很多個關聯

如果想要一次join很多關聯的話,joins方法也可以用傳入多個參數的方式來辦到。

Article.joins(:category, :comments)# => SELECT articles.* FROM articles
INNER JOIN comments ON comments.article_id = articles.id
INNER JOIN guests ON guests.comment_id = comments.id

用以上的寫法便可以同時join兩個關聯了(articles join categories, articles join comments)。

join兩層以上的關聯

在專案裡面撈資料的時候常常會遇到需要撈到兩層以上的資料,這時候可以使用下面的語法:

Article.joins(comments: :guest)
# => SELECT articles.* FROM articles
INNER JOIN comments ON comments.article_id = articles.id
INNER JOIN guests ON guests.comment_id = comments.id

這邊的寫法代表的意思是先將articles與comments join起來,再將comments join guests。

當然大於兩層的關聯也是相當常見的,這時候只要用hash的寫法寫下去就可以了。

Category.joins(articles: {comments: guest})

第二層的join也要與多個table關聯的話呢?

Category.joins(articles: [{ comments: :guest }, :tags])# => SELECT categories.* FROM categories
INNER JOIN articles ON articles.category_id = categories.id
INNER JOIN comments ON comments.article_id = articles.id
INNER JOIN guests ON guests.comment_id = comments.id
INNER JOIN tags ON tags.article_id = articles.id

上面程式碼的重點在articles也需要跟另外兩個以上的table join的話我們可以傳入陣列形式的參數來做到。上面程式碼的例子是先將categories與articles join後,articles會與comments以及tags join,最後comments會再跟guests join再一起。

參考資料:

https://guides.rubyonrails.org/active_record_querying.html#joins

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade