Home › Python › No Such Table: Fix Django OperationalError Fast
Beginner 4 min · September 23, 2026

No Such Table: Fix Django OperationalError Fast

Run showmigrations, apply migrate, verify the DB file.

N
Naren Founder & Principal Engineer

20+ years shipping production Python across data and backend systems. Drawn from code that ran under real load.

Follow
✓ Production
production tested
October 31, 2026
last updated
2,099
articles · all by Naren
Before you start⏱ 11 min
  • ✓Basic Django: models, migrations, and manage.py
  • ✓Running scripts with python3 and reading tracebacks
  • ✓How databases store tables (SQLite basics)
 ● Production Incident 🔎 Debug Guide
⚡Quick Answer
  • Run showmigrations to see what's applied, then migrate so schema ships before code ever queries it
  • A model without a migration file is invisible — generate with makemigrations, review, commit, then migrate
  • Confirm the app reads the database you migrated; dev sqlite vs staging Postgres splits cause phantom tables
  • Keep queries lazy: module-level evaluation runs before fresh databases exist, so wrap it in functions
✦ Definition~90s read
What is Django OperationalError No Such Table Fix?

Django's ORM maps each model to a database table, and migrations are the versioned scripts that build those tables: makemigrations records schema changes into files, migrate executes them against a database in dependency order. showmigrations lists which files have been applied (marked [X]) to the current database. django.db.utils.OperationalError: no such table is the database driver reporting that a query named a table absent from the schema it searched — the ORM asked honestly, and the database answered truthfully.

★
Think of models as architectural blueprints and the database as the built house.

The mismatch has exactly five common sources. Unapplied migrations mean the file exists but never ran here. Missing files mean the model was never recorded, so nothing could run. Wrong-database settings mean the table was built somewhere the app isn't reading.

Stale --keepdb test databases mean the schema predates new migrations. Import-time queries mean SQL ran before any schema could exist. Each produces the identical error string with a different table name, which is why the table name alone never diagnoses it.

Test and deploy contexts add the sharp edges. The test runner creates schema by applying the full chain — fast when squashed, skipped when --keepdb reuses stale state. Deploy platforms order code rollout and migrate steps per your configuration, defaulting in some setups to concurrent (code beside schema) rather than sequential (schema before code).

The mental model is physical: a query can only read tables built in the database it opened, after the migration that built them, evaluated at a time the database already exists.

Plain-English First

Think of models as architectural blueprints and the database as the built house. A blueprint for a new pantry doesn't put shelves on the wall — builders (migrations) must construct it first. No-such-table means someone tried to store jam in a pantry that was drawn but never built. Either the builders never came, they built the wrong house, or someone reached for the shelf before construction started.

Your view, shell command, or test crashes with django.db.utils.OperationalError: no such table: myapp_order. The model exists. The migration might even exist. But the database your process is reading has no such table, and Django won't invent one at query time. This error is a schema-reality mismatch, and its causes split neatly into five buckets you'll learn to check in order.

The impact ranges from annoying to severe. Locally it's a 2-minute migrate. In CI it's a red suite blocking every merge. In production it's checkout 500s because the deploy ran code before schema, or tests passing against a stale kept database while fresh installs crash. One variant — queries evaluated at import time — breaks every fresh environment while working perfectly on the developer's seasoned laptop.

This guide covers all five: unapplied migrations, missing migration files, wrong-database settings, --keepdb test staleness, and import-time queries. You'll get the triage order that names the cause in two minutes, the exact commands for each fix, and deploy patterns that keep schema ahead of code forever.

Migrations Unapplied: Schema Ships Before Code

The most common cause is also the simplest: the migration exists but was never applied to the database being read. You added a model, a teammate's branch added a table, or a fresh environment was provisioned — and nobody ran migrate against this particular database. Django models describe desired schema; only the migrator creates it. Querying before that step raises OperationalError naming the absent table, which is the database telling the truth about its contents.

Confirm with showmigrations: applied migrations carry [X], pending ones [ ]. If your app's latest migration shows [ ], the fix is python manage.py migrate — nothing more exotic. Verify afterward at the database level (dbshell plus .tables on SQLite, or the information-schema query on Postgres) rather than trusting the command's summary line. The table must exist where the app reads, not just where you assume it does.

