jypi
  • Explore
ChatWays to LearnMind mapAbout

jypi

  • About Us
  • Our Mission
  • Team
  • Careers

Resources

  • Ways to Learn
  • Mind map
  • Blog
  • Help Center
  • Community Guidelines
  • Contributor Guide

Legal

  • Terms of Service
  • Privacy Policy
  • Cookie Policy
  • Content Policy

Connect

  • Twitter
  • Discord
  • Instagram
  • Contact Us
jypi

© 2026 jypi. All rights reserved.

CS50 - Web Programming with Python and JavaScript
Chapters

1Orientation and Web Foundations

2Tools, Workflow, and Git

3HTML5 and Semantic Structure

4CSS3, Layouts, and Responsive Design

5Python Fundamentals for the Web

6Flask, Routing, and Templates

7Data, SQL, and ORM Patterns

Relational modeling basicsNormalization and denormalizationSQLite setup and usageSQL select insert update deleteJoins and subqueriesIndexes and query planningTransactions and isolationConnection poolingMigrations with AlembicSQLAlchemy core conceptsORM models and relationshipsLazy and eager loadingValidation and constraintsData seeding and fixturesCaching strategies

8State, Sessions, and Authentication

9JavaScript Essentials and the DOM

10Asynchronous JS, APIs, and JSON

11Frontend Components and React Basics

12Testing, Security, and Deployment

Courses/CS50 - Web Programming with Python and JavaScript/Data, SQL, and ORM Patterns

Data, SQL, and ORM Patterns

31507 views

Model, query, and evolve data with SQL, migrations, and ORM techniques for robust persistence.

Content

5 of 15

Joins and subqueries

SQL Joins and Subqueries Explained for Web Developers
3183 views
beginner
web-development
orm
humorous
computer-science
gpt-5-mini
3183 views

Versions:

SQL Joins and Subqueries Explained for Web Developers

Watch & Learn

AI-discovered learning video

Sign in to watch the learning video for this topic.

Sign inSign up free

Start learning for free

Sign up to save progress, unlock study materials, and track your learning.

  • Bookmark content and pick up later
  • AI-generated study materials
  • Flashcards, timelines, and more
  • Progress tracking and certificates

Free to join · No credit card required

Joins and Subqueries — Make Your Data Talk to Each Other

"This is the moment where separate tables stop ghosting each other and start collaborating." — your future self after mastering joins

You're already comfortable with SELECT/INSERT/UPDATE/DELETE and you've set up SQLite and Flask routes/templates. Now we're leveling up: how to combine data across tables (joins) and how to ask nested questions of the database (subqueries). These are the engine that powers real web apps — feeds, leaderboards, recommendations, and anything that says "combine stuff from two places and show me the result."


Why this matters (and where you saw it before)

  • When you built endpoints and templates, you probably rendered a user's posts or a post's comments — that's joining concepts across tables. Now we'll do it in SQL and ORM-land explicitly.
  • Good joins = correct and performant pages. Bad joins = wrong counts, duplicated rows, or painfully slow load times.

Think of tables as cast members in a play: a join tells them to share the same stage. A subquery is when one actor whispers to another and uses that info in their line.


Table example (our running scenario)

We'll use a simple blog-like schema:

CREATE TABLE users (id INTEGER PRIMARY KEY, username TEXT);
CREATE TABLE posts (id INTEGER PRIMARY KEY, user_id INTEGER, title TEXT);
CREATE TABLE comments (id INTEGER PRIMARY KEY, post_id INTEGER, body TEXT);

You're comfortable inserting/selecting from single tables; let's combine them.


Joins: the basics and the practical

INNER JOIN — match and show only matches

Use when you want rows that have partners on both sides.

SELECT users.username, posts.title
FROM users
JOIN posts ON users.id = posts.user_id;

This returns only users with posts. If Alice has zero posts, she is absent from the result.

Real-world analogy

Inner join = "show me only pairs who RSVP'd to the party." If you didn't RSVP, you don't get shown.

LEFT JOIN (LEFT OUTER JOIN) — keep left rows even if no match

SELECT users.username, posts.title
FROM users
LEFT JOIN posts ON users.id = posts.user_id;

This shows all users; users without posts will have NULL for posts.title. Use when you need to list everything from the left side and include related data if it exists.

Note: SQLite doesn't support RIGHT JOIN or FULL OUTER JOIN directly. You can flip sides for RIGHT JOIN (swap tables) or emulate FULL using UNIONs.

Common pitfalls with joins

  • Ambiguous column names: when both tables have id, qualify them: users.id AS user_id.
  • Duplicates: joining a one-to-many relationship (user -> posts) then also joining comments may multiply rows. Use aggregates or distinct as needed.

Subqueries — queries inside queries (nesting dolls of SQL)

Subqueries let you compute a value or a set and use it in an outer query.

