Rails 的 ActiveRecord 查询虽然可以附加很多诸如 includes、select 方法,但查询结果只返回该 Model 的字段,其它返回的字段都被抛弃,但我们经常会对数据库进行多表组合字段的查询,例如:
A 表:产品、销量;B 表:门店、产品;查询出:门店、产品、销量,甚至要更复杂点的统计:门店、产品、SUM(销量)
一直没有找到正确的解决方案(很困惑:难道需要我在 controller 里写若干循环来进行“结果集合并”吗?),请教 ActiveRecord 或者 Rails 处理 SQL 的高手,正确的做法是怎样的?谢谢!
模型的字段,是由其查询后的结果动态生成的,也就是说:如果你返回了 a b c 三个字段,那它即有 a b c 三个方法,而在另一个地方,你返回了 a b c d 那么它就拥有了 a b c d 四个方法。
A.select("table_a.a, table_a.b, table_a.c")
.select("table_b.d")
另外,你可以自己写 SQL 语句来做复杂的查询
result = ActiveRecord::Base.connection.execute <<-EOF
select table_a.a, table_a.b table_a.c, table_b.d, sum(column) as xxxx
from table_a
inner join table_b on (.......)
EOF
还可以写视图,然后建一个 model 来跟它对应
class Report < ActiveRecord::Base
self.table_name = 'xxxx_view'
end
@hz_qiuyuanxin 我理解 select 对 model 固有的字段选择有效。你的后两种方法对我有启迪,ActiveRecord::Base.connection.select_all("..."),可以返回一个包含查询结果的 hash,我在 rails c 下试验成功;视图的方法还没试。
@hz_qiuyuanxin 真的吗?为什么我拿不到?
我又三张表:
products(id, product_name)
stores(id, store_name)
orders(id, store_id, product_id, rmb)
我想执行的 sql 是:
select
stores
.store_name
AS store_name
,
products
.product_name
AS product_name
,
sum(orders
.rmb
) AS amount
from
((orders
left join products
ON ((products
.id
= orders
.product_id
)))
left join stores
ON ((stores
.id
= orders
.dim_store_id
)))
group by stores
.store_name
, products
.product_name
order by stores
.store_name
, products
.product_name
在 Order Model 执行: Order.includes(:product, :store).select("stores.store_name AS store_name, products.product_name AS product_name, sum(orders.rmb) AS amount").group("...").order("...")
返回的结果里有 orders 表的字段,但没有我 select 的三个字段。
何解?
#13 楼 @lotusroot 你理解错 ActiveRecord
的 includes
,好好看看这里的文档
你应该这样
Order.join("LEFT OUTER JOIN products ON (products.id = orders.product.id)")
.join("LEFT JOIN stores ON (stores.id = orders.store_id)")
.select("stores.store_name AS store_name")
.select("products.product_name AS product_name")
.select("SUM(orders.rmb AS amount)")
.group("stores.store_name, products.product_name")
.order("stores.store_name DESC, products.product_name DESC")
而且,按照你这个需要来看,根本就不是 Order 该管的事情,你应该直接写 SQL
report = ActiveRecord::Base.connection.execute <<-EOF
SELECT stores.store_name AS store_name,
products.product_name AS product_name,
SUM(orders.rmb) AS amount
FROM orders
LEFT JOIN products ON (products.id = orders.product_id)
LEFT JOIN stores ON (stores.id = orders.dim_store_id)
GROUP BY stores.store_name, products.product_name
ORDER BY stores.store_name DESC, products.product_name DESC
EOF
report.to_a.each do |row|
puts "#{row["store_name"]} - #{row["product_name"]} - #{row["amount"]}"
end
又或者去建一个 VIEW
最后一点:能看一下写 markdown 的语法么?代码应该用什么包起来?
@hz_qiuyuanxin 高手!赞一个! join,includes,select 看了好多遍,确实没理解透。这个场景确实跟 Order 关系不大,主要是想学习掌握 ActiveRecord 的用法。 这里还有一个问题请教,对于 ActiveRecord::Base.connection.execute 查询结果,用哪个 Grid 组件可以很好的在页面上显示出来(能够分页)?
#15 楼 @lotusroot 我不是高手,这是数据库基础,AR 只是一个 ORM,提供了一套 DSL
分页跟你用什么没有关系,
至于分页插件,现在比较流行的是 kaminari,通过 AR 的 DSL,你可以直接这样
Order.join("LEFT OUTER JOIN products ON (products.id = orders.product.id)")
.join("LEFT JOIN stores ON (stores.id = orders.store_id)")
.select("stores.store_name AS store_name")
.select("products.product_name AS product_name")
.select("SUM(orders.rmb AS amount)")
.group("stores.store_name, products.product_name")
.order("stores.store_name DESC, products.product_name DESC")
.page(params[:page])
.per(30)
而如果自己写 SQL,然后用 ActiveRecord::Base.connection.execute
的话,你需要自己在 SQL 里管分页,而页面的显示,可以参考一下我最近写的博文 Custom Pagination in Rails Use Kaminari