如何计算具有一个或多个关联对象的记录数?

Posted

技术标签:

【中文标题】如何计算具有一个或多个关联对象的记录数?【英文标题】:How do I count the number of records that have one or more associated object? 【发布时间】:2019-06-11 08:48:05 【问题描述】:

我有一个Property 模型has_many :photos。我想统计拥有一张或多张照片的properties 的数量。

我该怎么做?

我尝试了简单的方法:

> Property.where('properties.photos.count > ?', 0).count

   (3.1ms)  SELECT COUNT(*) FROM "properties" WHERE (properties.photos.count > 1)
ActiveRecord::StatementInvalid: PG::UndefinedTable: ERROR:  missing FROM-clause entry for table "photos"
LINE 1: SELECT COUNT(*) FROM "properties"  WHERE (properties.photos....
                                                  ^
: SELECT COUNT(*) FROM "properties"  WHERE (properties.photos.count > 0)
from /ruby-2.3.0@myproject/gems/activerecord-3.2.22.5/lib/active_record/connection_adapters/postgresql_adapter.rb:1163:in `async_exec'
Caused by PG::UndefinedTable: ERROR:  missing FROM-clause entry for table "photos"
LINE 1: SELECT COUNT(*) FROM "properties"  WHERE (properties.photos....

到:

> Property.joins(:photos).where('photos.count > ?', 0).count

   (3.7ms)  SELECT COUNT(*) FROM "properties" INNER JOIN "photos" ON "photos"."property_id" = "properties"."id" WHERE (photos.count > 0)
ActiveRecord::StatementInvalid: PG::GroupingError: ERROR:  aggregate functions are not allowed in WHERE
LINE 1: ..."photos"."property_id" = "properties"."id" WHERE (photos.cou...
                                                             ^
: SELECT COUNT(*) FROM "properties" INNER JOIN "photos" ON "photos"."property_id" = "properties"."id" WHERE (photos.count > 0)
from ruby-2.3.0@myproject/gems/activerecord-3.2.22.5/lib/active_record/connection_adapters/postgresql_adapter.rb:1163:in `async_exec'
Caused by PG::GroupingError: ERROR:  aggregate functions are not allowed in WHERE
LINE 1: ..."photos"."property_id" = "properties"."id" WHERE (photos.cou...

到更高级的:

>Property.includes(:photos).group(['property.id', 'photos.id']).order('COUNT(photos.id) DESC').count

(0.6ms)  SELECT COUNT(DISTINCT "properties"."id") AS count_id, property.id AS property_id, photos.id AS photos_id FROM "properties" LEFT OUTER JOIN "photos" ON "photos"."property_id" = "properties"."id" GROUP BY property.id, photos.id ORDER BY COUNT(photos.id) DESC
ActiveRecord::StatementInvalid: PG::UndefinedTable: ERROR:  missing FROM-clause entry for table "property"
LINE 1: ...CT COUNT(DISTINCT "properties"."id") AS count_id, property.i...
                                                             ^
: SELECT COUNT(DISTINCT "properties"."id") AS count_id, property.id AS property_id, photos.id AS photos_id FROM "properties" LEFT OUTER JOIN "photos" ON "photos"."property_id" = "properties"."id" GROUP BY property.id, photos.id ORDER BY COUNT(photos.id) DESC
from ruby-2.3.0@myproject/gems/activerecord-3.2.22.5/lib/active_record/connection_adapters/postgresql_adapter.rb:1163:in `async_exec'
Caused by PG::UndefinedTable: ERROR:  missing FROM-clause entry for table "property"
LINE 1: ...CT COUNT(DISTINCT "properties"."id") AS count_id, property.i...

以及其他一些变体,它们都会产生类似的错误。

我做错了什么?

注意:我想要的只是拥有photos.count > 0properties 的计数。我不想要所有属性和照片数量的哈希值。换句话说,如果我的数据库中有 5000 个属性,我想构建一个只返回实际有照片的属性的范围。

【问题讨论】:

请通过编辑而非 cmets 进行澄清。请在代码问题中提供minimal reproducible example--cut & paste & runnable code;具有期望和实际输出的示例输入(包括逐字错误消息);明确的规范和解释。这包括您可以提供的最少代码,即您显示的代码可以通过您显示的代码扩展为不正常。 (调试基础。) PS您的问题是代码扩展没有返回您所期望的。等到问题解决后再询问您的总体目标。 PS“拥有”。阅读手册重新聚合。 【参考方案1】:
Property.includes(:photos).where("SELECT count(photos.id) > 0 FROM photos WHERE property_id = properties.id")

作为一个范围:

scope :with_photos, ->  where("SELECT count(photos.id) > 0 FROM photos WHERE property_id = properties.id") 

【讨论】:

这不起作用有两个原因 1)includes 在这种情况下不会自动加入,所以photos 没有参考 2)where 子句没有条件它只是一个子查询,它将是语法错误【参考方案2】:

根据你的要求你可以试试这个

1) 对具有 1 个或多个属性的简单计数 照片

要获取包含一张或多张照片的属性的数量,您可以这样做

Property.joins(:photos).distinct.count

由于我们不使用group,所以distinctuniq 是必要的。 distinct 将返回 ActiveRecord_Relationuniq 将返回 Array

2) 我希望返回那组属性,这样我就可以 创建仅包含这些属性的范围。另外,我确实有很多 包含 1 张以上照片的属性。

要获取具有一张或多张照片的所有属性对象,您可以使用相同的查询:

Property.joins(:photos).distinct

或者您可以使用group_by 子句:

Property.joins(:photos).group('properties.id')

不同之处在于,当您在 group 查询上使用 size 方法时,它将返回一个哈希值,其中 property_id 作为键,属性上的照片数量作为值。

性能更新:

如果您总是需要关联对象的计数并且想要有效地获取它,您可以像这样使用counter_cache

class Photo < ApplicationRecord
  belongs_to :property, counter_cache: true
end

class Property < ApplicationRecord
  has_many :photos
end

然后您必须在属性表中添加一个名为photos_count 的列,每次添加新照片记录或删除现有照片记录时,Rails 都会自动更新该列。然后也可以直接查询:

Property.where('photos_count > ?', 1)

这是可选的,如果您在获取数据时遇到性能问题,您可以这样做。

【讨论】:

【参考方案3】:

你可以这样尝试,我在我的项目中做过,

Photo.group(:property_id).count

您将获得带有照片数

property id
results =  3314=>3, 2033=>3, 3532=>2, 3565=>6, 3510=>1, 3022=>7, 648=>2, 570=>3, 4678=>3, 3540=>1, 3489=>4, 536=>1, 1715=>4 

【讨论】:

这是我最初拥有的,但它并没有给我我需要的东西——它是一个简单的整数,它返回至少有 1 张照片与之关联的属性记录的数量。 【参考方案4】:

试一试:

class Property < ApplicationRecord
  has_many :photos

  def self.with_photos
    self.all.reject  |p| p.photos.empty? 
  end
end

Property.with_photos.count

Source

更高效(Rails 4+):

Property.joins(:photos).uniq.count

Source

更高效(Rails 5.1+):

Property.joins(:photos).distinct.count

Source

【讨论】:

【参考方案5】:

既然您只需要 Propertys 和 Photos,那么您只需要 INNER JOIN。

Property.joins(:photos) 

就是这样。如果你想要一个范围,那么

class Property < ActiveRecord::Base
  scope :with_photos, -> joins(:photos) 
end 

使用 rails 3.2 获取计数

Property.with_photos.count(distinct: true)  

您还可以使用:在 rails 3.2 中

Property.count(joins: :photos, distinct: true) 

ActiveRecord::Calculations#count Doc

这将执行

SELECT 
  COUNT(DISTINCT properties.id) 
FROM 
  properties
  INNER JOIN photos ON photos.property_id = properties.id

【讨论】:

【参考方案6】:

编辑:

Property.joins(:photos).group('photos.property_id').having('count(photos.property_id) > 1').count

#=> 1234=>2  # 1234 is property id 2 is count of photos 

您将获得带有相关照片数量的 property_ids。

旧答案:

您可以获得至少一张与之关联的照片的属性

Property.includes(:photos).where.not(photos:  property_id: nil )

当您使用 rails 3.2 .not 将无法正常工作时,您必须使用

Property.includes(:photos).where("property_id IS NOT null")

【讨论】:

“至少一个”和“多个”是不同的。 我在尝试此操作时收到此错误:PG::UndefinedColumn: ERROR: column "property_id" does not exist LINE 1: SELECT "properties".* FROM "properties" WHERE (property_id ... @sawa 没注意到 > 1. 现在根据它进行更改 @DeepakMahakale 当我尝试您的更新时,我得到一个空哈希结果:Property.joins(:photos).group('photos.property_id').having('count(photos.property_id) &gt; 1').count (74.8ms) SELECT COUNT(*) AS count_all, photos.property_id AS photos_property_id FROM "properties" INNER JOIN "photos" ON "photos"."property_id" = "properties"."id" GROUP BY photos.property_id HAVING count(photos.property_id) &gt; 1 =&gt; 这意味着您可能没有任何属性包含一张以上的照片。您能否通过向属性添加 2 张照片来手动检查并验证您是否得到结果

以上是关于如何计算具有一个或多个关联对象的记录数?的主要内容,如果未能解决你的问题,请参考以下文章

查找具有多个 ActiveRecord HABTM 关联的记录

VBA:如何通过集合、“迟到”字典、其他方式关联多个对象实例?

如何使普通的 ruby​​ 对象可分配为活动记录关联

hibernate

如何使用具有关联类型的非静态特征对象?

如何使用实体框架关联来自多个上下文的对象