如何将两个不同 ActiveRecord 关系的 COUNT(*) 合并到一个 SQL 查询中?
How can I combine COUNT(*) for two different ActiveRecord relations into a single SQL query?
对于两个不同的 ActiveRecord 关系对象,有没有办法发出一个 SQL 查询来比较关系的计数?
例如。假设我有两个这样的 ActiveRecord::Relation 对象:
posts = Post.where().where().some_scope
users = User.where().some_other_scope.where().joins(:something)
要比较每个关系的计数,我必须发出两个 SQL 查询。
posts.count == users.count
# => SELECT COUNT(*) FROM posts WHERE... ;
# => SELECT COUNT(*) FROM users INNER JOIN ... WHERE... ;
我希望能够只发出一个查询。类似于:
Post.select("COUNT(first) == COUNT(second) as are_equal"), posts, users).are_equal
您可以随时编写自己的 SQL 查询。
假设您有两个模型,AdminUser 和 Company。做你想做的事情的一种方法如下:
ActiveRecord::Base.connection.execute("SELECT COUNT(*) as nb from admin_users UNION SELECT COUNT(*) as nb from companies;").to_a
您最终会得到一个包含两个散列的数组,每个散列包含每个数据库的记录数 table。
除非您使用 UNION
,否则无法将两个不同表的两个计数合并到一个查询中。这将 运行 两个单独的查询并合并结果。这将花费与 运行 分别执行这两个查询大约相同的时间,除了您只访问数据库服务器一次(1 个查询),但您失去了可读性。所以恕我直言,我真的很想知道这是否值得。
例如在一种情况下你可以写
if posts.count == users.count
在另一种情况下,可以这样写:
count_sql = <<-SQL
select "Posts" as count_type, count(*) from posts where ...
union
select "Users" as count_type, count(*) from users where ...
SQL
result = Post.connection.execute(count_sql)
if result[0]["count"] == result[1]["count"]
您必须决定性能改进是否会导致可读性下降。
这对于 ActiveRecord 查询方法是不可能的,但是底层的 Arel 查询生成器(ActiveRecord 在内部使用)可以实现这一点,它看起来不太优雅:
posts = Post.where().where().some_scope
users = User.where().some_other_scope.where().joins(:something)
posts_table = Post.arel_table
users_table = User.arel_table
posts_count = Arel::Nodes::Count.new([posts_table[:id]]).as('count')
users_count = Arel::Nodes::Count.new([users_table[:id]]).as('count')
union = posts.select(posts_count).arel.union(users.select(users_count).arel)
post_count, user_count = Post.from(posts_table.create_table_alias(union, :posts)).map(&:count)
虽然在这种情况下它实际上可能没有好处(如其他答案中所讨论的),但值得了解 Arel,因为有时它很有用 - 我总是尽量避免原始 SQL我的 Rails 应用程序和 Arel 使这成为可能。
可以在此处找到出色的介绍:https://danshultz.github.io/talks/mastering_activerecord_arel/#/
对于两个不同的 ActiveRecord 关系对象,有没有办法发出一个 SQL 查询来比较关系的计数?
例如。假设我有两个这样的 ActiveRecord::Relation 对象:
posts = Post.where().where().some_scope
users = User.where().some_other_scope.where().joins(:something)
要比较每个关系的计数,我必须发出两个 SQL 查询。
posts.count == users.count
# => SELECT COUNT(*) FROM posts WHERE... ;
# => SELECT COUNT(*) FROM users INNER JOIN ... WHERE... ;
我希望能够只发出一个查询。类似于:
Post.select("COUNT(first) == COUNT(second) as are_equal"), posts, users).are_equal
您可以随时编写自己的 SQL 查询。
假设您有两个模型,AdminUser 和 Company。做你想做的事情的一种方法如下:
ActiveRecord::Base.connection.execute("SELECT COUNT(*) as nb from admin_users UNION SELECT COUNT(*) as nb from companies;").to_a
您最终会得到一个包含两个散列的数组,每个散列包含每个数据库的记录数 table。
除非您使用 UNION
,否则无法将两个不同表的两个计数合并到一个查询中。这将 运行 两个单独的查询并合并结果。这将花费与 运行 分别执行这两个查询大约相同的时间,除了您只访问数据库服务器一次(1 个查询),但您失去了可读性。所以恕我直言,我真的很想知道这是否值得。
例如在一种情况下你可以写
if posts.count == users.count
在另一种情况下,可以这样写:
count_sql = <<-SQL
select "Posts" as count_type, count(*) from posts where ...
union
select "Users" as count_type, count(*) from users where ...
SQL
result = Post.connection.execute(count_sql)
if result[0]["count"] == result[1]["count"]
您必须决定性能改进是否会导致可读性下降。
这对于 ActiveRecord 查询方法是不可能的,但是底层的 Arel 查询生成器(ActiveRecord 在内部使用)可以实现这一点,它看起来不太优雅:
posts = Post.where().where().some_scope
users = User.where().some_other_scope.where().joins(:something)
posts_table = Post.arel_table
users_table = User.arel_table
posts_count = Arel::Nodes::Count.new([posts_table[:id]]).as('count')
users_count = Arel::Nodes::Count.new([users_table[:id]]).as('count')
union = posts.select(posts_count).arel.union(users.select(users_count).arel)
post_count, user_count = Post.from(posts_table.create_table_alias(union, :posts)).map(&:count)
虽然在这种情况下它实际上可能没有好处(如其他答案中所讨论的),但值得了解 Arel,因为有时它很有用 - 我总是尽量避免原始 SQL我的 Rails 应用程序和 Arel 使这成为可能。
可以在此处找到出色的介绍:https://danshultz.github.io/talks/mastering_activerecord_arel/#/