如何计算具有一个或多个关联对象的记录数?
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 > 0
的properties
的计数。我不想要所有属性和照片数量的哈希值。换句话说,如果我的数据库中有 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
,所以distinct
或uniq
是必要的。 distinct
将返回 ActiveRecord_Relation
和 uniq
将返回 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 idresults = 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】:既然您只需要 Property
s 和 Photo
s,那么您只需要 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) > 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) > 1 =>
这意味着您可能没有任何属性包含一张以上的照片。您能否通过向属性添加 2 张照片来手动检查并验证您是否得到结果以上是关于如何计算具有一个或多个关联对象的记录数?的主要内容,如果未能解决你的问题,请参考以下文章
查找具有多个 ActiveRecord HABTM 关联的记录