Rails
提供了数据库查询方法以供我们快捷的去查询数据,以至于让我们不需要太懂sql
的查询语句就可以通过Rails
提供的方法查询出我们所需要的数据。
比如说:
Order.joins(:user).where(users: { id: 1 })
但是,实际工作中使用到的sql可能并不是那么简单,所以有些时候我们可能还需要使用更复杂的sql
语句去进行查询。
比如说以下查询语句
SELECT
*
From
(
SELECT
ss.name AS name,
count(DISTINCT o.id) as order_quantity,
sum(
i.quantity * i.unit_price - i.discount_amount
) AS revenu
FROM
orders o,
order_items i,
products p,
standard_products sp,
channels ss
WHERE
o.id = i.order_id
AND sp.main_display_in_site = ss.site_id
AND i.product_id = p.id
AND p.standard_product_id = sp.id
AND o.checkout_step = 8
AND o.completed_at >= '2018-3-1'
AND o.completed_at <= '2018-3-31'
GROUP BY
ss.site_id
Union All
SELECT
ss.name AS name,
count(DISTINCT o.id) as order_quantity,
sum(
i.quantity * i.unit_price - i.discount_amount
) AS revenu
FROM
orders o,
order_items i,
products p,
standard_products sp,
channels ss
WHERE
o.id = i.order_id
AND sp.main_display_in_site = ss.site_id
AND i.product_id = p.id
AND p.standard_product_id = sp.id
AND o.checkout_step = 8
AND o.completed_at >= '2017-3-1'
AND o.completed_at <= '2017-3-31'
GROUP BY
ss.site_id
) as part_data
这个其实算是简单的,最开始想到的或许是用Rails
提供的model.find_by_sql
来查
sql = <<-SQL
SELECT
*
From
(
SELECT
ss.name AS name,
count(DISTINCT o.id) as order_quantity,
sum(
i.quantity * i.unit_price - i.discount_amount
) AS revenu
FROM
orders o,
order_items i,
products p,
standard_products sp,
channels ss
WHERE
o.id = i.order_id
AND sp.main_display_in_site = ss.site_id
AND i.product_id = p.id
AND p.standard_product_id = sp.id
AND o.checkout_step = 8
AND o.completed_at >= '2018-3-1'
AND o.completed_at <= '2018-3-31'
GROUP BY
ss.site_id
Union All
SELECT
ss.name AS name,
count(DISTINCT o.id) as order_quantity,
sum(
i.quantity * i.unit_price - i.discount_amount
) AS revenu
FROM
orders o,
order_items i,
products p,
standard_products sp,
channels ss
WHERE
o.id = i.order_id
AND sp.main_display_in_site = ss.site_id
AND i.product_id = p.id
AND p.standard_product_id = sp.id
AND o.checkout_step = 8
AND o.completed_at >= '2017-3-1'
AND o.completed_at <= '2017-3-31'
GROUP BY
ss.site_id
) as part_data
SQL
orders_data = Order.find_by_sql(sql)
得出如下
然后我们查看一下数据
=> [#,
#,
#,
#,
#,
#]
[7] pry(main)> orders_data.first.name
=> "123Ink"
[8] pry(main)>
是能查出数据的
现在现在查出来的数据对象比较少,可以直接用拿到前台页面直接each
使用。但是,如果查出的数据对象太多了,一次查询出来可能会很慢,放到前台有可能还会撑爆页面,会产生浏览障碍,这个时候可能需要进行分页显示。那么拿我们平时很常用的gem 'kaminari'
来做分页吧。
[8] pry(main)> orders_data = Order.find_by_sql(sql).page(1).per(2)
Order Load (5720.8ms) SELECT
*
From
(
SELECT
ss.name AS name,
count(DISTINCT o.id) as order_quantity,
sum(
i.quantity * i.unit_price - i.discount_amount
) AS revenu
FROM
orders o,
order_items i,
products p,
standard_products sp,
channels ss
WHERE
o.id = i.order_id
AND sp.main_display_in_site = ss.site_id
AND i.product_id = p.id
AND p.standard_product_id = sp.id
AND o.checkout_step = 8
AND o.completed_at >= '2018-3-1'
AND o.completed_at <= '2018-3-31'
GROUP BY
ss.site_id
Union All
SELECT
ss.name AS name,
count(DISTINCT o.id) as order_quantity,
sum(
i.quantity * i.unit_price - i.discount_amount
) AS revenu
FROM
orders o,
order_items i,
products p,
standard_products sp,
channels ss
WHERE
o.id = i.order_id
AND sp.main_display_in_site = ss.site_id
AND i.product_id = p.id
AND p.standard_product_id = sp.id
AND o.checkout_step = 8
AND o.completed_at >= '2017-3-1'
AND o.completed_at <= '2017-3-31'
GROUP BY
ss.site_id
) as part_data
NoMethodError: undefined method `page' for #
from (pry):59:in `'
[9] pry(main)>
啊~~~ 报错了
为什么会这样呢???
在rails
中kaminari
的源码中
类似.page .per
方法是放在Kaminari::ActiveRecordExtension
模块里面
ActiveSupport.on_load(:active_record) do
require 'kaminari/models/active_record_extension'
::ActiveRecord::Base.send :include, Kaminari::ActiveRecordExtension
end
那么我们看看orders_data
是什么?
[9] pry(main)> orders_data.class
=> Array
[10] pry(main)>
Array
数组???
原来model.find_by_sql
生成的数据返回值是一个数组,而不是集成ActiveRecord::Base
的对象数组。这下就明白是什么原因导致无法使用kaminari
来帮助我们分页了。
现在到了这里,如果要进行分页,还得通过写方法来分页,实在是令人觉得麻烦。那么,有办法既能在Rails
提供的方法里进行如此复杂的查询,还能使用kaminari
来帮助我们分页吗?
答案是有的,而且也非常简单。只要把sql
往里塞就好。
还是拿上面的sql
做例子吧
只需要这样
orders_data = Order.select("*").from("(
SELECT
ss.name AS name,
count(DISTINCT o.id) as order_quantity,
sum(
i.quantity * i.unit_price - i.discount_amount
) AS revenu
FROM
orders o,
order_items i,
products p,
standard_products sp,
channels ss
WHERE
o.id = i.order_id
AND sp.main_display_in_site = ss.site_id
AND i.product_id = p.id
AND p.standard_product_id = sp.id
AND o.checkout_step = 8
AND o.completed_at >= '2018-3-1'
AND o.completed_at <= '2018-3-31'
GROUP BY
ss.site_id
Union All
SELECT
ss.name AS name,
count(DISTINCT o.id) as order_quantity,
sum(
i.quantity * i.unit_price - i.discount_amount
) AS revenu
FROM
orders o,
order_items i,
products p,
standard_products sp,
channels ss
WHERE
o.id = i.order_id
AND sp.main_display_in_site = ss.site_id
AND i.product_id = p.id
AND p.standard_product_id = sp.id
AND o.checkout_step = 8
AND o.completed_at >= '2017-3-1'
AND o.completed_at <= '2017-3-31'
GROUP BY
ss.site_id
) as part_data")
我们看下结果
[14] pry(main)> orders_data
=> [#,
#,
#,
#,
#,
#]
我们会发现,数据出来了
然后我们看一下它的类
[15] pry(main)> orders_data.class
=> Order::ActiveRecord_Relation
[16] pry(main)>
是Order::ActiveRecord_Relation
,既然如此,那么我们是不是就可以使用kaminari
来帮助我们分页了呢?
[16] pry(main)> orders_data.page(1).per(1)
Order Load (5680.1ms) SELECT * FROM (
SELECT
ss.name AS name,
count(DISTINCT o.id) as order_quantity,
sum(
i.quantity * i.unit_price - i.discount_amount
) AS revenu
FROM
orders o,
order_items i,
products p,
standard_products sp,
channels ss
WHERE
o.id = i.order_id
AND sp.main_display_in_site = ss.site_id
AND i.product_id = p.id
AND p.standard_product_id = sp.id
AND o.checkout_step = 8
AND o.completed_at >= '2018-3-1'
AND o.completed_at <= '2018-3-31'
GROUP BY
ss.site_id
Union All
SELECT
ss.name AS name,
count(DISTINCT o.id) as order_quantity,
sum(
i.quantity * i.unit_price - i.discount_amount
) AS revenu
FROM
orders o,
order_items i,
products p,
standard_products sp,
channels ss
WHERE
o.id = i.order_id
AND sp.main_display_in_site = ss.site_id
AND i.product_id = p.id
AND p.standard_product_id = sp.id
AND o.checkout_step = 8
AND o.completed_at >= '2017-3-1'
AND o.completed_at <= '2017-3-31'
GROUP BY
ss.site_id
) as part_data LIMIT 1 OFFSET 0
=> [#]
[17] pry(main)>
结果发现,完全符合我们的预想,能分页啦。