Here is the SQL:

    We’ll do this by creating a subquery which selects each user and the timestamp of their latest tweet. Then we can query the tweets table in the outer query and join on the user and timestamp combination from the subquery.

    1. # we will be querying from the Tweet model directly in the outer query.
    2. Latest = Tweet.alias()
    3. latest_query = (Latest
    4. .select(Latest.user, fn.MAX(Latest.timestamp).alias('max_ts'))
    5. .group_by(Latest.user)
    6. .alias('latest_query'))
    7. # Our join predicate will ensure that we match tweets based on their
    8. # timestamp *and* user_id.
    9. predicate = ((Tweet.user == latest_query.c.user_id) &
    10. # We put it all together, querying from tweet and joining on the subquery
    11. query = (Tweet
    12. .select(Tweet, User) # Select all columns from tweet and user.
    13. .join(latest_query, on=predicate) # Join tweet -> subquery.
    14. .join_from(Tweet, User)) # Join from tweet -> user.

    There are a couple things you may not have seen before in the code we used to create the query in this section:

    • We used to explicitly specify the join context. We wrote .join_from(Tweet, User), which is equivalent to .switch(Tweet).join(User).
    • We referenced columns in the subquery using the magic .c attribute, for example latest_query.c.max_ts. The .c attribute is used to dynamically create column references.
    • Instead of passing individual fields to Tweet.select(), we passed the Tweet and User models. This is shorthand for selecting all fields on the given model.

    In the previous section we joined on a subquery, but we could just as easily have used a common-table expression (CTE). We will repeat the same query as before, listing users and their latest tweets, but this time we will do it using a CTE.

    1. SELECT user_id, MAX(timestamp) AS max_ts
    2. GROUP BY user_id)
    3. SELECT tweet.*, user.*
    4. FROM tweet
    5. INNER JOIN latest
    6. ON ((latest.user_id = tweet.user_id) AND (latest.max_ts = tweet.timestamp))
    7. INNER JOIN user
    8. ON (tweet.user_id = user.id)

    This example looks very similar to the previous example with the subquery:

    We can iterate over the result-set, which consists of the latest tweets for each user:

    1. >>> for tweet in query:
    2. ... print(tweet.user.username, '->', tweet.content)
    3. ...
    4. huey -> purr

    For more information about using CTEs, including information on writing recursive CTEs, see the section of the “Querying” document.