😊

PL/pgSQLでページングされた一覧と全件数を取得する

2024/03/27に公開

目的

アプリケーションサーバーとDBサーバーの通信は少なければ少ないほどパフォーマンスはよくなります。ここではPL/pgSQLを使って通信数を減らすテクニックを紹介します。

ページングありの一覧のページを考えます。当然1ページのデータを取得しますが、ページングのためには全件数が必要になります。普通なら1ページのデータを取得するのと、全件数を取得するSQLは戻り値がまったく違うので2回呼び出すことになります。

で、ちょっといんちきして、1回のPL/pgSQLの呼び出しで完了するようにします。

コード

テーブル

bookテーブルを考えます。一覧取得は作成日順で取得します。

DROP TABLE IF EXISTS books;
CREATE TABLE books (
    uuid UUID NOT NULL DEFAULT gen_random_uuid()
    ,title TEXT NOT NULL
    ,created_at TIMESTAMPTZ
    ,PRIMARY KEY (uuid)
);

PL/pgSQL

取得するコードは以下のようになります。
p_titleでタイトルをLIKEで検索します。p_limitが1ページあたりのサイズ、p_offsetがずらす数です。
p_with_count_flagがTRUEの時に一覧と一緒に数を返します。
全件数は無理やりタイトルにつっこんで返します。取得したアプリケーションサーバーで分解して利用します。

DROP TYPE IF EXISTS type_my_get_booke_for_paging CASCADE;
CREATE TYPE type_my_get_booke_for_paging AS (
  uuid UUID
  ,title TEXT
);
CREATE OR REPLACE FUNCTION my_get_booke_for_paging (
  p_title TEXT DEFAULT NULL
  ,p_limit BIGINT DEFAULT NULL
  ,p_offset BIGINT DEFAULT 0
  ,p_with_count_flag BOOLEAN DEFAULT FALSE
) RETURNS SETOF type_my_get_booke_for_paging AS $FUNCTION$
DECLARE
BEGIN
  RETURN QUERY SELECT
    t1.uuid
    ,t1.title
  FROM
    public.books AS t1
  WHERE
    p_title IS NULL OR title LIKE p_title
  ORDER BY
    t1.created_at
  LIMIT
    p_limit
  OFFSET
    p_offset
  ;

  IF p_with_count_flag IS TRUE THEN
    RETURN QUERY SELECT
      NULL::UUID
      ,COUNT(*)::TEXT
    FROM
      public.books
    WHERE
      p_title IS NULL OR title LIKE p_title
    ;
  END IF;
END;
$FUNCTION$ LANGUAGE plpgsql;

まとめ

型を無視して適当なカラムに全件数を返すいんちき技です。あまり良くないコードなのでこのあたりはPL/pgSQLもアプリケーション側のコードも自動生成して人が触らないような工夫をしています。

Discussion