我正在使用 acts-as-taggable-on 并且我想搜索模型或其关联中可能存在的标签。
class Event < ApplicationRecord
acts_as_taggable_on :mentions
belongs_to :establishment
end
class Establishment < ApplicationRecord
acts_as_taggable_on :hashtags
end
我正在尝试使用标签值上的
LIKE
进行搜索。我对连接的 ActiveRecord
命名感到困惑。
Event.left_joins([:mentions]).to_sql
#=> SELECT "events".* FROM "events" LEFT OUTER JOIN "taggings" ON "taggings"."taggable_type" = 'Event' AND "taggings"."context" = 'mentions' AND "taggings"."taggable_id" = "events"."id" LEFT OUTER JOIN "tags" ON "tags"."id" = "taggings"."tag_id"
# name is "tags" for "mentions"
Event.left_joins([:mentions], {:establishment=>[:hashtags]}).to_sql
#=> SELECT "events".* FROM "events" LEFT OUTER JOIN "taggings" ON "taggings"."taggable_type" = 'Event' AND "taggings"."context" = 'mentions' AND "taggings"."taggable_id" = "events"."id" LEFT OUTER JOIN "tags" ON "tags"."id" = "taggings"."tag_id" LEFT OUTER JOIN "establishments" ON "establishments"."id" = "events"."establishment_id" LEFT OUTER JOIN "taggings" "hashtag_taggings_establishments_join" ON "hashtag_taggings_establishments_join"."taggable_type" = 'Establishment' AND "hashtag_taggings_establishments_join"."context" = 'hashtags' AND "hashtag_taggings_establishments_join"."taggable_id" = "establishments"."id" LEFT OUTER JOIN "tags" "hashtags_establishments" ON "hashtags_establishments"."id" = "hashtag_taggings_establishments_join"."tag_id"
# name is "tags" for "mentions" and "hashtags_establishments" for hashtags in establishment
Event.left_joins({:establishment=>[:hashtags]}).to_sql
#=> SELECT "events".* FROM "events" LEFT OUTER JOIN "establishments" ON "establishments"."id" = "events"."establishment_id" LEFT OUTER JOIN "taggings" ON "taggings"."taggable_type" = 'Establishment' AND "taggings"."context" = 'hashtags' AND "taggings"."taggable_id" = "establishments"."id" LEFT OUTER JOIN "tags" ON "tags"."id" = "taggings"."tag_id"
# name is "tags" for "hashtags" in establishment
如果只有一个连接,则文本值在
tags.name
中,但如果有多个连接,就我而言,它是 "#{tag_association}_#{model_name.pluralize}.name"
。
我想强制一个名称 100% 确定我正在搜索的位置,而不必计算我必须猜测该名称的连接数。
我喜欢这篇文章,它解释了连接的左外部 https://www.ananunesdasilva.com/posts/understanding-activerecord-left-outer-joins
了解左外连接及其应用
在本文中,我们将深入研究左外连接(或简称左连接)的概念并探讨其用例。左外连接允许我们根据相关表中的匹配和不匹配条目来查询表。与仅返回匹配条目的内连接不同,左外连接始终包含左表中的所有条目,即使右表中没有相应的匹配项也是如此。
为了说明这一点,让我们参考一个涉及两个类的域模型:住宿和预订。一个住宿可以有多个预订,每个预订都属于一个住宿。
以下是两个表中的示例记录:
住宿 |身份证 |名称 | |----|----------------| | 1 | “海滨别墅”| | 2 | “迷人的房子”| | 3 | “里斯本公寓”| | 4 | “小木屋”| | 5 | “农舍”|
预订 |身份证 |住宿 ID |入住 |退房 | |----|--------------------|-------------|------------ --| | 1 | 1 | 2023 年 6 月 20 日 | 2023 年 6 月 23 日 | | 2 | 1 | 2023 年 8 月 7 日 | 2023 年 8 月 8 日 | | 3 | 2 | 2023 年 5 月 22 日 | 2023 年 5 月 28 日 | | 4 | 4 | 2023 年 4 月 1 日 | 2023 年 4 月 2 日 |
获取所有住宿及其预订数
要检索住宿列表及其预订数量,我们需要在两个表之间执行联接。最初,使用 ActiveRecord 的
joins
方法,相应的 SQL 查询会产生 INNER JOIN:
Accommodation.joins(:bookings)
等效的SQL:
SELECT "accommodations".* FROM "accommodations" INNER JOIN "bookings" ON "bookings"."id" = "accommodations"."booking_id"
此 INNER JOIN 仅返回具有匹配预订的住宿。因此,“里斯本公寓”和“农舍”被排除在结果之外,因为它们没有关联的预订。
即使没有预订,也要包含住宿,我们可以采用
left_joins
(或 left_outer_joins
)方法:
Accommodation.left_joins(:bookings)
这会生成以下 SQL:
SELECT "accommodations".* FROM "accommodations" LEFT OUTER JOIN "bookings" ON "bookings"."id" = "accommodations"."booking_id"
带有左外连接的结果表将如下所示:
住宿.id | 住宿.名称 | 预订.id | bookings.accommodation_id | 预订.check_in | 预订.check_out |
---|---|---|---|---|---|
1 | “海滨别墅” | 1 | 1 | 2021 年 6 月 20 日 | 2021 年 6 月 23 日 |
1 | “海滨别墅” | 2 | 1 | 2021 年 8 月 7 日 | 2021 年 8 月 8 日 |
2 | “迷人的房子” | 3 | 2 | 2021 年 5 月 22 日 | 2021 年 5 月 28 日 |
3 | '里斯本公寓' | 空 | 空 | 空 | 空 |
4 | “小木屋” | 4 | 4 | 2021 年 4 月 1 日 | 2021 年 4 月 2 日 |
5 | “农舍” | 空 | 空 | 空 | 空 |
如上所示,左外连接包括所有住宿,甚至包括那些没有预订的住宿。住宿“Lisbon Flat”和“Farm House”出现在结果中,与预订相关的属性设置为“null”。
计算每个住宿的预订量
要获取每个住宿的预订数量,我们可以将 ActiveRecord 的
group
和 count
方法与左外连接结合使用:
Accommodation.left_joins(:bookings).group(:name).count('bookings.id')
此 SQL 查询计算所需的输出,提供一个哈希值,其中住宿名称作为键,相应的预订计数作为值。即使没有预订的住宿也会出现在结果中,显示计数为 0。
{"Beach House"=>2, "Charming House"=>1, "Lisbon Flat"=>0, "Le Petit Chalet"=>1, "Farm House"=>0}
使用 SQL 字符串自定义联接
left_joins
和 left_outer_joins
方法从 Rails 版本 5.2.3 开始可用。对于早期的 Rails 版本,需要使用 SQL 字符串自定义联接。例如:
Accommodation.joins('LEFT OUTER JOIN bookings ON bookings.accommodation_id = accommodations.id')
无需预订即可获得住宿
要仅检索没有预订的住宿,我们可以使用
where
方法根据预订表中的空值过滤左外连接的结果:
Accommodation.left_joins(:bookings).where(bookings: { id: nil })
SQL:
SELECT COUNT(*) FROM "accommodations" LEFT OUTER JOIN "bookings" ON "bookings"."accommodation_id" = "accommodations"."id" WHERE "bookings"."id" IS NULL
输出:
身份证 | 姓名 |
---|---|
3 | '里斯本公寓' |
5 | “农舍” |
对于 Rails 6 及更高版本,可以使用 ActiveRecord 的
missing
方法实现同一解决方案的更多语义版本:
Accommodation.where.missing(:bookings)
在本系列的下一部分中,我们将探索在单个查询中组合多个联接。