shimotsu tech

Webフロントエンドエンジニア @ to-R inc.

Supabase のデータ取得の際に、任意のプロパティで複数の条件に合致するレコードを取得したい

Supabase の DB からデータを取得する際に、少し凝ったフィルタリングの実装に悩んだのでメモがてら書いておきます。

通常

supabase.from() メソッドでデータを取得する際、通常は以下のように eq() メソッドを繋ぎ、条件を指定します。この例では、posts テーブルから、 user_id が合致するレコードを取得しています。

const { data: posts } = await supabase
    .from("posts")
    .select('*')
    .eq('user_id', userId) // user_id の値が合致するレコードを抽出

任意のプロパティで、複数の条件に合致するレコードを取得したいケース

今回少し悩んだのが、任意のプロパティで、複数の条件に合致するレコードを取得したいケースです。

例えば、posts テーブルに is_published(公開されているか否か) というカラムがあると仮定して、is_published の値が true なレコードのみを抽出したい場合は以下のようにすればOKです。これは単純です。

const { data: posts } = await supabase
    .from("posts")
    .select('*')
    .eq('is_published', "true")

次に、URLのクエリで ?tab=all?tab=public?tab=private のようにフィルタリングをかける例を考えてみます。以下のようなイメージです。

  1. ?tab=all : 全件取得(is_publishedの値がtrueかつfalse` なレコードを取得)
  2. ?tab=public : is_published の値が true なレコードのみ取得
  3. ?tab=private : is_published の値が false なレコードのみ集約

2と3は上記の例のとおりに書けば取得できます。こんな感じで、クエリの値を見て条件を絞ればいいでしょう。

const specificValueFromQuery = () => {
    if (query.tab === 'public') return 'true'
    if (query.tab === 'private') return 'false'
  }

const { data: posts } = await supabase
    .from("posts")
    .select('*')
    .eq('is_published', specificValueFromQuery())

しかし、問題となるのが1のパターンです。?tab=all の場合は全件取得したいですが、eq() メソッドで絞り込んでいる以上、 is_publishedtrue または false のレコードを取得することはできません。

Supabase のjavascript クライアントでの型定義を見ても、eq() メソッドの第二引数は一意の値でないといけなさそうです(true | false とかで指定できるとベターだった)。

/**
   * Finds all rows whose value on the stated `column` exactly matches the
   * specified `value`.
   *
   * @param column  The column to filter on.
   * @param value  The value to filter with.
   */
  eq(column: keyof T, value: T[keyof T]): this {
    this.url.searchParams.append(`${column}`, `eq.${value}`)
    return this
  }

この場合、そもそも .eq() メソッドが不要なので外したいが、ちょっとそれは難しそう。 でも、このために2回以上APIを叩くのは無駄だし、どうしたものか...。

解決策

苦肉の策ですが、全件取得した後のデータに対して、query の値を条件にフィルタリングをかけ、新しい配列を生成するようにしました。

const { data: rowPosts } = await supabase
    .from("posts")
    .select('*')
    .eq('user_id', id)

  const posts = rowPosts?.filter((post) => {
    if (!query.tab) return post

    if (query.tab === 'public') return post.is_published
    if (query.tab === 'private') return !post.is_published
  })

Supabase 側の実装では実現できそうになかったので、苦し紛れの方法になってしまいましたが、なんとか条件を満たすフィルタリングができるようになりました。