Closed kakeru-one closed 1 month ago
OFFSETには何件目から取得するかを指定する 例えばOFFSETに20を指定すれば21件目から表示することになる LIMITにはOFFSETで指定した場所から何件取得するのか SQLの例(21件目から取得する場合)
SELECT *
FROM memos
LIMIT 10 OFFSET 20;
ページ計算
(page - 1) * limit
limitは定数にする
LIMIT_COUNT = 10
pageはクエリパラメータで取得する
これでOFFSETは計算できる。
@kuri0616
何故、OFFSETページネーションは遅いのか
この記事読むといいですよ〜! https://use-the-index-luke.com/ja/sql/partial-results/fetch-next-page
@ochi-sho-private-study ありがとうございます! 読んでみます!
全体のメモ件数から算出した合計ページ数をレスポンスに含める必要がある COUNTで行数を取得して計算 フロント側でページネーションの表示に必要
COUNT()OVER()でデータと総件数を同時に取得できるらしいが、フルスキャンになってめちゃくちゃパフォーマンスが落ちるらしいから、別クエリの方が良い???
@ochi-sho-private-study 読みました〜 今回、IDでソートしているからシーク法使えるんじゃないかと思ったんですけど、検索条件によって昇順、降順変わるし、実装複雑になりそうですね😭笑 メモアプリは長期間運用しても総レコード数大したことないと思うので、おとなしくOFFSETにします😭
@ochi-sho-private-study 1点、相談したいのですが ページネーション機能は、別にページネーションモジュールを作成して、Queryモジュールで検索された結果に対して、行う感じで考えているのですが、どうでしょうか?
def index
memos = Memo::Query.resolve(memos: Memo.all, params: params)
paginated_memos = Pagination.apply(scope: memos, params: params)
render json: { memos: paginated_memos }, status: :ok
end
イメージとしてはこんな感じで呼び出す感じでと思っているのですが。 あくまでも検索とページネーションは別の機能なので単一責任の原則という点で考えても切り分けた方が良いかなと思ったからです。あとは。indexアクションみた時も1行毎に何の処理をしているのかわかりやすいかなと。 上記の方法だとクエリ発行の回数が増えたり、するのでしょうか? ActiveRecordは遅延評価と聞いたので、検索条件にLIMITとOFFSETを含めて1回のクエリで発行してくれるんですかね? この辺あまり理解できておらず・・
@kuri0616
ページネーション機能は、別にページネーションモジュールを作成して、Queryモジュールで検索された結果に対して、行う感じで考えているのですが、どうでしょうか?
それでもいいと思いますが、 個人的にこの程度の役割ならまとめてしまっていいと思っていて(pagenationもクエリを発行するため、Queryと言える)、 一応以下のように実装すると、MemoQueryを呼び出すだけで良くなりそうです。 (pagenateする前のcountが必要なければ、FILTERSにPageFilterを入れられるので、もっと記述が簡単になりそう。)
module PageFilter
MAX_ITEMS = 10.0
public_constant :MAX_ITEMS
FIRST_PAGE = 1
private_constant :FIRST_PAGE
class << self
def resolve(scope:, params:)
return scope.limit(MAX_ITEMS) if params[:page].blank?
# 整数と文字列数値以外はエラーとする
target_page = Integer(params[:page], 10, exception: false) || params[:page]
raise TypeError unless target_page.is_a?(Integer)
[target_page - 1, 0].max.then do |page|
scope.offset(MAX_ITEMS * page).limit(MAX_ITEMS)
end
end
end
end
module MemoQuery
FILTERS = [
TitleFilter,
ContentFilter,
OrderFilter
].freeze
private_constant :FILTERS
class << self
def call(filter_collection:, params:)
memo_relation = \
filtered_memos(
filter_collection: filter_collection,
params: params
)
count = memo_relation.count
[
PageFilter.resolve(scope: memo_relation, params: params),
count,
count.zero? ? 1 : (memo_relation.count / PageFilter::MAX_ITEMS).ceil,
page_number(params[:page])
]
end
private
def filtered_memos(filter_collection:, params:)
FILTERS.reduce(filter_collection) do |scope, filter|
filter.resolve(scope: scope, params: params)
end
end
def page_number(page)
return Integer(page) if page.present?
1
rescue ArgumentError
1
end
end
end
上記の方法だとクエリ発行の回数が増えたり、するのでしょうか? ActiveRecordは遅延評価と聞いたので、検索条件にLIMITとOFFSETを含めて1回のクエリで発行してくれるんですかね? この辺あまり理解できておらず・・
これは試してみたらいいと思います!
(LIMITとOFFSETを含めて1回のクエリで発行してくれる
で合ってるはず!)
@ochi-sho-private-study 実装例ありがとうございます!! 大変、勉強になります!こんな方法は、全く持って思いつきませんでした😂笑
1つ、質問なのですが! こちらintegerメソッドでは例外を発生しないようにして手動でintegerかチェックしてTypeErrorを発生させるようにしている理由は何故ですか?? 変換できない時に発生させられるデフォルトのArgumentErrorでは何か問題がある感じなんですかね??
target_page = Integer(params[:page], 10, exception: false) || params[:page]
raise TypeError unless target_page.is_a?(Integer)
[target_page - 1, 0].max.then do |page|
scope.offset(MAX_ITEMS * page).limit(MAX_ITEMS)
end
配列で最小値持たせて、maxで0とることで負の数ならないようにするアプローチも色々な場面で応用できそうで学びなります
こういうのってどうやって考えるんですかね?😭笑
@kuri0616
こちらintegerメソッドでは例外を発生しないようにして手動でintegerかチェックしてTypeErrorを発生させるようにしている理由は何故ですか??
integerメソッドでは例外を発生しないようにしているのは、 params[:page]が整数だったときにArgumentErrorがraiseしないようにするためです!
irb(main):014:0> Integer(2, 10)
Traceback (most recent call last):
5: from /usr/bin/irb:23:in `<main>'
4: from /usr/bin/irb:23:in `load'
3: from /Library/Ruby/Gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
2: from (irb):14
1: from (irb):14:in `Integer'
ArgumentError (base specified for non string value)
こういうのってどうやって考えるんですかね?😭笑
実務のコード読んだり、OSS読んだりすると思いつくようになると思います!
Reference①: https://zenn.dev/takahashim/articles/ac725fb16ec7a11809c5 Reference②: https://github.com/dodonki1223/rails_oss Reference③: https://zenn.dev/kitabatake/books/learn-from-devto-data-update-script
ゴール
参考