Selecting from multiple sources

If we wished to list all the tweets in the database, along with the username of their author, you might try writing this:

  1. >>> for tweet in Tweet.select():
  2. ... print(tweet.user.username, '->', tweet.content)
  3. ...
  4. huey -> meow
  5. huey -> hiss
  6. huey -> purr
  7. mickey -> woof
  8. mickey -> whine

There is a big problem with the above loop: it executes an additional query for every tweet to look up the tweet.user foreign-key. For our small table the performance penalty isn’t obvious, but we would find the delays grew as the number of rows increased.

If you’re familiar with SQL, you might remember that it’s possible to SELECT from multiple tables, allowing us to get the tweet content and the username in a single query:

  1. SELECT tweet.content, user.username
  2. FROM tweet
  3. INNER JOIN user ON tweet.user_id = user.id;

Peewee makes this quite easy. In fact, we only need to modify our query a little bit. We tell Peewee we wish to select Tweet.content as well as the User.username field, then we include a join from tweet to user. To make it a bit more obvious that it’s doing the correct thing, we can ask Peewee to return the rows as dictionaries.

  1. >>> for row in Tweet.select(Tweet.content, User.username).join(User).dicts():
  2. ... print(row)
  3. ...
  4. {'content': 'meow', 'username': 'huey'}
  5. {'content': 'hiss', 'username': 'huey'}
  6. {'content': 'purr', 'username': 'huey'}
  7. {'content': 'woof', 'username': 'mickey'}
  8. {'content': 'whine', 'username': 'mickey'}

Now we’ll leave off the call to “.dicts()” and return the rows as Tweet objects. Notice that Peewee assigns the username value to tweet.user.username – NOT tweet.username! Because there is a foreign-key from tweet to user, and we have selected fields from both models, Peewee will reconstruct the model-graph for us:

  1. >>> for tweet in Tweet.select(Tweet.content, User.username).join(User):
  2. ... print(tweet.user.username, '->', tweet.content)
  3. ...
  4. huey -> meow
  5. huey -> hiss
  6. huey -> purr
  7. mickey -> woof
  8. mickey -> whine

If we wish to, we can control where Peewee puts the joined User instance in the above query, by specifying an attr in the join() method:

  1. >>> query = Tweet.select(Tweet.content, User.username).join(User, attr='author')
  2. >>> for tweet in query:
  3. ... print(tweet.author.username, '->', tweet.content)
  4. ...
  5. huey -> meow
  6. huey -> hiss
  7. huey -> purr
  8. mickey -> woof
  9. mickey -> whine

Conversely, if we simply wish all attributes we select to be attributes of the Tweet instance, we can add a call to objects() at the end of our query (similar to how we called dicts()):

  1. >>> for tweet in query.objects():
  2. ... print(tweet.username, '->', tweet.content)
  3. ...
  4. huey -> meow
  5. (etc)

More complex example

As a more complex example, in this query, we will write a single query that selects all the favorites, along with the user who created the favorite, the tweet that was favorited, and that tweet’s author.

In SQL we would write:

  1. SELECT owner.username, tweet.content, author.username AS author
  2. FROM favorite
  3. INNER JOIN user AS owner ON (favorite.user_id = owner.id)
  4. INNER JOIN tweet ON (favorite.tweet_id = tweet.id)
  5. INNER JOIN user AS author ON (tweet.user_id = author.id);

Note that we are selecting from the user table twice - once in the context of the user who created the favorite, and again as the author of the tweet.

With Peewee, we use Model.alias() to alias a model class so it can be referenced twice in a single query:

  1. Owner = User.alias()
  2. query = (Favorite
  3. .select(Favorite, Tweet.content, User.username, Owner.username)
  4. .join(Owner) # Join favorite -> user (owner of favorite).
  5. .switch(Favorite)
  6. .join(Tweet) # Join favorite -> tweet
  7. .join(User)) # Join tweet -> user

We can iterate over the results and access the joined values in the following way. Note how Peewee has resolved the fields from the various models we selected and reconstructed the model graph:

  1. >>> for fav in query:
  2. ... print(fav.user.username, 'liked', fav.tweet.content, 'by', fav.tweet.user.username)
  3. ...
  4. huey liked whine by mickey
  5. mickey liked purr by huey
  6. zaizee liked meow by huey
  7. zaizee liked purr by huey