如何使 ActiveRecord 查询按列唯一

iro*_*and 5 postgresql activerecord ruby-on-rails

我有一个Company模型有很多Disclosures. 将Disclosure有一个名为列titlepdfpdf_sha256

class Company < ActiveRecord::Base
  has_many :disclosures
end

class Disclosure < ActiveRecord::Base
  belongs_to :company
end
Run Code Online (Sandbox Code Playgroud)

我想让它独特的pdf_sha256,如果pdf_sha256就是nil应该被视为是唯一的。

如果是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
Run Code Online (Sandbox Code Playgroud)

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

Ale*_*tos 2

可以在一个查询中完成此操作,首先id为每个不同的元素获取不同的pdf_sha256元素作为子查询,然后在查询中通过传递子查询来获取该 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
Run Code Online (Sandbox Code Playgroud)

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

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

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
)
Run Code Online (Sandbox Code Playgroud)

该解决方案的优点在于,返回的值是一个 ActiveRecord 查询,因此只有在您真正需要时才会加载它。您还可以使用它来保持链接查询。例如,您可以仅选择而id不是整个模型并限制数据库返回的结果数量:

unique_disclosures_by_pdf_sha256(company).select(:id).limit(10).each { |d| puts d }
Run Code Online (Sandbox Code Playgroud)