The production lesson is ordering: migrate before traffic, every deploy, as a blocking step. Release-phase hooks, init containers, and entrypoint scripts all exist for this; pick the one your platform supports and make failed migrates block rollout. Code that queries a table must never reach a dyno whose database lacks it — the 47-minute outage in this article's incident came from deploying code beside schema instead of after it.

django_no_such_table_repro.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import sqlite3

# No-such-table reproduced with stdlib sqlite3 (same OperationalError).
con = sqlite3.connect(":memory:")
try:
    con.execute("SELECT * FROM shop_order")
except sqlite3.OperationalError as exc:
    print("reproduced:", exc)

# The migrate equivalent: create the table, then query.
con.execute("CREATE TABLE shop_order (id INTEGER PRIMARY KEY, total REAL)")
con.execute("INSERT INTO shop_order VALUES (1, 59.99)")
print("after migrate:", con.execute("SELECT * FROM shop_order").fetchall())

# Triage mapping:
#   showmigrations -> which migrations carry [X]
#   migrate        -> CREATE TABLE equivalents applied in order
#   dbshell .tables -> verify the table now exists
🎯 Key Takeaway
showmigrations names pending work; migrate creates it — block traffic until migrate exits zero.

Missing Migration File: The Invisible Model

A model without a migration file is invisible to the migrator. migrate can run a hundred times and create nothing, because it only executes operations recorded in files under myapp/migrations/. This happens when a developer adds a model and forgets makemigrations, when the file is generated but never committed, or when a squash-and-rebuild cycle drops an operation. The symptom is distinctive: migrate reports nothing to apply, yet the table is missing.

Diagnose with the check command: makemigrations --check --dry-run exits nonzero when model state diverges from migration files, printing what it would generate. If it proposes a CreateModel for your missing table, you've found the gap. Generate the file with makemigrations myapp, read the operations (auto-detection occasionally misreads renames as drop-plus-create — fix those by hand), commit the file alongside the model change, then migrate.

Gate this in CI so it can't recur: the check command fails the build when files and models diverge, forcing the file into the same pull request as the model. Migration files are code — they get reviewed, tested, and deployed like code. Teams that enforce the same-commit rule stop seeing phantom-table failures because the schema change and its construction script can never separate.

django_missing_migration_file.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
import os

# Migration-file presence check (run from the repo root).
app = "myapp"
mig_dir = os.path.join(app, "migrations")
print("check these in YOUR repo:")
print(f"  ls {mig_dir}/            # expect 0001_initial.py, 0002_..., ...")
print("  python manage.py makemigrations --check --dry-run")
print("  # exit 0 = files match models; exit 1 = a file is missing")
print("  git log --oneline -5 --", mig_dir + "/")
print("  # to inspect tables: python manage.py dbshell, then .tables (SQLite) or \\dt (Postgres)")
print("  # or: python manage.py inspectdb | grep -i <table>")
print("rule: every model change ships its migration file in the same commit")
🎯 Key Takeaway
No file means no table — generate, review the operations, commit with the model, and gate CI with --check.

Wrong DB in Settings: The Table Exists Elsewhere

Sometimes the table exists — just not in the database your process reads. Development points at db.sqlite3 while a second settings file points at prod.sqlite3; staging uses Postgres while a debug override uses SQLite; two dynos mount different volumes. You migrate database A with full success, the app reads database B with full confidence, and the error persists through every retry because both sides are behaving correctly against different files.

The tell is identity: print DATABASES['default'] from the exact settings module the failing process loads, then compare NAME (or HOST/PORT for server databases) against where you ran migrate. SQLite makes this concrete — list *.sqlite3 files with sizes and timestamps; a 0-byte file created 5 minutes ago next to a 40MB file from last month means the app is reading a fresh empty database while you migrated the old one.

Standardize ruthlessly. One canonical DATABASES per environment, committed or templated, with the database NAME echoed into deploy logs at boot. Run migrate against that same NAME in the same job that deploys code. When the logs name the file or host on every boot, wrong-database mysteries collapse to a one-line diff instead of a two-hour hunt across volumes and settings files.

