Rails 使用其他 2 个虚拟列计算虚拟列

Rails calculating a virtual column using 2 other virtual columns

提问人:sunny1304 提问时间:6/17/2023 最后编辑:sunny1304 更新时间:6/19/2023 访问量:98

问:

我需要帮助将以下MySQL查询转换为ActiveRecord查询,该查询将生成与IP记录作为对象的关系。 为了更好地解释这一点,如何在 ActiveRecord 查询中将两个虚拟列相和到第三列中转换以下查询?

select a_id
u_c,
i_c,
(u_c + i_c) as t_c
from (
    select distinct a.id as a_id,
    (   SELECT count(b.id) FROM UP b
        WHERE b.i_p_id = a.id
        AND b.role = "L"
    ) as u_c,
    (   SELECT count(b.id) from UP b
        WHERE b.institution_package_id = a.id
        AND b.role = "I"
    ) as i_c
    from IP a
    left outer join UP b on b.IP = a.id       
) sub 

让我再解释一下。 我有 2 个表,我正在查询第一个表并计算 3 个虚拟列。 第 3 列将是其他 2 个虚拟列的总和。 感谢您的帮助。

Ruby-on-Rails Ruby-on-Rails-4 Ruby-on-Rails-7

评论

1赞 dbugger 6/18/2023
你已经有了你想要的 SQL -- 让它成为一个视图,并基于它创建一个 ActiveRecord 类。
1赞 crodev 6/18/2023
每当您有一个复杂的SQL查询,而这些查询可能无法通过ActiveRecord轻松实现时,您可以利用Arel(查看本指南)。它并不是真的要直接使用,但很多人使用它,因为它是真正强大的工具(这实际上是 ActiveRecord 的引擎盖下)。
1赞 Les Nightingill 6/18/2023
如果这是其他人编写的查询,请注意,Rails 应用程序中的此类查询通常意味着 ActiveRecord 实现太困难、效率太低且太慢。
0赞 plombix 6/19/2023
理解这么小的 var 名称不是很容易,你是用导轨设计的表吗?我认为如果你在sql中需要这样的装置,你的rails应用程序的基本设计中有一个pb。您能否展示您的模型并解释用例。或者,如果您不想解释,可以使用:sql = "Select * from ... your sql query here" records_array = ActiveRecord::Base.connection.execute(sql)
0赞 sunny1304 6/23/2023
谢谢大家,最后我选择了原始查询执行。我试图保留 ActiveRecord 关系,这就是关于 activerecord 查询的原因。

答:

1赞 Siim Liiser 6/19/2023 #1

在 SQL 中编写复杂的查询并通过 rails 执行它们是一个简单、好的、通常是更短的解决方案。

但是,如果您真的想将 Rails 工具用于可重用性和/或安全性目的,那么这个查询可以通过 Arel 来实现,Arel 是 ActiveRecord 查询在内部构建的工具。

这被视为私有 API,可能会发生变化。升级导轨时要小心。

class IP < ActiveRecord::Base
    has_many :ups, foreign_key: :IP, class_name: 'UP'
end
class UP < ActiveRecord::Base
end


a = IP.arel_table.alias('a')
b = UP.arel_table.alias('b')

first_subquery = Arel::Nodes::Grouping.new(
    UP.from(b).where(
      b[:i_p_id].eq(a[:id]).and(b[:role].eq('L'))
    ).select(b[:id].count).arel
)
second_subquery = Arel::Nodes::Grouping.new(
    UP.from(b).where(
      b[:institution_package_id].eq(a[:id]).and(b[:role].eq('I'))
    ).select(b[:id].count).arel
)
sub = IP.left_joins(:ups).distinct.select(
  a[:id].as('a_id'),
  first_subquery.as('u_c')
  second_subquery.as('i_c')
).arel.as('sub')

Arel::SelectManager.new.from(sub).project(
  sub[:a_id],
  sub[:u_c],
  sub[:i_c],
  (sub[:u_c] + sub[:i_c]).as('t_c')
).to_sql

它会产生这样的东西

SELECT sub."a_id",
    sub."u_c",
    sub."i_c",
    (sub."u_c" + sub."i_c") AS t_c
FROM (
        SELECT DISTINCT "a"."id" AS a_id,
            (
                (
                    SELECT COUNT("b"."id")
                    FROM "UP" "b"
                    WHERE "b"."i_p_id" = "a"."id"
                        AND "b"."role" = 'L'
                )
            ) AS u_c,
            (
                (
                    SELECT COUNT("b"."id")
                    FROM "UP" "b"
                    WHERE "b"."institution_package_id" = "a"."id"
                        AND "b"."role" = 'I'
                )
            ) AS i_c
        FROM "IP"
            LEFT OUTER JOIN "UP" ON "UP"."IP" = "IP"."id"
    ) sub;

我添加了几个别名以使其类似于初始查询,但由于 Arel 负责引用它们,因此可以删除大多数别名。