你会如何使用Squeel进行这个Rails子查询?

Pet*_*xey 9 activerecord ruby-on-rails arel squeel

我想使用Squeel重构下面的查询.我想这样做,以便我可以链接其中的运算符并重新使用查询的不同部分中的逻辑.

User.find_by_sql("SELECT 
    users.*,
    users.computed_metric,
    users.age_in_seconds,
    ( users.computed_metric / age_in_seconds) as compound_computed_metric
    from
    (
      select
        users.*,
        (users.id *2 ) as computed_metric,
        (extract(epoch from now()) - extract(epoch from users.created_at) ) as age_in_seconds
        from users
    ) as users")
Run Code Online (Sandbox Code Playgroud)

查询必须全部在DB中运行,并且不应该是混合Ruby解决方案,因为它必须对数百万条记录进行排序和切片.

我已经设置了问题,因此它应该针对普通user表运行,以便您可以使用它的替代方案.

对可接受答案的限制

  • 查询应返回User具有所有常规属性的对象
  • 每个用户对象也应该包括extra_metric_we_care_about,age_in_secondscompound_computed_metric
  • 查询不应该只通过在多个地方打印出一个字符串来复制任何逻辑 - 我想避免两次做同样的事情
  • [更新]查询应该在数据库中都可以执行,以便在返回到Rails之前,可以在数据库中对可能包含数百万条记录的结果集进行排序和切片
  • [更新]该解决方案适用于Postgres数据库

我想要的解决方案类型示例

下面的解决方案不起作用,但它显示了我希望实现的优雅类型

class User < ActiveRecord::Base
# this doesn't work - it just illustrates what I want to achieve

  def self.w_all_additional_metrics
    select{ ['*', 
              computed_metric, 
              age_in_seconds, 
              (computed_metric / age_in_seconds).as(compound_computed_metric)] 
      }.from{ User.w.compound_computed_metric.w_age_in_seconds }
  end

  def self.w_computed_metric
    where{ '(id *2 ) as computed_metric' }
  end

  def self.w_age_in_seconds
    where{ '(extract(epoch from now()) - extract(epoch from created_at) ) as age_in_seconds' }
  end
end
Run Code Online (Sandbox Code Playgroud)

您应该能够针对现有数据库运行此操作

请注意,我有点设计了这个问题,以便您可以使用现有的User课程并在控制台中使用它.

编辑

  1. 我正在使用的DB是Postgres.
  2. 我不确定我是否100%明确查询应该全部在DB中执行.它不能是一个混合的答案,一些逻辑基本上是在Rails中完成的.这很重要,因为我希望能够使用计算列对数百万条记录进行排序和切片.

Big*_*ang 7

在你的情况下我有2个苏打.我的数据库是mysql,我简化了你的demo代码,我想你可以扩展它.

第一种是Squeel方式,我在Squeel中混合了"sift",在ActiveRecord Query中混合了"from".我刚刚安装了postgresql并测试了我的解决方案,似乎很难一起使用"squeel"和"epoch from",但我在postgresql中找到了另一种方法,它叫做"date_part".我还修改了sql并减少了重复计算:

class User < ActiveRecord::Base           
  sifter :w_computed_metric do
    (id * 2).as(computed_metric)
  end

  sifter :w_age_in_seconds do
    (date_part('epoch' , now.func) - date_part('epoch', created_at)).as(age_in_seconds)
  end

  sifter :w_compound_computed_metric do
    (computed_metric / age_in_seconds).as(compound_computed_metric)
  end

  def self.subquery
    select{['*', sift(w_computed_metric) , sift(w_age_in_seconds)]}
  end

  def self.w_all_additional_metrics
    select{['*', sift(w_compound_computed_metric)]}.from("(#{subquery.to_sql}) users")
  end      
end
Run Code Online (Sandbox Code Playgroud)

它产生了sql:

SELECT *, "users"."computed_metric" / "users"."age_in_seconds" AS compound_computed_metric 
FROM (SELECT *, 
             "users"."id" * 2 AS computed_metric, 
             date_part('epoch', now()) - date_part('epoch', "users"."created_at") AS age_in_seconds FROM "users" 
     ) users
Run Code Online (Sandbox Code Playgroud)

您可以使用控制台进行测试:

> User.w_all_additional_metrics.first.computed_metric
=> "2"
> User.w_all_additional_metrics.first.age_in_seconds
=> "633.136693954468"
> User.w_all_additional_metrics.first.compound_computed_metric
=> "0.00315887551471441"
Run Code Online (Sandbox Code Playgroud)

第二种是ActiveRecord方式,因为你的sql不是很复杂,所以你可以在ActiveRecord Query中链接它,这对于一些范围就足够了:

class User < ActiveRecord::Base
  scope :w_computed_metric, proc { select('id*2 as computed_metric') }
  scope :w_age_in_seconds, proc { select('extract (epoch from (now()-created_at)) as age_in_seconds') }
  scope :w_compound_computed_metric, proc { select('computed_metric/age_in_seconds as compound_computed_metric') }

  def self.subquery
    select('*').w_computed_metric.w_age_in_seconds
  end

  def self.w_all_additional_metrics
    subquery.w_compound_computed_metric.from("(#{subquery.to_sql}) users")
  end
end
Run Code Online (Sandbox Code Playgroud)

这会产生以下SQL:

SELECT 
  *, id*2 as computed_metric, 
  extract (epoch from (now()-created_at)) as age_in_seconds, 
  computed_metric / age_in_seconds as compound_computed_metric
FROM (
    SELECT 
      *, 
      id*2 as computed_metric, 
      extract (epoch from (now()-created_at)) as age_in_seconds 
    FROM 
      "users" 
    ) users 
ORDER BY compound_computed_metric DESC 
LIMIT 1
Run Code Online (Sandbox Code Playgroud)

希望它符合你的要求:)

  • 我不敢相信这是多么光滑.Bigxiang你是我的英雄!此解决方案的关键之一是在`from`子句中使用'users'的别名作为子查询.再次,神奇! (2认同)