【问题标题】:Raw SQL and generate_series in Rails Active RecordRails Active Record 中的原始 SQL 和 generate_series
【发布时间】:2019-08-25 14:06:43
【问题描述】:

不知道给这个标题取什么名字,但我很难将原始 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

我怎样才能做到这一点?

【问题讨论】:

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


    【解决方案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

    【讨论】:

    • 谢谢,@Aarthi,我真的很难过没有其他方法可以使用@relation。我以为我对 ActiveRecord 了解的不够多,也不知道如何处理现有的 @relation。
    • 你能分享一下你是如何初始化@relation
    • 当然,这是日期范围和输入类型,inputs = GroupedTransactionsQuery.new(relation: current_user.account.transactions.inputs.from_date(@start_date).to_date(@start_date)) 和内部查询对象初始化器:@relation = params[:relation] || Transaction.all
    • 我已经更新了答案,这只是一个可能的解决方案,在某些情况下不起作用(在注释中添加)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-10-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多