Selecting from multiple sources

If we wished to list all the tweets in the database, along with the username oftheir 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 forevery tweet to look up the tweet.user foreign-key. For our small table theperformance penalty isn’t obvious, but we would find the delays grew as thenumber of rows increased.

If you’re familiar with SQL, you might remember that it’s possible to SELECTfrom multiple tables, allowing us to get the tweet content and the usernamein 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 alittle bit. We tell Peewee we wish to select Tweet.content as well asthe 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 askPeewee 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 Tweetobjects. Notice that Peewee assigns the username value totweet.user.username – NOT tweet.username! Because there is aforeign-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 inthe 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 ofthe Tweet instance, we can add a call to objects() atthe 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 thatselects all the favorites, along with the user who created the favorite, thetweet 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 ofthe 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 bereferenced 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 followingway. Note how Peewee has resolved the fields from the various models weselected 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