Question #449MediumDatabase & StorageImportant

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
sqflite
package), joins allow you to combine data from multiple tables. The two most common types are
text
INNER JOIN
and
text
LEFT JOIN
, and they differ in how they handle non-matching rows.


INNER 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.

dart
Future<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
NULL
.

dart
Future<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
users
and
text
orders
:

dart
// 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:

nameproduct
AliceFlutter Book
AliceDart Course
BobKeyboard

Charlie is excluded — no matching orders.

LEFT JOIN result:

nameproduct
AliceFlutter Book
AliceDart Course
BobKeyboard
CharlieNULL

Charlie is included — with

text
NULL
for order columns.


Finding Records with No Match

A powerful

text
LEFT JOIN
pattern — find users who have never placed an order:

dart
Future<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

text
INNER JOIN
.


Key Differences

FeatureINNER JOINLEFT JOIN
Non-matching left rowsExcludedIncluded (right columns =
text
NULL
)
Result set sizeSmaller or equalEqual or larger
NULL valuesOnly from source dataYes, for non-matching rows
Find orphan recordsNot possibleYes (
text
WHERE right.col IS NULL
)
PerformanceGenerally fasterSlightly slower
SQLite aliasNone
text
LEFT OUTER JOIN
is identical

When to Use Each

Use

text
INNER JOIN
when:

  • 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

text
LEFT JOIN
when:

  • 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

text
RIGHT JOIN
or
text
FULL OUTER JOIN
. To achieve right-join behavior, swap the table order and use
text
LEFT JOIN
. For full outer join, use a
text
UNION
of two left joins.

Learn more at sqflite package documentation and SQLite JOIN syntax.