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