django_wrong_db.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
import os

# Which database would Django read? Print DATABASES identity.
print("candidate sqlite files in cwd:")
for f in sorted(os.listdir(".")):
    if f.endswith(".sqlite3"):
        print(f"  {f}  ({os.path.getsize(f)} bytes)")
print("then compare with settings:")
print("  grep -n -A 8 'DATABASES' mysite/settings*.py")
print("  # NAME must match the file you migrated")
print("rule: migrate the NAME the app reads, not the one you assume")
🎯 Key Takeaway
Print DATABASES identity from the failing context and migrate that exact NAME — one canonical DB per environment.

--keepdb Tests: Stale Schema, Green Runserver

Django's test runner builds a fresh database by applying every migration, then destroys it — unless --keepdb tells it to reuse last run's database. Reuse is fast and usually fine, until the migration set changes: new tables from new migrations don't exist in the kept schema, and tests fail with no-such-table while runserver (against your dev database) works perfectly. The failure looks like broken tests but is really a stale cache.

Confirm by forcing one clean rebuild: run the suite once without --keepdb (or delete the test_ database) and watch the failures vanish. The fix for local work is periodic recreation; the policy for CI is never passing --keepdb at all, so the pipeline always validates the full migration chain from zero. Fast suites that skip schema creation prove nothing about fresh installs — the exact environments new customers and new teammates hit.

Keep migrations quick so full rebuilds stay painless: squash long chains, avoid data migrations that loop millions of rows in tests (gate them behind settings flags), and keep test settings on SQLite or a disposable Postgres schema. When creating from scratch takes seconds, nobody reaches for --keepdb as a crutch, and the staleness class disappears with it.

🔥Keepdb is a cache, not a contract
Reuse test databases with --keepdb for speed, but rebuild from zero in CI. A kept schema from last month's migrations can't validate this month's tables.
🎯 Key Takeaway
Recreate test databases from zero in CI; reserve --keepdb for local speed with periodic fresh rebuilds.

Querying at Import Time: SQL Before Schema Exists

The sneakiest variant works on every seasoned machine and fails on every fresh one. A QuerySet evaluated at import time — a module-level FEATURED = Product.objects.filter(...), a class attribute, a decorator argument, a default parameter — executes its SQL during app loading, before any migrate could create the table. The developer's laptop has the table from months ago, so imports succeed; a new container, a rebuilt database, or a test runner hits no-such-table during check or collection, long before any test runs.

Find these with a targeted grep: QuerySet calls (objects.all/filter/get) at module or class scope, outside any function body. The fix is laziness — move the query into a method, a property, or get_queryset() so it executes per request against a ready database. Startup data needs belong behind AppConfig.ready() with a connection guard, or behind post_migrate signals that run after schema exists by definition.

Prove the fix on an empty database: build fresh (migrate --run-syncdb on a blank file), then run check and collect tests with zero queries at import. If collection passes against nothing, the import-time dependency is gone. This is the one no-such-table variant that code review must catch, because no amount of migrating fixes a query that runs before migrations exist.

🎯 Key Takeaway
No QuerySet evaluation at import scope — defer to methods, properties, or ready() gates that run after schema exists.
ExampleCODE
1
2
3
4
5
6
7
8
9
10
11
12
13
import sqlite3

# Post-fix verification: assert the table exists before traffic.
con = sqlite3.connect(":memory:")
con.execute("CREATE TABLE shop_coupon (id INTEGER PRIMARY KEY, code TEXT)")

tables = {r[0] for r in con.execute(
    "SELECT name FROM sqlite_master WHERE type='table'")}
required = {"shop_coupon"}
missing = required - tables
print("tables:", sorted(tables))
print("missing:", sorted(missing) or "none - traffic may proceed")
assert not missing, f"block deploy
● Production incidentPOST-MORTEMseverity: high

Code Beat Schema to Production: 1,180 Checkouts Failed

