Recent chats
ASKGEEZ Q&A GUIDETechnology & devices

How do I join two tables using more than one matching column?

Quick answer

Add both matching conditions to the JOIN’s ON clause and connect them with AND. For example, ON e.course = o.course AND e.term = o.term matches an enrollment only to the offering with the same course and term. Matching on course alone can pair a record with offerings from other terms. The examples below use invented data and SQLite syntax.

LET’S WORK THROUGH IT

The question, the details, the next step.

Question

I have an enrollments table and a course offerings table. A course can run in multiple terms, so course alone doesn't identify the right offering. How should I join them?

Geez

Use both columns in the ON clause, joined with AND. In this example, the intended match is the pair (course, term): an enrollment for CS101 in Fall should match the CS101 Fall offering, not CS101 in Spring. SQLite evaluates the join condition for candidate row pairs; for an inner join, only pairs where the condition is true are included. [S1]

Here is a small, invented SQLite example. The names and records are illustrative, not real enrollment data:

CREATE TABLE offerings (
  course TEXT,
  term TEXT,
  title TEXT
);

CREATE TABLE enrollments (
  student TEXT,
  course TEXT,
  term TEXT
);

INSERT INTO offerings VALUES
  ('CS101', 'Fall', 'Intro to Computing'),
  ('CS101', 'Spring', 'Computing Projects'),
  ('MATH200', 'Fall', 'Algebra');

INSERT INTO enrollments VALUES
  ('Ari', 'CS101', 'Fall'),
  ('Bea', 'CS101', 'Spring'),
  ('Cal', 'MATH200', 'Fall');

SELECT e.student, e.course, e.term, o.title
FROM enrollments AS e
JOIN offerings AS o
  ON e.course = o.course
 AND e.term = o.term;

The result has three rows: Ari, CS101, Fall, Intro to Computing; Bea, CS101, Spring, Computing Projects; and Cal, MATH200, Fall, Algebra. Each enrollment matches the offering with both values in common.

Follow-up question

What goes wrong if I only match the course? Can you show the difference with these same rows?

Geez

If you use ON e.course = o.course alone, both CS101 enrollments can match both CS101 offerings. The join has no term condition to rule out the cross-term pairs. With the invented rows above, that incomplete join returns five rows:

SELECT e.student, e.course, e.term, o.term AS offering_term, o.title
FROM enrollments AS e
JOIN offerings AS o
  ON e.course = o.course;

Its results include Ari’s Fall enrollment paired with the Spring offering, and Bea’s Spring enrollment paired with the Fall offering. It also returns each student’s correct-term match and Cal’s one MATH200 match. By contrast, adding the term comparison leaves three matching rows. The SQLite documentation’s join-condition guidance supports this distinction; it does not establish whether (course, term) is the right key for every real dataset. [S1]

Follow-up question

Could I write this with USING instead, and what should I check if I get unexpected results?

Geez

SQLite allows USING with multiple shared column names; each listed column comparison must be true for the match. [S1] Since both example tables have columns named course and term, you could write:

SELECT student, course, term, title
FROM enrollments
JOIN offerings USING (course, term);

The explicit ON version makes it especially clear which table supplies each comparison, so it can be easier to inspect while learning or troubleshooting. If a result is missing or unexpected, check that the values in both tables agree exactly in both columns, and that the query includes both comparisons. Also check that you are joining the intended columns rather than similarly named columns with different meanings. This example shows matching logic; it does not verify your schema or establish which database systems accept the same syntax.

Follow-up question

Can you give me a reusable pattern and a quick way to review it before I use it?

Geez

Start by writing down the complete set of values that identifies the intended match, then add one equality condition per value. For this example, the pair is course plus term. Adapt the table names and column names to your own schema:

SELECT left_table.*, right_table.*
FROM left_table
JOIN right_table
  ON left_table.key_part_1 = right_table.key_part_1
 AND left_table.key_part_2 = right_table.key_part_2;

Before relying on the result, test a few records where the first column matches but the second differs. Those are the cases a one-column join may pair incorrectly. If your real tables have additional matching requirements, include those only when they are part of the intended relationship. The sample does not determine whether either table has unique rows for each course-term pair, or whether another column is needed; inspect your data and requirements to decide.

A practical checklist

  • Write down which columns together identify the intended match.

  • Include one comparison for each matching column in the ON clause, joined with AND.

  • Test a case where the first column matches but another differs.

  • Review sample result rows for cross-term or otherwise unintended pairings.

  • Confirm whether your real data requires additional matching columns.

A few more useful answers

Does `AND` mean either condition can match?

No. In this join pattern, both comparisons must be true for the pair to match. SQLite also documents USING with multiple shared columns as requiring all listed comparisons to be true. [S1]

Is `USING (course, term)` interchangeable with the example’s `ON` clause?

For SQLite, the documented USING form can compare multiple shared column names, and all listed comparisons must be true. The source does not establish syntax or behavior for every other database system. [S1]

Does joining on both columns guarantee one result per enrollment?

Not by itself. The example demonstrates matching conditions, but the source does not establish uniqueness in your tables; check whether the matching pair occurs more than once.

Sources

References used for this guide.

  1. [S1] SQLite SELECT: join conditions and matching rows
    sqlite.org · Checked

YOUR QUESTION. YOUR DETAILS.

Let’s work through your situation.

Get a tailored answer or help with your next task. Starts at $1, with a quote before you pay.

Ask your own question
← Back to Geez at Work