Rails Active Record中的原始SQL和generate_series

时间:2019-08-25 14:06:43

标签: ruby-on-rails postgresql activerecord ruby-on-rails-5

不知道该如何命名标题,但是我很难将原始SQL重写为Rails ActiveRecord方法。

我想在选择的日期范围内按月提取交易总额。

注意:需要显示该范围内的所有月份。 为此,我使用了generate_series SQL方法来生成该范围内的一系列月份。

我正在编写的方法在Query对象中,该对象具有一个已存在的关系,该关系已从之前筛选出事务。

我目前的功能:

  def group_total_by_months
   @relation.joins("RIGHT JOIN generate_series(TIMESTAMP '1-1-2018', TIMESTAMP '1-1-2020', interval '1 month') AS series
                     ON date_trunc('month', transactions.payment_date) = series")
            .group("series")
            .select("series AS payment_date, sum(transactions.total) AS total")
  end

结果:

#<ActiveRecord::AssociationRelation [#<Transaction id: nil, total: 0.61173e3, payment_date: "2019-06-01 00:00:00">, #<Transaction id: nil, total: 0.364446e4, payment_date: "2019-10-01 00:00:00">, #<Transaction id: nil, total: 0.1625e4, payment_date: "2019-08-01 00:00:00">]>    

这是按月总计的总和,但只有交易中存在的月份。为什么?因为生成的SQL看起来像这样,并且FROM子句来自所有事务,而不是来自筛选的事务:

    SELECT series AS payment_date, sum(transactions.total) AS total FROM \"transactions\" 
RIGHT JOIN generate_series(TIMESTAMP '1-1-2018', TIMESTAMP '1-1-2020', interval '1 month') AS series\n    ON date_trunc('month', transactions.payment_date) = series 
WHERE \"transactions\".\"account_id\" = 1 
GROUP BY series

需要如下所示的SQL

WITH filteret_transactions AS (
    SELECT * FROM transactions WHERE transactions.account_id = 1
)

 SELECT series AS payment_date, sum(filteret_transactions.total) AS total 
    FROM  filteret_transactions
  RIGHT JOIN generate_series(TIMESTAMP '1-1-2018', TIMESTAMP '1-1-2020', interval '1 month') AS series
  ON date_trunc('month', filteret_transactions.payment_date) = series GROUP BY series

我该如何实现?

1 个答案:

答案 0 :(得分:1)

您需要在加入之前应用条件。您可以像第一种方法一样传递所需的参数作为参数并进行查询。我已经在联接本身中添加了条件(可以根据需要添加更多条件),并使用模型本身(事务)进行联接。

def group_total_by_months(account_id: )
   Transaction.joins("RIGHT JOIN generate_series(TIMESTAMP '1-1-2018', TIMESTAMP '1-1-2020', interval '1 month') AS series
                     ON date_trunc('month', transactions.payment_date) = series AND transactions\".\"account_id\" = #{account_id}")
            .group("series")
            .select("series AS payment_date, sum(transactions.total) AS total")
end

编辑:可能的解决方案(但不适用于所有情况)

您可以使用此方法(https://apidock.com/rails/ActiveRecord/Relation/where_values_hash)获得适用的where条件。创建查询条件并在原始查询中使用它。

def group_total_by_months

  applied_conditions = []

  @transation.where_values_hash.each do |p|
    applied_conditions << "AND #{p.first} = #{p.second}"
  end

  Transaction.joins("RIGHT JOIN generate_series(TIMESTAMP '1-1-2018', TIMESTAMP '1-1-2020', interval '1 month') AS series
                         ON date_trunc('month', transactions.payment_date) = series #{applied_conditions.join(' ')}")
                .group("series")
                .select("series AS payment_date, sum(transactions.total) AS total")
 end

注意

>> Project.where(id: 12).where_values_hash
>>  {"id"=>12}
>> Project.where('id = 12').where_values_hash
>> {}

参考:SQL join: where clause vs. on clause