Symptom
At 11:04 a.m. on a sale day, checkout 500s jumped to 100% on 3 of 4 dynos seconds after release 2.41.0 went live. The 4th dyno (which had opportunistically migrated first) served normally, producing a maddening pattern where every 4th retry succeeded. The error was uniform — no such table: shop_coupon on every failure — and 1,180 checkout attempts failed over 47 minutes. The rollback was delayed because reverting code without reverting schema would have broken the already-migrated dyno.
Assumption
The team assumed migrations were safe to run after traffic because the change was additive and staging showed zero downtime. Staging, though, runs one web dyno with the SQLite file on local disk, while production runs 4 dynos behind a router — and the release-phase hook had been disabled 2 weeks earlier during a buildpack fight. Nobody verified schema timing, because the deploy dashboard only tracks code rollout, not migrate exit codes.
Root cause
Release 2.41.0 added the coupon table via migration 0041, but the pipeline deployed code to all 4 production dynos concurrently with the migrate step instead of before it. Every checkout POST in the 47-minute window hit a dyno whose code queried shop_coupon before migration 0041 had run there, raising OperationalError: no such table: shop_coupon. The release-phase hook that once ordered this correctly had been disabled 2 weeks prior. The first dyno to finish migrating recovered, but the router kept sending traffic to the 3 unmigrated ones, so the outage persisted until an engineer manually sequenced scale-down, migrate, and scale-up.
Fix
The fix took 19 minutes: scale web to 0, run python manage.py migrate (applying 0041 in 8 seconds), then scale back to 4 dynos and replay the 1,180 failed checkouts from the idempotency log. The release-phase migrate hook was restored and made blocking, a /health/ check now asserts the table exists before the router sends traffic, and CI fails the build when migration files diverge from models. Error rate fell from 100% to 0.01% within 4 minutes of the migrate.
Key lesson
  • Schema ships before traffic: run migrate as a blocking release step, never beside or after code rollout.
  • Health checks must assert schema readiness (table exists), not just process liveness.
  • Stage the deploy topology, not just the code — single-node staging hides multi-node migration races.
Production debug guideSymptom → Root cause → Fix5 entries
Symptom · 01
No such table right after adding a model
→
Fix
List migration state: python manage.py showmigrations myapp (applied ones carry [X]) and python manage.py makemigrations --check --dry-run (nonzero exit means model state diverged from files). Apply with python manage.py migrate, then confirm the table: run python manage.py dbshell and type .tables for sqlite or \dt for Postgres, or use python manage.py inspectdb | grep -i <table>.
Symptom · 02
migrate says nothing to apply but the table is missing
→
Fix
Find the file gap: ls myapp/migrations/ and python manage.py makemigrations myapp --dry-run --verbosity 3 to see what Django would generate. If it proposes CreateModel for your table, the file was never made or never committed — check git status --short myapp/migrations/ and git log --oneline -5 -- myapp/migrations/. Generate, review, commit, migrate.
Symptom · 03
Table exists somewhere but the app can't see it
→
Fix
Print the actual database identity: python -c "import os, django; os.environ.setdefault('DJANGO_SETTINGS_MODULE','mysite.settings'); django.setup(); from django.conf import settings; print(settings.DATABASES['default'])" and ls -la .sqlite3 2>/dev/null; grep -rn "DATABASES" mysite/settings.py | head. Migrate the NAME the app reads, not the one you assume — then re-run showmigrations against that same NAME.
Symptom · 04
Tests fail with no such table but runserver works
→
Fix
Drop the stale kept database and rebuild: python manage.py test myapp --keepdb -v 2 2>&1 | tail -5 to see reuse messaging, then force recreation once with python manage.py test myapp --noinput (or delete the test_ database). In CI, never pass --keepdb; always build from zero so the suite validates the full migration chain.
Symptom · 05
Fresh installs crash while seasoned laptops work
→
Fix
Find import-time evaluation: grep -rn "objects\.\(all\|filter\|get\)\|\.all()" myapp/ --include='*.py' | grep -v "def \|return\|test" | head -20. Any hit at class or module scope (defaults, attributes, decorator args) runs at import. Wrap it in a function or get_queryset(), then prove laziness: a fresh migrate --run-syncdb on an empty DB followed by python manage.py check should pass with zero queries.
No Such Table Causes at a Glance
Root CauseHow to ConfirmFixPrevention
Migrations unappliedshowmigrations shows [ ] on the app's latest migrationRun migrate before routing trafficBlocking migrate release step on every deploy
Migration file missingmakemigrations --check exits 1 proposing CreateModelGenerate, review operations, commit, migrateCI gate on --check; file ships in the same commit
Wrong database configuredDATABASES NAME differs from the migrated DB file/hostMigrate the exact NAME the app readsOne canonical DATABASES per env, logged at boot
Stale --keepdb test databaseClean rebuild passes; kept DB predates new migrationsRecreate once; never --keepdb in CIFast squashed migrations so fresh builds stay cheap
Query evaluated at import timegrep finds objects.filter/all at module or class scopeDefer to methods/properties/ready() gatesEmpty-DB collection check in CI; review for laziness
⚙ Quick Reference
4 commands from this guide
FileCommand / CodePurpose
django_no_such_table_repro.pycon = sqlite3.connect(":memory:")Migrations Unapplied
django_missing_migration_file.pyapp = "myapp"Missing Migration File
django_wrong_db.pyprint("candidate sqlite files in cwd:")Wrong DB in Settings
con = sqlite3.connect(":memory:") configuration

