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_idtaggings.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 []数组中的所有内容一起使用?

展平 3D NumPy 数组中的内部元组并作为浮点数保存到 CSV

Rails 中的自定义字段作为未来条目的模板

SQL:选择 * where 属性匹配数组中的所有项目

防止数组中的双重条目[重复]