Querying and Filtering
Table of contents
- Example data
- Simple Queries
- Get First result
- Query operators
- List membership
- in and array-contains-any
- Compound queries
- Custom Query Method
- Sub collection
- Filter with nested Model
- Filter with Reference Model
- Collection group queries
- Query limitations
FireO provides powerful query functionality for specifying which documents you want to retrieve from a collection. These queries can also be used with either get() or fetch()
Example data
To get started, write some data about cities so we can look at different ways to read it back:
class City(Model):
short_name = IDField()
name = TextField()
state = TextField()
country = TextField()
capital = BooleanField()
population = NumberField()
regions = ListField()
City.collection.create(
short_name='SF', name='San Francisco', state='CA', country='USA',
capital=False, population=860000, regions=['west_coast', 'norcal']
)
City.collection.create(
short_name='LA', name='Los Angeles', state='CA', country='USA',
capital=False, population=3900000, regions=['west_coast', 'socal']
)
City.collection.create(
short_name='DC', name='Washington D.C.', state='CA', country='USA',
capital=True, population=680000, regions=['east_coast']
)
City.collection.create(
short_name='TOK', name='Tokyo', country='Japan',
capital=True, population=9000000, regions=['kanto', 'honshu']
)
City.collection.create(
short_name='BJ', name='Beijing', country='China',
capital=True, population=21500000, regions=['hebei']
)
Simple Queries
The following query returns all cities with state CA
cities = City.collection.filter('state', '==', 'CA').fetch()
The following query returns all the capital cities
cities = City.collection.filter('capital', '==', True).fetch()
For equality(==) operator you can direct assign value and this will work.
For example:
City.collection.filter(state='CA') # Equal to 'state', '==', 'CA'
Multiple equality operator can also apply in same single filter
City.collection.filter(state='CA', capital=True)
Direct assignment is only work with equality(==) operator for others user regular filter
For example:
Get cities with state CA and population is greater than 1000000
City.collection.filter(state='CA').filter('population', '>', 1000000)
Get First result
After creating a query object, use the get() function to retrieve the first matching result. fetch() return generator for retrieve all mating result.
The following query returns first matching city with state CA
city = City.collection.filter('state', '==', 'CA').get()
Query operators
The filter() method takes three parameters: a field to filter on, a comparison operation, and a value. The comparison can be <, <=, ==, >, >=, array-contains, in and array-contains-any.
Some example filters:
City.collection.filter('state', '==', 'CA')
City.collection.filter('population', '<', 1000000)
City.collection.filter('name', '>=', 'San Francisco')
List membership
You can use the array-contains operator to filter based on list values. For example:
query = City.collection.filter('regions', 'array_contains', 'west_coast')
This query returns every city document where the regions field is an array that contains west_coast. If the array has multiple instances of the value you query on, the document is included in the results only once.
in and array-contains-any
Use the in operator to combine up to 10 equality (==) clauses on the same field with a logical OR. An in query returns documents where the given field matches any of the comparison values. For example:
query = City.filter('country', 'in', ['USA', 'Japan'])
This query returns every city document where the country field is set to USA or Japan. From the example data, this includes the SF, LA, DC, and TOK documents.
Similarly, use the array-contains-any operator to combine up to 10 array-contains clauses on the same field with a logical OR. An array-contains-any query returns documents where the given field is an array that contains one or more of the comparison values:
query = City.filter(
'regions', 'array_contains_any', ['west_coast', 'east_coast']
)
This query returns every city document where the region field is an array that contains west_coast or east_coast. From the example data, this includes the SF, LA, and DC documents.
Results from array-contains-any are de-duped. Even if a document’s array field matches more than one of the comparison values, the result set includes that document only once.
array-contains-any always filters by the array data type. For example, the query above would not return a city document where instead of an array, the region field is the string west_coast.
You can use an array value as a comparison value for in, but unlike array_contains_any, the clause matches for an exact match of array length, order, and values. For example:
query = City.filter(
'regions', 'in', [['west_coast'], ['east_coast']]
)
This query returns every city document where the region field is an array that contains exactly one element of either west_coast or east_coast. From the example data, only the DC document qualifies with its region field of ["east_coast"]. The SF document, however, does not match because its region field is ["west_coast", "norcal"].
Limitations
Note the following limitations for in and array-contains-any:
inandarray-contains-anysupport up to 10 comparison values.- You can use only one
inorarray-contains-anyclause per query. You can’t use bothinandarray-contains-anyin the same query. - You can combine
array-containswithinbut not witharray-contains-any.
Compound queries
You can also chain multiple filter() methods to create more specific queries (logical AND). However, to combine the equality operator (==) with a range or array-contains clause (<, <=, >, >=, or array-contains), make sure to create a composite index.
sydney_query = City.collection.filter('state', '==', 'CO').filter('name', '==', 'Denver')
large_us_cities_query = City.collection.filter('state', '==', 'CA').filter('population', '>', 1000000)
You can only perform range comparisons (<, <=, >, >=) on a single field, and you can include at most one array-contains clause in a compound query:
Valid: Range filters on only one field
City.collection.filter('state', '>=', 'CA').filter('state', '<=', 'IN')
Invalid: Range filters on different fields
City.collection.filter('state', '>=', 'CA').filter('population', '>=', 1000000)
Custom Query Method
Instead of creating query again and again you can create @classmethod
Example Usage
class City(Model):
# .. code ..
@classmethod
def get_capital_cities(cls):
return cls.collection.filter('capital', '==', True).fetch()
# The following query returns all the capital cities
capital_cities = City.get_capital_cities()
More useful Example
class Post(Model):
title = TextField()
content = TextField()
@classmethod
def get_detail(cls, key):
post = cls.collection.get(key)
recent_review = Review.collection.parent(key).order('-created_on').fetch(3)
return post, recent_review
class Review(Model):
name = TextField()
stars = NumberField()
created_on = DateTime(auto=True)
p = Post(title="First Post", content="Post Content")
p.save()
r1 = Review(parent=p.key)
r1.name = "Azeem"
r1.stars = 5
r1.save()
r2 = Review(parent=p.key)
r2.name = "Azeem"
r2.stars = 4
r2.save()
r2 = Review(parent=p.key)
r2.name = "Arfan"
r2.stars = 3
r2.save()
Get Post and most recent Reviews
post, reviews = Post.get_detail(post_key)
print(post.title)
for r in reviews:
print(r.name, r.stars)
Sub collection
Sub collection queries work in same fashion but you need to pass parent_key to search in specific collection.
Sample Data
class Post(Model):
title = TextField()
content = TextField()
class Review(Model):
name = TextField()
stars = NumberField()
p = Post(title="First Post", content="Some Content")
p.save()
r1 = Review(parent=p.key)
r1.name = 'Azeem'
r1.stars = 5
r1.save()
r2 = Review(parent=p.key)
r2.name = 'Arfan'
r2.stars = 3
r2.save()
Example Usage
The following query returns all reviews which is posted by Azeem
reviews = Review.collection.parent(post_key).filter('name', '==', 'Azeem').fetch()
Filter with nested Model
You can filter models with nested_models fields using dot(.) notation.
Example Usage
class Level1(Model):
name = TextField()
class Level2(Model):
name = TextField()
lev1 = NestedModel(Level1)
class Level3(Model):
name = TextField()
lev2 = NestedModel(Level2)
# Adding data
l = Level3()
l.name = 'level 1'
l.lev2.name = 'level 2'
l.lev2.lev1.name = 'level 3'
l.save()
# Filtering data
l = Level3.collection.filter('lev2.lev.name', '==', 'level 3').get()
Filter with Reference Model
You can filter models with reference model key.
Example Usage
class Company(Model):
name = TextField()
class Employee(Model):
name = TextField()
company = ReferenceField(Company, auto_load=False)
# Adding data
c = Company(name="Abc_company")
c.save()
e = Employee()
e.name = 'Employee Name'
e.company = c
e.save()
# Filtering data
el = Employee.collection.filter(company=c.key).fetch()
Collection group queries
A collection group consists of all collections with the same ID. By default, queries retrieve results from a single collection in your database. Use a collection group query to retrieve documents from a collection group instead of from a single collection.
For example, you can create a landmarks collection group by adding a landmarks subcollection to each city:
class City(Model):
state = TextField()
class Landmarks(Model):
type = TextField()
name = TextField()
c = City.collection.create(state='SF')
Landmarks.collection.parent(c.key).create(type='bridge', name='Golden Gate Bridge')
Landmarks.collection.parent(c.key).create(type='museum', name='Legion of Honor')
c = City.collection.create(state='LA')
Landmarks.collection.parent(c.key).create(type='park', name='Griffith Park')
Landmarks.collection.parent(c.key).create(type='museum', name='The Getty')
c = City.collection.create(state='DC')
Landmarks.collection.parent(c.key).create(type='memorial', name='Lincoln Memorial')
Landmarks.collection.parent(c.key).create(type='museum', name='National Air and Space Museum')
We can use the simple and compound query described earlier to query a single city’s landmarks subcollection, but you might also want to retrieve results from every city’s landmarks subcollection at once.
The landmarks collection group consists of all collections with the ID landmarks, and you can query it using a collection group query. For example, this collection group query retrieves all museum landmarks across all cities:
museums = Landmarks.collection.filter('type', '==', 'museum').group_fetch()
for museum in museums:
print(museum.name)
Query limitations
Cloud Firestore does not support the following types of queries:
- Queries with range filters on different fields, as described in the previous section.
- Queries with a
!=clause. In this case, you should split the query into a greater-than query and a less-than query. For example, although the query clausefilter("age", "!=", "30")is not supported, you can get the same result set by combining two queries, one with the clausefilter("age", "<", "30")and one with the clausefilter("age", ">", 30). - Cloud Firestore provides limited support for logical
ORqueries. Theinandarray-contains-anyoperators support a logicalORof up to 10 equality (==) orarray-containsconditions on a single field. For other cases, create a separate query for eachORcondition and merge the query results in your app.