Rails 和 SQL - 与数组、条目中的所有元素相关联
Posted
技术标签:
【中文标题】Rails 和 SQL - 与数组、条目中的所有元素相关联【英文标题】:Rails and SQL - get related by all elements from array, entries 【发布时间】:2021-11-07 15:52:08 【问题描述】:我有这样的事情:
duplicates = ['a','b','c','d']
if duplicates.length > 4
Photo.includes(:tags).where('tags.name IN (?)',duplicates)
.references(:tags).limit(15).each do |f|
returned_array.push(f.id)
end
end
duplicates 是与其他Photo
标记重复的标记数组
我想要的是获得包含 duplicates 数组中所有标签的Photo
,但现在我得到每个包含数组中至少一个标签的Photo
。
感谢您的回答:
我尝试了它们,有些东西开始起作用,但对我来说不是太清楚,需要一些时间来执行。
今天我让它创建数组,比较它们,获取数组中存在超过 X 次的重复项,最后获得 uniq 的照片 id 数组。
【问题讨论】:
或许可以加group(:id).having(Photo.arel_table[:id].count.eq(duplicates.length))
。
Photo 和 Tag 有 HABTM 关系吗?
照片有很多标签,标签belongs_to photo
尝试组并拥有但没有 arel_table ... 必须阅读它
数据库是mysql还是Postgres?
【参考方案1】:
我看到的问题是您只进行一次连接,这意味着您必须指定 tags.name
在重复列表中。
你可以在两个地方解决这个问题:
在数据库中查询 在您的应用程序代码中对于您的示例,查询类似于“查找照片表中的所有记录,这些记录也与标签表中的特定记录集有关系”。所以我们需要将 photos 表加入到 tags 表中,同时指定我们加入的唯一标签是重复列表中的标签。
我们可以为此使用内连接
select photos.* from photos
inner join tags as d1 on d1.name = 'a' and d1.photo_id = photos.id
inner join tags as d2 on d2.name = 'b' and d2.photo_id = photos.id
inner join tags as d3 on d3.name = 'c' and d3.photo_id = photos.id
inner join tags as d4 on d4.name = 'd' and d4.photo_id = photos.id
在 ActiveRecord 中,我们似乎不能为连接指定别名,但我们可以链接查询,所以我们可以这样做:
query = Photo
duplicate.each_with_index do |tag, index|
join_name = "d#index"
query = query.joins("inner join tags as #join_name on #join_name.name = '#tag' and #join_name.photo_id = photos.id")
end
丑陋,但可以完成工作。我确信使用arel
会有更好的方法 - 但它演示了如何构造一个 SQL 查询来查找与所有重复标签相关的所有照片。
另一种方法是扩展您拥有的内容并在应用程序中进行过滤。由于您已经拥有至少具有一个标签的照片,您可以选择具有所有标签的照片。
Photo
.includes(:tags)
.joins(:tags)
.where('tags.name IN (?)',duplicates)
.select do |photo|
(duplicates - photo.tags.map(&:name)).empty?
end
(duplicates - photo.tags.map(&:name)).empty?
采用重复数组并删除所有出现在照片标签中的任何项目。如果这返回一个空数组,那么我们知道照片中的标签也有所有重复的标签。
如果重复数组很大,这可能会出现性能问题,因为它可能会返回数据库中的所有照片。
【讨论】:
这将创建一个非常长的无法缓存的 SQL 字符串和 N 个连接数。我不会推荐这种方法。【参考方案2】:如果您想查找具有所有给定标签的照片,您只需应用一个 GROUP 并使用 HAVING 为该组设置条件:
class Photo
def self.with_tags(*names)
t = Tag.arel_table
joins(:tags)
.where(tags: name: names )
.group(:id)
.having(t[:id].count.eq(tags.length)) # COUNT(tags.id) = ?
end
end
这有点像 WHERE 子句,但它适用于组。使用 .gteq
(>=
) 而不是 .eq
将为您提供可以在列表中包含所有标签但可能有更多标签的记录。
解决这个问题的更好方法是使用一个更好的域模型,首先不允许重复:
class Photo < ApplicationRecord
has_many :taggings
has_many :tags, through: :taggings
end
class Tag < ApplicationRecord
has_many :taggings
has_many :photos, through: :taggings
validates :name,
uniqueness: true,
presenece: true
end
class Tagging < ApplicationRecord
belongs_to :photo
belongs_to :tag
validates :tag_id,
uniqueness: scope: :photo_id
end
通过在tags.name
上添加唯一索引以及在taggings.tag_id
和taggings.photo_id
上添加复合索引,无法创建重复项。
【讨论】:
group(:id) 总是给出错误:ActionView::Template::Error (Mysql2::Error: SELECT 列表的表达式 #13 不在 GROUP BY 子句中,并且包含非聚合列 'database. tags.id' 在功能上不依赖于 GROUP BY 子句中的列;这与 sql_mode=only_full_group_by 不兼容): 您是否选择了不在照片表中的列?或者做一些像使用添加.includes
到范围的事情?正如您在这个小提琴sqlfiddle.com/#!9/ea3545/1 中看到的,这里提供的代码不是问题
感谢您撰写答案(我的评论有点含糊)。 @Wordica 遇到的问题是Server Mode,它使 MySQL 的行为类似于 MSSQL。选择中的每一列都必须在 GROUP BY 中或在聚合函数中使用。因此group(:id)
就足够了;但是您可以添加 select(:id)
并将其转换为子查询。以上是关于Rails 和 SQL - 与数组、条目中的所有元素相关联的主要内容,如果未能解决你的问题,请参考以下文章
MongoDB - 仅当嵌套数组中的所有条目存在时才更新它们
如何将redirect_to()与rails 3中当前params []数组中的所有内容一起使用?