採用はこちら!

Shinonome Tech Blog

株式会社Shinonomeの技術ブログ
3 min read

Firestoreでのタグのフィルタリングについて

Firestoreのタグフィルタリングのやり方についてまとめた備忘録です。

はじめに

バックエンドの藤岡です。

NoSQL構造であるFirestoreは大変便利ですよね。ですが、その扱いには多少の癖があります。今回はタグのフィルタリング方法について少し手間取ったので、備忘録としてそのやり方をまとめようと思います。

まず、タグの構造をどのようにするかは、タグをどのように使うかによると思います。一先ず今回は jobs というコレクションに、 tags というタグを入れる配列があるというシンプルなやり方でいきます。

jobs: {
	// ...
    tags: string[] // タグの名前やID入る配列
}

単一のタグの取得

まずは一つのタグを取得する場合を考えます。特定のタグを配列フィールドからフィルタリングするには array-contains を使用します。

const q = query(jobsRef, where("tags", "array-contains", "Typescript"));

取得したクエリのソートは orderBy により簡単にできます。

const q = query(jobsRef, where("tags", "array-contains", "Typescript"), orderBy("createdAt"));

複数タグのORフィルタリング

次に複数タグのORフィルタリングを考えます。複数のタグを配列フィールドからフィルタリングするには array-contains-any を使用します。

const q = query(jobsRef, where("tags", "array-contains-any", ["Typescript", "Java"]));

取得したクエリのソートは orderBy により簡単にできます。

const q = query(jobsRef, where("tags", "array-contains-any", ["Typescript", "Java"]), orderBy("createdAt"));

複数タグのANDフィルタリング

次に複数タグのANDフィルタリングを考えます。実は、現在(2023/01/31)Firestoreでは配列フィールドのAND検索に対応してありません。以下のようにarray-contains を複数回使用してANDフィルタリングしようとすると、エラーを吐いてしまいます。

const q = query(jobsRef, where("tags", "array-contains", "Typescript"), , where("tags", "array-contains", "Typescript", orderBy("createdAt"));

エラーメッセージ:

FirebaseError: Invalid query. You cannot use more than one 'array-contains' filter.

公式ドキュメントでも、このように書かれています。(Firestore公式ドキュメントより)

You can use at most one array-contains clause per query. You can't combine array-contains with array-contains-any.

解決策

今の状態ではタグのANDフィルタリングはできません。そこで、タグの保存方法を配列からmapに変えます。

// これから
tags: [
  'Typescript',
  'Java',
  'C++'
]
// これに変更
tags: {
  Typescript: true,
  Java: true,
  C++: true
}

こうすると、以下のようにANDフィルタリングが可能になります。

const q = query(jobsRef, where("tags.Typescript", "==", "true"), where("tags.Java", "==", "true"));

取得したクエリのソートは orderBy により簡単にできます。

const q = query(jobsRef, where("tags.Typescript", "==", "true"), where("tags.Java", "==", "true"), orderBy("createdAt"));

また、読み込みのドキュメントが莫大な数でない限り、ソートをクライアント側で処理することもできるでしょう。

inによるフィルタリング

最後に in を用いたフィルタリングを紹介します。 in は特定のフィールドに対してのORフィルタリングができます。以下の例ですと、 location フィールドが TokyoOsaka のクエリが取得できます。

const q = query(jobsRef, where('location', 'in', ["Tokyo", "Osaka"]));

以下のように配列を渡すこともできますが、その場合は"location フィールドがTokyoOsaka のみを配列に含むクエリ"が取得できます。array-contains-any ではTokyo Osaka を配列に含むクエリでしたので、その違いに気をつけて下さい。

const q = query(jobsRef, where('location', 'in', [["Tokyo", "Osaka"]]));

さいごに

Firestoreは便利な機能が豊富にある反面、フィルタリングという側面では絶妙にかゆいところに手が届かないという印象が私の中であります。この絶妙な不便さ、どうにかならないものでしょうか。。。

参考文献

How to implement tag filtering in Firestore
Approaches to tagging functionality with Firestore and their limitations.
【Firebase】Cloud Firestore クエリ まとめ
Perform simple and compound queries in Cloud Firestore | Firebase
Order and limit data with Cloud Firestore | Firebase