繁体   English   中英

Rails - 活动记录:查找所有记录与某些属性has_many关联的记录

[英]Rails - Active Record: Find all records which have a count on has_many association with certain attributes

用户具有许多身份。

class User < ActiveRecord::Base
    has_many :identities
end

class Identity < ActiveRecord::Base
    belongs_to :user
end

标识具有已confirmed:boolean列。 我想查询只有一个身份的所有用户。 此身份也必须证实为假。

我试过这个

User.joins(:identities).group("users.id").having( 'count(user_id) = 1').where(identities: { confirmed: false })

但是这会返回一个身份confirmed:false用户confirmed:false 但如果确认为true,他们也可以拥有其他身份。 我只希望确认只有一个身份的用户:false,并且没有已确认属性为true的其他身份

我也试过这个,但显然它很慢,而且我正在寻找合适的SQL来在一个查询中执行此操作。

  def self.new_users
    users = User.joins(:identities).where(identities: { confirmed: false })
    users.select { |user| user.identities.count == 1 }
  end

如果已经回答了这个问题但是我找不到相似的帖子,请提前道歉。

  def self.new_users
    joins(:identities).group("identities.user_id").having("count(identities.user_id) = 1").where(identities: {confirmed: false}).uniq
  end

如果您在DBMS中拥有该功能,我认为group_concat可能就是答案。 (如果没有,可能有一个等价物)。 这将从组中将字段的所有值收集到以逗号分隔的字符串中。 我们想要这个字符串等于“假”的那些:即,只有一个,它是假的(我认为这是你的要求,它有点不清楚)。 我认为如果我们让Rails处理false转换然后DB存储它,这应该工作。

User.joins(:identities).group("identities.user_id").having("group_concat(identities.confirmed) = ?", false)

编辑 - 如果您的数据库将false存储为0那么上面将生成类似于having group_concat(identities.confirmed) = 0 sql。 因为group_concat的结果是一个字符串,所以它可能(在某些DBMS中)对结果执行字符串到整数的转换,然后将其与0进行比较,如果所有其他字符串都转换为0,则会返回大量的误报在这种情况下你可以试试这个:

User.joins(:identities).group("identities.user_id").having("group_concat(identities.confirmed) = '?'", false)

(注意引用?)

EDIT2 - postgres版本。

我没试过这个,但看起来最近版本的postgres有一个函数array_agg() ,它和mysql的group_concat() 因为postgres将true / false存储为't'/'f'我们不应该包装? 在引号中。 尝试这个:

User.joins(:identities).group("identities.user_id").having("array_agg(identities.confirmed) = ?", false)

一种解决方案是使用rails嵌套查询

User.joins(:identities).where(id: Identity.select(:user_id).unconfirmed).group("users.id").having( 'count(user_id) = 1')

这是查询生成的SQL

SELECT "users".* FROM "users"
INNER JOIN "identities" ON "identities"."user_id" = "users"."id"
WHERE "users"."id" IN (SELECT "identities"."user_id" FROM "identities"  WHERE "identities"."confirmed" = 'f')
GROUP BY users.id HAVING count(user_id) = 1

我仍然认为这不是最有效的方式。 虽然我只能生成一个SQL查询(意味着只对数据库进行一次网络调用),但我仍然需要进行两次扫描:USERS表上的一次扫描和IDENTITIES表上的一次扫描。 这可以通过索引identities.confirmed列来优化,但这仍然无法解决两个完整扫描问题。

对于那些了解查询计划的人来说,它是:

     QUERY PLAN
-------------------------------------------------------------------------------------------
 HashAggregate  (cost=32.96..33.09 rows=10 width=3149)
   Filter: (count(identities.user_id) = 1)
   ->  Hash Semi Join  (cost=21.59..32.91 rows=10 width=3149)
         Hash Cond: (identities.user_id = identities_1.user_id)
         ->  Hash Join  (cost=10.45..21.61 rows=20 width=3149)
               Hash Cond: (identities.user_id = users.id)
               ->  Seq Scan on identities  (cost=0.00..10.70 rows=70 width=4)
               ->  Hash  (cost=10.20..10.20 rows=20 width=3145)
                     ->  Seq Scan on users  (cost=0.00..10.20 rows=20 width=3145)
         ->  Hash  (cost=10.70..10.70 rows=35 width=4)
               ->  Seq Scan on identities identities_1  (cost=0.00..10.70 rows=35 width=4)
                     Filter: (NOT confirmed)
(12 rows)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM