我在名为CashTransaction
的模型中有以下方法。
def is_refundable?
self.amount > self.total_refunded_amount
end
def total_refunded_amount
self.refunds.sum(:amount)
end
现在我需要提取满足上述功能的所有记录,即返回true
的记录。
我通过以下声明得到了它:
CashTransaction.all.map { |x| x if x.is_refundable? }
但结果是Array
。我正在寻找ActiveRecord_Relation
对象,因为我需要对结果执行join
。
我觉得我在这里遗漏了一些东西,因为它看起来并不那么困难。无论如何,它让我陷入困境。建设性的建议会很棒。
注意:amount
只是CashTransaction
列。
修改
关注SQL
完成工作。如果我可以将其更改为ORM
,它仍然会完成这项工作。
SELECT `cash_transactions`.* FROM `cash_transactions` INNER JOIN `refunds` ON `refunds`.`cash_transaction_id` = `cash_transactions`.`id` WHERE (cash_transactions.amount > (SELECT SUM(`amount`) FROM `refunds` WHERE refunds.cash_transaction_id = cash_transactions.id GROUP BY `cash_transaction_id`));
分享进度
我设法通过遵循ORM来实现它:
CashTransaction
.joins(:refunds)
.group('cash_transactions.id')
.having('cash_transactions.amount > sum(refunds.amount)')
但我实际上看起来像是:
CashTransaction.joins(:refunds).where(is_refundable? : true)
其中is_refundable?
是模型函数。最初我认为将is_refundable?
设置为attr_accesor
会起作用。但我错了。
只是一个想法,可以使用Arel
以优雅的方式解决问题。
答案 0 :(得分:3)
有两种选择。
1)完成,你已经开始了(当涉及到更大量的数据时效率非常低,因为它在处理之前全部被带入内存):
CashTransaction.all.map(&:is_refundable?) # is the same to what you've written, but shorter.
得到ids:
ids = CashTransaction.all.map(&:is_refundable?).map(&:id)
现在,要获得ActiveRecord Relation:
CashTransaction.where(id: ids) # will return a relation
2)将计算移动到SQL:
CashTransaction.where('amount > total_refunded_amount')
第二种选择以各种可能的方式更快更有效。
当您处理数据库时,尝试在数据库级别处理它,尽可能使用最少的Ruby。
根据编辑的问题,您将如何实现预期的结果:
CashTransaction.joins(:refunds).where('amount > SUM(refunds.amount)')
至于您的相关更新 - 我真的不明白,为什么你作为实例方法锁定到is_refundable?
,可以在查询中使用,这在AR中基本上是不可能的,但是..
我的建议是创建一个范围is_refundable
:
scope :is_refundable, -> { CashTransaction
.joins(:refunds)
.group('cash_transactions.id')
.having('cash_transactions.amount > sum(refunds.amount)')
}
现在可以用
作为简短表示法CashTransaction.is_refundable
比目标更短更清晰
CashTransaction.where('is_refundable = ?', true)
答案 1 :(得分:1)
你可以这样做:
cash_transactions = CashTransaction.all.map { |x| x if x.is_refundable? } # Array
CashTransaction.where(id: cash_transactions.map(&:id)) # ActiveRecord_Relation
但是,正如其他回答者所提到的那样,这是一种无效的方式。
如果amount
和total_refunded_amount
是数据库中cash_transactions
表的列,效率和性能更高,则可以使用SQL执行此操作:
CashTransaction.where('amount > total_refunded_amount')
但是,如果amount
或total_refunded_amount
不是数据库中的实际列,那么就不能这样做。然后,我猜你已经采用了比使用原始SQL更有效的另一种方式。
答案 2 :(得分:0)
我认为你应该在使用回调更新CashTransaction
和他的退款(假设has_many
?)时预先计算is_refundable结果(在新列中):
class CashTransaction
before_save :update_is_refundable
def update_is_refundable
is_refundable = amount > total_refunded_amount
end
def total_refunded_amount
self.refunds.sum(:amount)
end
end
class Refund
belongs_to :cash_transaction
after_save :update_cash_transaction_is_refundable
def update_cash_transaction_is_refundable
cash_transaction.update_is_refundable
cash_transaction.save!
end
end
注意:必须优化上面的代码以防止一些查询
他们可以查询is_refundable列:
CashTransaction.where(is_refundable: true)
答案 3 :(得分:0)
我认为在两个查询而不是连接表上执行此操作并不错,就像这样
def refundable
where('amount < ?', total_refunded_amount)
end
这将执行单个求和查询,然后在第二个查询中使用sum,当表变大时,您可能会发现这比在数据库中进行连接更快。