Rails使用model.find_by_sql手写sql查询无法使用分页gem的解决方法

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)

得出如下


Rails使用model.find_by_sql手写sql查询无法使用分页gem的解决方法_第1张图片
image.png

然后我们查看一下数据

=> [#,
 #,
 #,
 #,
 #,
 #]
[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)>

啊~~~ 报错了
为什么会这样呢???

railskaminari的源码中
类似.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)>

结果发现,完全符合我们的预想,能分页啦。

你可能感兴趣的:(Rails使用model.find_by_sql手写sql查询无法使用分页gem的解决方法)