ORDER key ASC しつつ Last N 件のレコードを取得する

動かしたのは Rails5.0.1 です

# :thinking:やりたいこと　

例えば、

| id |
| :--: |
| 1  |
| 2  |
| 3  |
| 4  |
| 5  |


のデータが有る`hoges`テーブルがあったときに

```ruby
Hoge.where.not(id: 3).order(:id).endmost(3)
#=> [#<Hoge:0x007f83f2b312a8 id: 2>, #<Hoge:0x007f83f2b310c8 id: 4>, #<Hoge:0x007f83f2b30f10 id: 5>]
```
となるような`endmost`メソッドをどう実装すればよいか、という話です。
(メソッド名は↓で紹介する記事から取りました)

- orderで昇順で取得できる
- 下からN件取れる
- ArrayではなくActiveRecord_Relationである

という条件です。

# 方法 :one:

[RETRIEVING THE LAST N ORDERED RECORDS WITH ACTIVERECORD](https://www.varvet.com/blog/retrieving-the-last-n-ordered-records-with-activerecord/)
に書いてある方法です。

```ruby
class Hoge
  class << self
    def endmost(n)
      all.only(:order).from(all.reverse_order.limit(n), table_name)
    end
  end
end
```

onlyやfromなどあまり見慣れないメソッドを使っていますが、発行されるクエリを見るととてもシンプルです
`Hoge.where.not(id: 3).order(:id).endmost(3)`を実行した時発行されるクエリは↓です。

```sql
SELECT "hoges".*
FROM
  (SELECT "hoges".*
   FROM "hoges"
   WHERE ("hoges"."id" != 3)
   ORDER BY "hoges"."id" DESC LIMIT 3) hoges
ORDER BY "hoges"."id" ASC
```

from句のサブクエリとして`where.not(id: 3)`の部分と`下からN件`が処理され、
外側のクエリで`order(:id)`で並び替えられています。

# 方法 :two: 

```ruby
def endmost(n)
  all.only(:order).where(id: all.reverse_order.limit(n).select(:id))
end
```

sqlは↓。

```sql
SELECT "hoges".*
FROM "hoges"
WHERE "hoges"."id" IN
    (SELECT "hoges"."id"
     FROM "hoges"
     WHERE ("hoges"."id" != 3)
     ORDER BY "hoges"."id" DESC LIMIT 3)
ORDER BY "hoges"."id" ASC
```

先程はselect句のサブクエリでしたが、今回はwhere句のサブクエリとして`下からN件`を取得しています。


# :warning:注意点

```ruby
Hoge.where.not(id: 3).order(:id).endmost(3)
```

と

```ruby
Hoge.order(:id).endmost(3).where.not(id: 3)
```

は結果が異なります。
後者のSQLは下になります。(方法1の場合)

```sql
SELECT "hoges".*
FROM
  (SELECT "hoges".*
   FROM "hoges"
   ORDER BY "hoges"."id" DESC LIMIT 3) hoges
WHERE ("hoges"."id" != 3)
ORDER BY "hoges"."id" ASC
```

`where.not(3)`の部分のsqlがサブクエリの中か外か、の違いです。
サブクエリ内で`下から3件`を取得した後、`WHERE ("hoges"."id" != 3)`が処理されることになるので、
結果は、もともと期待していたのは
`[#<Hoge:0x007f83f2b312a8 id: 2>, #<Hoge:0x007f83f2b310c8 id: 4>, #<Hoge:0x007f83f2b30f10 id: 5>]`
の3件だったのに
`[#<Hoge:0x007f83f1c9fc08 id: 4>, #<Hoge:0x007f83f1c8eb10 id: 5>]`
の2件になってしまいます。

重要なのは、

- `endmost`以前の`order`以外は全てサブクエリとして処理される
- `endmost`以降は何を書いても外側のメインのクエリとして処理される

ということです。

```ruby
Hoge.where.not(id: 3).joins(:user).order(:id).endmost(3).where(id: 4).select(:id) # このコードに意味は無いです。すごく適当に作りました
```
としたときに発行されるクエリは↓になります。

```sql
SELECT "id"
FROM
  (SELECT "hoges".*
   FROM "hoges"
   INNER JOIN "users" ON "users"."id" = "hoges"."user_id"
   WHERE ("hoges"."id" != 3)
   ORDER BY "hoges"."id" DESC LIMIT 3) hoges
WHERE "hoges"."id" = 4
ORDER BY "hoges"."id" ASC
```

# :stars:最後に

`方法1`と`方法2`のどちらが良いのかは判断できてないです。
from句書いたことがないのでちょっと怖い気持ちがあるけど、
勘ではクエリ的にはwhere句よりfrom句のサブクエリのほうが早そう、(SQLの気持ちわかってないので実際どっちが効率的なのかは知らないです。。)
というところです。

こっちの方法が良い、とか、この方法は間違っている、とか、もっと良い方法がある、などありましたらコメントをいただけると幸いです:bow:

--------------

ここまででこの記事のメインは終わります。
これ以降は調べてる途中にわかった補足です。

----------------------

# onlyについて

onlyは
`Hoge.where(id: 1).only(:order) # whereが無視される`
`Hoge.order(:id).where(id: 1).only(:order) whereが無視され、orderだけ有効`
というようなメソッドです。
`方法1`では
`all.only(:order).from(all.reverse_order.limit(n), table_name)`
としてましたが、
このonlyの入れる位置はもとても重要です。
これ以降も`方法1`を例にして説明していきます。

### そもそもonlyがない場合

```ruby
def endmost(n)
  all.from(all.reverse_order.limit(n), table_name)
end
Hoge.where.not(id: 3).order(:id).endmost(3)
```

の場合、sqlは↓になります。

```sql
SELECT "hoges".*
FROM
  (SELECT "hoges".*
   FROM "hoges"
   WHERE ("hoges"."id" != 3)
   ORDER BY "hoges"."id" DESC LIMIT 3) hoges
WHERE ("hoges"."id" != 3)
ORDER BY "hoges"."id" ASC
```

`WHERE ("hoges"."id" != 3)`が2回現れています。
結果は変わらないはずですが、無駄に処理が行われてしまいます。

### onlyをfromの中に入れた場合

```ruby
def endmost(n)
  all.from(all.only(:order).reverse_order.limit(n), table_name)
end
Hoge.where.not(id: 3).order(:id).endmost(3)
```

のケースです。
sqlは↓になります。

```sql
SELECT "hoges".*
FROM
  (SELECT "hoges".*
   FROM "hoges"
   ORDER BY "hoges"."id" DESC LIMIT 3) hoges
WHERE ("hoges"."id" != 3)
ORDER BY "hoges"."id" ASC
```

`注意点`のところで書いたSQLと同じです。
`where.not(id: 3)`のwhere句がサブクエリ内ではなく外にいます。
その為結果は、
`[#<Hoge:0x007f83f1c9fc08 id: 4>, #<Hoge:0x007f83f1c8eb10 id: 5>]`
となってしまいます。

# そもそもなぜこのendmostが必要になったか

Query Objectを作ろうとしていて必要になりました。

```ruby
class AwesomeHugaQuery
  def initialize(relation: Huga.all, limit:)
    @relation = relation
    @limit = limit
  end

  def all
    @relation.awesome_scope.order(:id).endmost(@limit) # こんな感じの複雑なクエリ
  end
end

query = AwesomeHugaQuery.new(relation: Huga.active, limit: LIMIT)
query.all.includes(:foo)
query.all.includes(:bar)
```

ということがしたかったのでした。

```ruby
@relation.awesome_scope.order(:id).last(@limit)
@relation.awesome_scope.order(id: :desc).limit(@limit).reverse
```

だとArrayになってしまうので`query.all.includes(:foo)`でエラーになってしまうし、
だからといって

```ruby
@relation.awesome_scope.order(id: :desc).limit(@limit)
query.all.includes(:foo).reverse
query.all.includes(:bar).reverse
```

ともしたくなかったし、

```ruby
def all(association = nil)
  scope = @relation.awesome_scope
  if association
    scope.includes(association)
  end
  scope.order(:id).last(@limit)
end
query.all(:foo)
query.all(:bar)
```

というようなこともしたくなかったので、`endmost`が必要になりました