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 onearray-containsclause per query. You can't combinearray-containswitharray-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 フィールドが Tokyo か Osaka のクエリが取得できます。
const q = query(jobsRef, where('location', 'in', ["Tokyo", "Osaka"]));以下のように配列を渡すこともできますが、その場合は"location フィールドがTokyo か Osaka のみを配列に含むクエリ"が取得できます。array-contains-any ではTokyo か Osaka を配列に含むクエリでしたので、その違いに気をつけて下さい。
const q = query(jobsRef, where('location', 'in', [["Tokyo", "Osaka"]]));さいごに
Firestoreは便利な機能が豊富にある反面、フィルタリングという側面では絶妙にかゆいところに手が届かないという印象が私の中であります。この絶妙な不便さ、どうにかならないものでしょうか。。。