Key takeaways

1
No-such-table means the process's database lacks the table
check applied migrations, files, DB identity, and query timing.
2
Migrate in the deploy pipeline before traffic; never ship code ahead of its schema.
3
Commit every migration file and gate CI with makemigrations --check so models and files can't diverge.
4
One canonical DATABASES per environment, printed at boot, so migrate and the app never target different DBs.
5
Keep queries lazy
no QuerySet evaluation at import time; use methods, properties, or ready() gates.
6
Recreate test DBs from scratch in CI; treat --keepdb as a local speed tool, not a correctness guarantee.

Common mistakes to avoid

6 patterns
×

Deploying code concurrently with migrations

Symptom
Some dynos serve 500s while migrated ones pass; every Nth retry succeeds.
Fix
Run migrate as a blocking release step; route traffic only on exit zero.
×

Adding a model without makemigrations

Symptom
migrate reports nothing to apply while the table stays missing forever.
Fix
Generate the file, read its operations, commit it with the model change.
×

Maintaining two settings files with different databases

Symptom
Migrations succeed and the app still can't see the table — different files.
Fix
Point every context at one canonical DATABASES and log the NAME at boot.
×

Always testing with --keepdb

Symptom
Green runserver, red suite; failures vanish on the first clean rebuild.
Fix
Delete the kept test database and rebuild; drop --keepdb from CI entirely.
×

Module-level QuerySets in app code

Symptom
Fresh containers and new teammates crash at import while old laptops pass.
Fix
Move queries into functions, properties, or get_queryset(); gate startup data behind ready().
×

Health checks that only test TCP liveness

Symptom
Load balancer reports green while every request returns a 500.
Fix
Assert table existence in /health/ before the router sends traffic.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
What does OperationalError: no such table mean?
Q02JUNIOR
How do missing migration files happen?
Q03SENIOR
What is --keepdb and when does it cause this error?
Q04SENIOR
Why do module-level queries break fresh environments?
Q05SENIOR
How can two settings files produce a permanently table-less app?
Q01 of 05JUNIOR

What does OperationalError: no such table mean?

ANSWER
The table was never created in the database the process is reading: migrations weren't applied, the migration file is missing, the wrong database is configured, or the query ran before schema existed (tests, import time). The error names the table, so you check showmigrations, the migration files, DATABASES, and the query's timing in that order.
FAQ · 6 QUESTIONS

Frequently Asked Questions

01
How do I apply missing migrations?
02
What if my model has no migration file?
03
How do wrong-database settings cause this?
04
How do --keepdb tests trigger no-such-table?
05
Why do import-time queries fail on fresh databases?
06
What's the fastest triage when this error fires?
N
Naren Founder & Principal Engineer

20+ years shipping production Python across data and backend systems. Drawn from code that ran under real load.

Follow
✓ Verified
production tested
October 31, 2026
last updated
2,099
articles · all by Naren
🔥

That's Python Libraries. Mark it forged?

4 min read · try the examples if you haven't

←
Previous
FastAPI Cloud — Official Deployment Platform
58 / 59 · Python Libraries
Next
Django ImproperlyConfigured Fix
→