67

Is it possible to query a firestore collection to get all document that starts with a specific string?

I have gone through the documentation but do not find any suitable query for this.

Bishwajyoti Roy
  • 1,091
  • 1
  • 14
  • 19

6 Answers6

93

You can but it's tricky. You need to search for documents greater than or equal to the string you want and less than a successor key.

For example, to find documents containing a field 'foo' staring with 'bar' you would query:

db.collection(c)
    .where('foo', '>=', 'bar')
    .where('foo', '<', 'bas');

This is actually a technique we use in the client implementation for scanning collections of documents matching a path. Our successor key computation is called by a scanner which is looking for all keys starting with the current user id.

Gil Gilbert
  • 7,722
  • 3
  • 24
  • 25
  • 1
    What is the use of the successor key? – Bishwajyoti Roy Oct 04 '17 at 21:13
  • 3
    The successor key bounds the query such that the results contain only strings starting with a specific string. Without it searching for just foo >= 'bar' would also return 'zoo' since that string is also lexicographically greater than bar. – Gil Gilbert Oct 04 '17 at 21:16
  • @GilGilbert, is it possible to implement contains with this trick? – Deepak Joshi Aug 11 '18 at 16:02
  • Is this case sensitive? – Niklas Raab Oct 15 '18 at 18:05
  • Yes this is case sensitive. – Gil Gilbert Nov 04 '18 at 20:27
  • It's not possible to implement contains this way. This mechanism exploits the arrangement of the index: all strings are arranged in order so we can efficiently find the start and end of the scan. That same index isn't useful for evaluating contains--it's just not ordered to satisfy that query. – Gil Gilbert Nov 04 '18 at 20:36
  • @GilGilbert alternatively, for the less-than condition, instead of changing the last letter of the query, why not simply append the highest possible unicode character (`U+10FFFF`) to the end of the query? Same result, less client-side computing. – youareawaitress Dec 31 '18 at 01:30
  • 1
    @youareawaitress That's a can of worms. While U+10FFFF is reserved as a non character, applications are free to use it for internal purposes. Using it this way excludes such usage (which we wouldn't want to do). Encoding issues also come into play. For example, in Java where strings are UTF-16, U+10FFFF encodes to a surrogate pair (U+DBFF U+DFFF) that could exclude some strings. For arbitrary byte sequences there is no value that's higher than all possible other valid bytes so it's easier to go the way I describe. – Gil Gilbert Jan 31 '19 at 19:08
54

same as answered by Gil Gilbert. Just an enhancement and some sample code. use String.fromCharCode and String.charCodeAt

var strSearch = "start with text here";
var strlength = strSearch.length;
var strFrontCode = strSearch.slice(0, strlength-1);
var strEndCode = strSearch.slice(strlength-1, strSearch.length);

var startcode = strSearch;
var endcode= strFrontCode + String.fromCharCode(strEndCode.charCodeAt(0) + 1);

then filter code like below.

db.collection(c)
.where('foo', '>=', startcode)
.where('foo', '<', endcode);

Works on any Language and any Unicode.

Warning: all search criteria in firestore is CASE SENSITIVE.

atlanteh
  • 5,615
  • 2
  • 33
  • 54
Kyo Kurosagi
  • 2,177
  • 1
  • 18
  • 18
  • Is it possible to do this one two or more fields in the same document. Like: `where('foo', '>=', startcode).where('foo', '<', endcode).where('bar', '<', endcode).where('bar, '<', endcode);` – Erik Jan 23 '18 at 18:54
  • 2
    Maybe this will hit the [Additionally, you can only perform range comparisons (<, <=, >, >=) on a single field](https://firebase.google.com/docs/firestore/query-data/queries) – Erik Jan 23 '18 at 19:04
  • this doesn't work if you're looking for `animal` sending `nima` – Dani Sep 22 '18 at 15:19
  • Thank you for this! – Carlo Nyte Mar 23 '21 at 23:28
22

Extending the previous answers with a shorter version:

  const text = 'start with text here';
  const end = text.replace(/.$/, c => String.fromCharCode(c.charCodeAt(0) + 1));

  query
    .where('stringField', '>=', text)
    .where('stringField', '<', end);

IRL example

async function search(startsWith = '') {
  let query = firestore.collection(COLLECTION.CLIENTS);

  if (startsWith) {
      const end = startsWith.replace(
        /.$/, c => String.fromCharCode(c.charCodeAt(0) + 1),
      );

      query = query
        .where('firstName', '>=', startsWith)
        .where('firstName', '<', end);
  }

  const result = await query
    .orderBy('firstName')
    .get();

  return result;
}
kidroca
  • 3,480
  • 2
  • 27
  • 44
15

If you got here looking for a Dart/Flutter version

Credit to the java answer by Kyo

final strFrontCode = term.substring(0, term.length - 1);
final strEndCode = term.characters.last;
final limit =
  strFrontCode + String.fromCharCode(strEndCode.codeUnitAt(0) + 1);

final snap = await FirebaseFirestore.instance
  .collection('someCollection')
  .where('someField', isGreaterThanOrEqualTo: term)
  .where('someField', isLessThan: limit)
  .get();
Alex.F
  • 5,648
  • 3
  • 39
  • 63
6

I found this, which works perfectly for startsWith

const q = query(
  collection(firebaseApp.db, 'capturedPhotos'),
  where('name', '>=', name),
  where('name', '<=', name + '\uf8ff')
)
danday74
  • 52,471
  • 49
  • 232
  • 283
2

The above are correct! Just wanted to give an updated answer!

var end = s[s.length-1]
val newEnding = ++end

var newString = s
newString.dropLast(1)
newString += newEnding

query
  .whereGreaterThanOrEqualTo(key, s)
  .whereLessThan(key, newString)
  .get()