Example: Find users who wrote more than 5 posts

SELECT username
FROM users
WHERE id IN (
  SELECT user_id
  FROM posts
  GROUP BY user_id
  HAVING COUNT(*) > 5
);

This asks the database: "Which user_id values belong to prolific posters?" Then the outer query selects user details for those IDs.

Correlated subquery — the dynamic whisper

A correlated subquery refers to the outer query's row. For example, get each post's comment count:

SELECT p.id, p.title,
  (SELECT COUNT(*) FROM comments c WHERE c.post_id = p.id) AS comment_count
FROM posts p;

The inner SELECT runs per post and counts its comments. Simple, expressive — but watch performance if posts are many.

Scalar subquery vs. IN-subquery

  • Scalar subquery: returns single value (used like a column)
  • IN-subquery: returns a set used for membership tests

Which to use: join or subquery?

Short answer: it depends.

  • Use joins when you want combined rows and plan to filter/sort on joined columns. Joins are often more readable and the DB optimizer can do more.
  • Use subqueries for set membership or when you need aggregated results used as a filter.

Why do people keep misunderstanding this? Because both can often express the same logic. Test both and check EXPLAIN if performance matters.


ORM patterns (SQLAlchemy with Flask) — because you love Python

Assume declarative models:

class User(Base):
    __tablename__ = 'users'
    id = Column(Integer, primary_key=True)
    username = Column(String)
    posts = relationship('Post', back_populates='author')

class Post(Base):
    __tablename__ = 'posts'
    id = Column(Integer, primary_key=True)
    user_id = Column(Integer, ForeignKey('users.id'))
    title = Column(String)
    author = relationship('User', back_populates='posts')

ORM join (eager loading)

# join to get posts with their users in one roundtrip
posts = session.query(Post).join(Post.author).filter(User.username == 'alice').all()

# eager-load to avoid N+1 when rendering templates
from sqlalchemy.orm import joinedload
posts = session.query(Post).options(joinedload(Post.author)).all()

ORM subquery example

Find users with > 5 posts using a subquery:

from sqlalchemy import func
subq = (session.query(Post.user_id, func.count(Post.id).label('cnt'))
        .group_by(Post.user_id)
        .having(func.count(Post.id) > 5)
        .subquery())

users = session.query(User).join(subq, User.id == subq.c.user_id).all()

ORMs often give you options: Express the same logic with query.join or with subquery constructs. Pick the style that matches clarity and performance.


Rendering in Jinja (Flask) without N+1 problems

In a route you might do:

@app.route('/feed')
def feed():
    posts = session.query(Post).options(joinedload(Post.author)).all()
    return render_template('feed.html', posts=posts)

Then in feed.html:

{% for post in posts %}
  <h2>{{ post.title }}</h2>
  <p>By {{ post.author.username }}</p>
{% endfor %}

If you forgot joinedload, each post.author access could trigger another DB query (N+1 problem). That's the ORM equivalent of calling everyone individually to ask "who wrote this?" — painfully slow.


Performance tips & debugging

  • Use EXPLAIN (or EXPLAIN QUERY PLAN in SQLite) to see what the DB is doing.
  • Prefer indexed join columns (foreign keys). Indexes make joins fast.
  • Watch row multiplication: if you need aggregates (counts), use GROUP BY instead of pulling all rows then counting in Python.
  • For heavy correlated subqueries, consider rewriting as a join + GROUP BY.

Quick comparative cheatsheet

  • INNER JOIN: return matches only
  • LEFT JOIN: return all left rows, matched or not
  • Correlated subquery: subquery refers to outer row (runs per row)
  • Subquery (set): used to filter outer query by membership
  • ORM: use joinedload for eager loading, .join for filtering across relationships

Final notes — TL;DR and a memorable line

  • Joins = blending rows from different tables. Subqueries = asking the DB another question inside your query.
  • Both are tools; choose for clarity and performance.

Memorable insight: If your page is slow and your ORM is doing lots of tiny queries, your joins or eager loading are probably in jail — bail them out.

Key takeaways

  • Use INNER JOIN to get only matching rows, LEFT JOIN to keep the left side.
  • Subqueries are great for set-membership and per-row calculations; correlated subqueries trade clarity for potential slowness.
  • In Flask + SQLAlchemy, use joinedload to avoid N+1 and session.query.join/subquery for complex filters.
  • Test with EXPLAIN and add indexes on join keys.

Go build a route that shows users and their latest post counts — and then pat yourself on the back. You just taught two stubborn tables to cooperate.

Flashcards
Mind Map
Speed Challenge

Comments (0)

Please sign in to leave a comment.

No comments yet. Be the first to comment!

Ready to practice?

Sign up now to study with flashcards, practice questions, and more — and track your progress on this topic.

Study with flashcards, timelines, and more
Earn certificates for completed courses
Bookmark content for later reference
Track your progress across all topics