【问题标题】:How to make ActiveRecord query unique by a column如何使 ActiveRecord 查询按列唯一
【发布时间】:2017-10-11 12:54:03
【问题描述】:

我有一个 Company 模型,其中包含许多 DisclosuresDisclosure 具有名为 titlepdfpdf_sha256 的列。

class Company < ActiveRecord::Base
  has_many :disclosures
end

class Disclosure < ActiveRecord::Base
  belongs_to :company
end

我想通过pdf_sha256 使其独一无二,如果pdf_sha256nil,则应将其视为唯一。

如果是Array,我会这样写。

companies_with_sha256 = company.disclosures.where.not(pdf_sha256: nil).group_by(&:pdf_sha256).map do |key,values|
  values.max_by{|v| v.title.length}
end
companies_without_sha256 = company.disclosures.where(pdf_sha256: nil)
companies = companies_with_sha256 + companeis_without_sha256

如何使用 ActiveRecord 查询获得相同的结果?

【问题讨论】:

标签: ruby-on-rails postgresql activerecord


【解决方案1】:

可以通过以下方式在一个查询中执行此操作:首先为每个不同的pdf_sha256 获取不同的id 作为子查询,然后在查询中通过传递子查询获取该组 id 中的元素,如下所示:

def unique_disclosures_by_pdf_sha256(company)
  subquery = company.disclosures.select('MIN(id) as id').group(:pdf_sha256)
  company.disclosures.where(id: subquery)
    .or(company.disclosures.where(pdf_sha256: nil))
end

这样做的好处是 ActiveRecord 是延迟加载的,因此第一个 subquery 将不会运行,并将合并到第二个主查询中以在数据库中创建单个查询。然后,它将检索由pdf_sha256 唯一的所有disclosures 以及所有将pdf_sha256 设置为nilnil

如果您好奇,给定一家公司,结果查询将类似于:

SELECT  "disclosures".* FROM "disclosures" 
WHERE (
  "disclosures"."company_id" = $1 AND "disclosures"."id" IN (
    SELECT MAX(id) as id FROM "disclosures" WHERE "disclosures"."company_id" = $2 GROUP BY "disclosures"."pdf_sha256"
  ) 
  OR "disclosures"."company_id" = $3 AND "disclosures"."pdf_sha256" IS NULL
)

这个解决方案的好处是返回的值是一个 ActiveRecord 查询,所以在你真正需要之前不会加载它。您还可以使用它来保持链接查询。例如,您可以只选择id 而不是整个模型,并限制数据库返回的结果数:

unique_disclosures_by_pdf_sha256(company).select(:id).limit(10).each { |d| puts d }

【讨论】:

  • 如果我不能做 MAX(id) 怎么办,因为我有 UUID 值的 ID-s。我尝试了select('MAX(created_at) as created_at').group(:feed_type),但我仍然需要 ID-s 来通过 ID-s 进行下一个查询和搜索记录。
【解决方案2】:

你可以通过使用 uniq 方法来实现这一点

 Company.first.disclosures.to_a.uniq(&:pdf_sha256)

这将通过 cloumn "pdf_sha256" 向您返回披露记录 uniq

希望对您有所帮助!干杯

【讨论】:

  • ActiveRecord::QueryMethods#uniq 只是ActiveRecord::QueryMethods#distinct 的别名。因此,该方法在这种情况下不起作用。我认为您与Array#uniq 混淆了。您的代码适用于 to_a 方法,例如 company.disclosures.to_a.uniq(&amp;:pdf_sha256)
  • 谢谢@ironsand。我已经编辑了我的答案。希望这对您有所帮助。
【解决方案3】:

假设您使用的是 Rails 5,您可以链接一个 .or 命令来合并您的两个查询。

pdf_sha256_unique_disclosures = company.disclosures.where(pdf_sha256: nil).or(company.disclosures.where.not(pdf_sha256: nil))

然后你可以继续你的 group_by 逻辑。

但是,在上面的示例中,我不确定目标是什么,但我很想更好地了解您将如何使用生成的 companies 变量。

如果您想要一个包含 nil 在内的唯一 pdf_sha256 键的哈希,以及由此产生的唯一披露文档,您可以尝试以下操作:

sorted_disclosures = company.disclosures.group_by(&:pdf_sha256).each_with_object({}) do |entries, hash|
  hash[entries[0]] = entries[1].max_by{|v| v.title.length}
end

这应该会为您提供类似于 group_by 的结果哈希结构,其中您的键都是您唯一的 pdf_sha256,并且值将是与该 pdf_sha256 匹配的最长命名披露。

【讨论】:

    【解决方案4】:

    为什么不:

    ids = Disclosure.select(:id, :pdf_sha256).distinct.map(&:id)
    Disclosure.find(ids)
    

    id 仍然是不同的,因为它是主键,所以您所要做的就是映射 id 并按 id 查找披露。

    【讨论】:

    • 我想通过pdf_sha256 的唯一性过滤disclosure 模型的所有列。
    【解决方案5】:

    如果您需要具有不同 pdf_sha256 的关系,而您不需要显式条件,则可以使用 group -

    scope :unique_pdf_sha256, -> { where.not(pdf_sha256: nil).group(:pdf_sha256) }
    scope :nil_pdf_sha256,    -> { where(pdf_sha256: nil) }
    

    您可以使用or,但传递给它的关系必须在结构上兼容。因此,即使您在这两个范围内获得相同类型的关系,也不能将其与or 一起使用。

    编辑:为了使其在结构上相互兼容,您可以查看@AlexSantos 的answer

    【讨论】:

      【解决方案6】:
      Model.select(:rating)
      

      结果是一个模型对象数组。不是普通的评级。而从uniq的角度来看,它们是完全不同的。你可以使用这个:

      Model.select(:rating).map(&:rating).uniq
      

      或者这个(最有效的)

      Model.uniq.pluck(:rating)
      
      Model.distinct.pluck(:rating)
      

      更新

      显然,从 rails 5.0.0.1 开始,它只适用于“顶级”查询,就像上面一样。不适用于集合代理(例如,“has_many”关系)。

      Address.distinct.pluck(:city) # => ['Moscow']
      user.addresses.distinct.pluck(:city) # => ['Moscow', 'Moscow', 'Moscow']
      

      在这种情况下,查询后去重

      user.addresses.pluck(:city).uniq # => ['Moscow']
      

      【讨论】:

      • 在您的示例中,我想获取addresses 的所有列,而不仅仅是city 列。
      猜你喜欢
      • 2011-10-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-03-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多