When using local database SQLite in Flutter, what is the difference between INNER JOIN vs LEFT JOIN?
#flutter#sqlite#sqflite#database#sql#joins
Answer
Overview
When working with SQLite in Flutter (using the
text
sqflitetext
INNER JOINtext
LEFT JOININNER JOIN
Returns only rows that have matching records in both tables. If a row in the left table has no corresponding row in the right table, it is excluded from the results.
dartFuture<List<Map<String, dynamic>>> getUsersWithOrders() async { final db = await database; return db.rawQuery(''' SELECT users.name, orders.product, orders.amount FROM users INNER JOIN orders ON users.id = orders.user_id '''); } // Only users who have placed orders appear in results
LEFT JOIN (LEFT OUTER JOIN)
Returns all rows from the left table, regardless of whether a match exists in the right table. Non-matching right-side columns are filled with
text
NULLdartFuture<List<Map<String, dynamic>>> getAllUsersWithOrders() async { final db = await database; return db.rawQuery(''' SELECT users.name, orders.product, orders.amount FROM users LEFT JOIN orders ON users.id = orders.user_id '''); } // ALL users appear — those without orders have NULL for product/amount
Practical Example
Consider two tables —
text
userstext
ordersdart// Setup tables await db.execute(''' CREATE TABLE users ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL ) '''); await db.execute(''' CREATE TABLE orders ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, product TEXT NOT NULL, FOREIGN KEY (user_id) REFERENCES users(id) ) '''); // Data: Alice has 2 orders, Bob has 1 order, Charlie has 0 orders
INNER JOIN result:
| name | product |
|---|---|
| Alice | Flutter Book |
| Alice | Dart Course |
| Bob | Keyboard |
Charlie is excluded — no matching orders.
LEFT JOIN result:
| name | product |
|---|---|
| Alice | Flutter Book |
| Alice | Dart Course |
| Bob | Keyboard |
| Charlie | NULL |
Charlie is included — with
for order columns.textNULL
Finding Records with No Match
A powerful
text
LEFT JOINdartFuture<List<Map<String, dynamic>>> getUsersWithNoOrders() async { final db = await database; return db.rawQuery(''' SELECT users.name FROM users LEFT JOIN orders ON users.id = orders.user_id WHERE orders.id IS NULL '''); } // Result: [{ name: Charlie }]
This pattern is not possible with
.textINNER JOIN
Key Differences
| Feature | INNER JOIN | LEFT JOIN |
|---|---|---|
| Non-matching left rows | Excluded | Included (right columns = text |
| Result set size | Smaller or equal | Equal or larger |
| NULL values | Only from source data | Yes, for non-matching rows |
| Find orphan records | Not possible | Yes ( text |
| Performance | Generally faster | Slightly slower |
| SQLite alias | None | text |
When to Use Each
Use when:textINNER JOIN
- You only want records with valid relationships in both tables
- Displaying orders with user details (skip invalid references)
- Building reports that exclude incomplete data
Use when:textLEFT JOIN
- You need all records from the primary table regardless of matches
- Finding records with no related data (orphan detection)
- Building dashboards where missing data should show as zero/N/A
- Optional enrichment — joining with supplementary data that may not exist
Note: SQLite does not support
ortextRIGHT JOIN. To achieve right-join behavior, swap the table order and usetextFULL OUTER JOIN. For full outer join, use atextLEFT JOINof two left joins.textUNION
Learn more at sqflite package documentation and SQLite JOIN syntax.