Retrieving Data
The real strength of our database is in how it allows us to retrieve data through queries. Relational databases are excellent for making ad-hoc queries.
Getting single records
Let’s retrieve Grandma’s record from the database. To get a single record from the database, use Select.get()
:
grandma = Person.select().where(Person.name == 'Grandma L.').get()
We can also use the equivalent shorthand Model.get()
:
grandma = Person.get(Person.name == 'Grandma L.')
Lists of records
Let’s list all the people in the database:
for person in Person.select():
print(person.name)
# prints:
# Bob
# Grandma L.
# Herb
Let’s list all the cats and their owner’s name:
query = Pet.select().where(Pet.animal_type == 'cat')
for pet in query:
print(pet.name, pet.owner.name)
# prints:
# Kitty Bob
# Mittens Jr Herb
Attention
There is a big problem with the previous query: because we are accessing pet.owner.name
and we did not select this relation in our original query, peewee will have to perform an additional query to retrieve the pet’s owner. This behavior is referred to as N+1 and it should generally be avoided.
For an in-depth guide to working with relationships and joins, refer to the Relationships and Joins documentation.
We can avoid the extra queries by selecting both Pet and Person, and adding a join.
query = (Pet
.select(Pet, Person)
.join(Person)
.where(Pet.animal_type == 'cat'))
for pet in query:
print(pet.name, pet.owner.name)
# prints:
# Kitty Bob
# Mittens Jr Herb
Let’s get all the pets owned by Bob:
for pet in Pet.select().join(Person).where(Person.name == 'Bob'):
print(pet.name)
# prints:
# Kitty
# Fido
We can do another cool thing here to get bob’s pets. Since we already have an object to represent Bob, we can do this instead:
for pet in Pet.select().where(Pet.owner == uncle_bob):
print(pet.name)
Sorting
Let’s make sure these are sorted alphabetically by adding an order_by()
clause:
for pet in Pet.select().where(Pet.owner == uncle_bob).order_by(Pet.name):
print(pet.name)
# prints:
# Fido
# Kitty
Let’s list all the people now, youngest to oldest:
for person in Person.select().order_by(Person.birthday.desc()):
print(person.name, person.birthday)
# prints:
# Bob 1960-01-15
# Herb 1950-05-05
# Grandma L. 1935-03-01
Combining filter expressions
Peewee supports arbitrarily-nested expressions. Let’s get all the people whose birthday was either:
- before 1940 (grandma)
- after 1959 (bob)
d1940 = date(1940, 1, 1)
d1960 = date(1960, 1, 1)
query = (Person
.select()
.where((Person.birthday < d1940) | (Person.birthday > d1960)))
for person in query:
print(person.name, person.birthday)
# prints:
# Bob 1960-01-15
# Grandma L. 1935-03-01
Now let’s do the opposite. People whose birthday is between 1940 and 1960:
query = (Person
.select()
.where(Person.birthday.between(d1940, d1960)))
for person in query:
print(person.name, person.birthday)
# prints:
# Herb 1950-05-05
Aggregates and Prefetch
Now let’s list all the people and how many pets they have:
for person in Person.select():
print(person.name, person.pets.count(), 'pets')
# prints:
# Bob 2 pets
# Grandma L. 0 pets
# Herb 1 pets
Once again we’ve run into a classic example of N+1 query behavior. In this case, we’re executing an additional query for every Person
returned by the original SELECT
! We can avoid this by performing a JOIN and using a SQL function to aggregate the results.
query = (Person
.select(Person, fn.COUNT(Pet.id).alias('pet_count'))
.join(Pet, JOIN.LEFT_OUTER) # include people without pets.
.group_by(Person)
.order_by(Person.name))
for person in query:
# "pet_count" becomes an attribute on the returned model instances.
print(person.name, person.pet_count, 'pets')
# prints:
# Bob 2 pets
# Grandma L. 0 pets
# Herb 1 pets
Now let’s list all the people and the names of all their pets. As you may have guessed, this could easily turn into another N+1 situation if we’re not careful.
Before diving into the code, consider how this example is different from the earlier example where we listed all the pets and their owner’s name. A pet can only have one owner, so when we performed the join from Pet
to Person
, there was always going to be a single match. The situation is different when we are joining from Person
to Pet
because a person may have zero pets or they may have several pets. Because we’re using a relational databases, if we were to do a join from Person
to Pet
then every person with multiple pets would be repeated, once for each pet.
It would look like this:
query = (Person
.select(Person, Pet)
.join(Pet, JOIN.LEFT_OUTER)
.order_by(Person.name, Pet.name))
for person in query:
# We need to check if they have a pet instance attached, since not all
# people have pets.
if hasattr(person, 'pet'):
print(person.name, person.pet.name)
else:
print(person.name, 'no pets')
# prints:
# Bob Fido
# Bob Kitty
# Grandma L. no pets
# Herb Mittens Jr
Usually this type of duplication is undesirable. To accommodate the more common (and intuitive) workflow of listing a person and attaching a list of that person’s pets, we can use a special method called prefetch()
:
query = Person.select().order_by(Person.name).prefetch(Pet)
for person in query:
print(person.name)
for pet in person.pets:
print(' *', pet.name)
# prints:
# Bob
# * Kitty
# * Fido
# Grandma L.
# Herb
# * Mittens Jr
SQL Functions
One last query. This will use a SQL function to find all people whose names start with either an upper or lower-case G:
expression = fn.Lower(fn.Substr(Person.name, 1, 1)) == 'g'
for person in Person.select().where(expression):
print(person.name)
# prints:
# Grandma L.