Djangoで「Unknown column ...id」が出る場合、まずモデルが選択する列と、接続している実テーブルを照合します。Djangoの主キー対応と、MySQLの別名/スコープの誤りは異なる問題です。
Djangoの暗黙idと実テーブルの不一致
主キーを明示しないDjangoモデルには、通常idという自動主キーが補われます。managed=FalseはDjangoによるテーブル作成/削除等の管理を制御するもので、idをモデルから消す設定ではありません。既存の表にperson_noしかないのに、モデルがidもSELECTするとエラーになります。
元の記事にあった「forで回すと主キー不足のエラー」という観測は、QuerySetの遅延評価でその時にSQLが実行された可能性と分けて理解します。forだけが原因ではなく、list()等でも発生します。valuesでidを選ばない読取が通っても、モデルの対応が直ったとは限りません。
| 確認するもの | 確認点 |
|---|---|
| 実DB | 接続alias/DB/表/列と制約。開発機や別DBだけの確認で済ませない。 |
| モデル | _meta.pkのname/column、db_table、db_column、managedと実際の設定。 |
| 一意キー | 存在・非NULL・一意性と更新の意味。都合のよい列へprimary_key=Trueを足して済ませない。 |
| SQL | 実行時の列と別名、query block。ログへURL/SQL/params/例外を丸ごと公開しない。 |
実在する一意キーへ対応付ける教材
次はperson_noが本当に一意な主キーである合成表だけの例です。Python側をperson_key、DB列をperson_noと明示します。既存のモデル/DBをこの定義で置き換える指示ではありません。複合キーやキーのない表は別の設計が必要で、重複列に主キーを宣言してもDBの一意性は生まれません。
from django.db import models
class LegacyPerson(models.Model):
person_key = models.BigIntegerField(primary_key=True, db_column="person_no")
display_name = models.CharField(max_length=50)
class Meta:
app_label = "legacy_pk_lab"
db_table = "legacy_person"
managed = False
mapping.pyは既知のモデルのメタ情報だけを辞書で返し、DBへ問い合わせたり環境変数を出したりしません。mapping_report(LegacyPerson)のprimary_fieldはperson_key、primary_columnはperson_noです。本番の管理情報を公開する診断URLとして使いません。
def mapping_report(model):
primary = model._meta.pk
return {
"model": model._meta.label,
"table": model._meta.db_table,
"managed": model._meta.managed,
"primary_field": primary.name,
"primary_column": primary.column,
}
読取だけで確認するSQL
read_queries.sqlは教材の既存表に対する読取だけです。実環境では許可された対象と読取権限で実施し、未知のDB名/識別子をユーザー入力から連結しません。制約とデータの意味まで確認してから対応を決めます。
SELECT DATABASE() AS current_database, VERSION() AS server_version;
SHOW FULL COLUMNS FROM legacy_person;
SHOW INDEX FROM legacy_person;
SELECT person_no, display_name FROM legacy_person ORDER BY person_no;
inspectdbは既存表からモデル候補を出力する補助であり、そのまま正しい設計が完成する保証ではありません。既存models.pyへ出力を上書きせず、別の確認用出力として照合します。raw()でモデルを得る場合も、対応する主キーがSELECT結果に必要です。managed=Falseを読取専用の認可や書込み禁止機能とも扱いません。
MySQL8.4.13とSQLiteの独立検体では、暗黙idは読取評価時に失敗し、正しい対応の2行は(11, Ada)/(22, Ben)で一致しました。非一意なgroup_noを主キーと偽ってもget(pk=7)は複数件になりました。実テーブルにid列を追加したり、migrationを走らせたりせずに比較しています。試験の表の作成・投入は新しい検体DB内だけです。
| この記事の要点 |
|
エラー内容
旧例の注意:次はMySQLのエラー表示例です。1054はエラー番号、42S22はSQLSTATE、field list等は参照箇所です。DjangoのPython例外型やSQLiteの文言と同じ分類ではありません。番号だけで主キー不足と決めつけず、実際の接続先と列を照合します。
ERROR 1054 (42S22): Unknown column 'users.id' in 'field list'
# 'field list' = SELECT 句で起きた
# 'where clause' = WHERE 句で起きた
# 'on clause' = JOIN ON 句で起きた
# 'order clause' = ORDER BY で起きた
MySQL が「指定されたカラムやテーブル.カラム表記が見当たらない」と言っているエラーです。末尾のfield list/where clause等を読みます。実テーブルにidが無い理由がモデルの対応違いなのか、SQLの別名なのかを分けます。
原因 1: カラム名 / テーブル名のタイポ
旧例の注意:次はordersにuser_idがあるという前提の旧SQLです。FROMで存在しないテーブルを指定した場合は1054ではなく1146等になるため、テーブル名の誤りを同じ番号と断定しません。SHOW/DESCも読取権限が必要で、rootへの変更を推奨しません。
-- ❌ 実際のカラムは user_id だが userid と書いた
SELECT userid FROM orders;
-- ERROR 1054: Unknown column 'userid' in 'field list'
-- 確認
SHOW COLUMNS FROM orders;
DESC orders;
-- ✅ 修正
SELECT user_id FROM orders;
原因 2: AS でエイリアスを付けた後に元テーブル名で参照
同じquery blockで表へ別名を付けた場合、その表の列参照も対応する別名へ合わせます。原因の頻度やすべてのSQL階層での有効範囲を断定するものではありません。
旧例の注意:次は同じquery blockでusersへuという別名を付けた例です。SELECT/ONの両方に不備があるため、末尾の句名は旧コメントでなく実際のエラーで確認します。別名の不一致を直すもので、Djangoの暗黙id/実列不一致を直す例ではありません。複数表に同じ列がある場合は曖昧な参照も別に確認します。
-- ❌ エイリアス u を付けたのに users.id を参照
SELECT users.id, users.name
FROM users AS u
JOIN orders AS o ON o.user_id = users.id;
-- ERROR 1054: Unknown column 'users.id' in 'field list'
-- ✅ エイリアスで統一
SELECT u.id, u.name
FROM users AS u
JOIN orders AS o ON o.user_id = u.id;
原因 3: サブクエリのスコープミス
相関サブクエリは外側を参照できます。どのquery blockにどの表/別名があるかを確認します。次の例の問題は、外側をpと名付けたのに内側でproductsと呼んでいることです。
旧例の注意:次の旧コメントは原因を言い切れていません。相関サブクエリは外側を参照できます。この例ではproductsをpと呼んでいるのにproducts.category_idと書くことが問題です。p.category_idに合わせれば参照できます。JOINへの書換えは一致する内側の行数により重複を増やすため、結果を比較せず等価とみなしません。
-- ❌ サブクエリ内に products.category_id は無い
SELECT p.name
FROM products p
WHERE p.id IN (
SELECT id FROM products_top
WHERE products.category_id = 5 -- ★ 外側の products を参照
);
-- ERROR 1054: Unknown column 'products.category_id' in 'where clause'
-- ✅ 内側にも参照を入れるか、JOIN に書き換える
SELECT p.name
FROM products p
JOIN products_top pt ON pt.id = p.id
WHERE p.category_id = 5;
結果を変えない相関の例
correlated.sqlは外側をp、内側をptとしてEXISTSで一致の有無を調べます。教材のproducts_topには同じidを2行入れ、IN/EXISTSは1行、単純JOINは2行になることを両DBで確認しました。別名を直すことと、重複を含む結果の等価性は別です。
SELECT p.id, p.name
FROM products AS p
WHERE p.category_id = 5
AND EXISTS (
SELECT 1 FROM products_top AS pt WHERE pt.id = p.id
)
ORDER BY p.id;
原因 4: JOIN の ON 句でテーブル名が違う
旧例の注意:次はONに現れるcustomersという名前が、このFROM/JOINに定義されていない例です。列名だけを修正せず、対象表/別名と結合条件を照合します。
-- ❌ JOIN したテーブル名と ON 句が食い違う
SELECT *
FROM users u
JOIN orders o ON o.user_id = customers.id;
-- ERROR 1054: Unknown column 'customers.id' in 'on clause'
-- ✅ 修正
SELECT *
FROM users u
JOIN orders o ON o.user_id = u.id;
原因 5: GROUP BY / ORDER BY での参照ミス
旧例の注意:次はcntとcountの食い違いの例です。最後のORDER BY cntだけでは独立したSQL文になりません。元のSELECTへ組み込み、同点順や集計の意味も別に確認します。
-- ❌ SELECT で別名を付けたカラムを別名で ORDER BY しているが、
-- その別名と異なる名前を使った
SELECT
u.name,
COUNT(*) AS cnt
FROM users u
JOIN orders o ON o.user_id = u.id
GROUP BY u.id
ORDER BY count; -- ★ 別名は cnt
-- ERROR 1054: Unknown column 'count' in 'order clause'
-- ✅
ORDER BY cnt;
切り分けの手順
- エラー末尾の
in 'xxx clause'でどの句かを特定 SHOW COLUMNS FROM テーブル名で実カラム確認- SQL の
FROM/JOINでつけたエイリアスを洗い出す - エイリアスを使うなら全箇所エイリアスに統一
- 相関の有無、query blockごとの別名、期待する行数と重複を確認する
他製品の旧比較例:Laravel Query Builder
旧例の注意:次はDjangoではなくLaravelのQuery Builderの旧比較例です。DB::tableはEloquentモデルの説明と同一ではありません。toSql()は実行履歴ではなく、dd()を本番へ足して内部情報を公開しません。この作業でPHP/Laravelの実行は検証していません。
<?php
// ❌ 別名 u を付けたのに users.id で参照
$users = DB::table('users as u')
->join('orders as o', 'o.user_id', '=', 'u.id')
->select('users.id', 'users.name') // ← ここ
->get();
// SQLSTATE[42S22]: Column not found: 1054 Unknown column 'users.id'
// ✅ エイリアスで統一
$users = DB::table('users as u')
->join('orders as o', 'o.user_id', '=', 'u.id')
->select('u.id', 'u.name')
->get();
// デバッグ: 実際に発行される SQL を確認
$sql = DB::table('users as u')
->join('orders as o', 'o.user_id', '=', 'u.id')
->select('u.id')
->toSql();
dd($sql);
他製品の旧比較例:Eloquentモデル
旧例の注意:次は別製品Eloquentの旧比較例です。Djangoのモデル設定へこのコードを移しません。集計別名を直すことと、Djangoの主キーを実テーブルへ対応付けることは別です。
<?php
// ❌ withCount のリレーション名と JOIN 結果の参照が食い違い
$users = User::withCount('orders')
->orderBy('order_count') // ★ 実際は orders_count
->get();
// Unknown column 'order_count'
// ✅
$users = User::withCount('orders')
->orderBy('orders_count')
->get();
マイグレーション直後に出る場合
環境差がある場合も、未実行の変更が原因とは限りません。接続DB/alias、表名/列名、modelと配布版、履歴上の変更を読取で照合します。変更が必要と分かっても、承認済みの手順・退避・互換性・失敗時の対応を別途検討します。次の旧コードを診断として実行しません。
旧例の注意:実行しないでください。次の旧手順にあるphp artisan migrate --forceはスキーマを変更し得る処理で、このDjangoエラーの診断方法ではありません。root接続や自動マイグレーションで解決を試みず、最初は読取権限で接続先・実列・モデル・配布版を照合します。
# Laravel
php artisan migrate:status
php artisan migrate --force
# 該当テーブルのカラム状態を直接確認
mysql -u root -p myapp -e "SHOW COLUMNS FROM users"
FAQ
Q: 大文字小文字が違うだけでも出る?
A: テーブル名の大小扱いはlower_case_table_namesとOS/ファイルシステム等の条件を確認します。Linuxという名称だけで実設定を決めつけず、既存DBの設定を診断目的で変更しません。列名の規則と表名の規則も区別します。
Q: 予約語を使っていると出る?
A: 構文の1064、列参照の1054等を分けます。MySQLのバッククォートは識別子を囲むもので、存在しない列を作りません。シングルクォートの文字列リテラルとも別です。ダブルクォートの扱いもsql_modeに依存するため一律の置換をしません。
Q: 通常のビューで起きる場合は?
A: ビュー上の存在しない列の参照と、定義先の表/列が変わってビュー自体が無効な場合を分けます。後者は1356等になることもあります。SHOW CREATE VIEWで定義と参照先を確認し、1054だけに限定したりDDLで作り直したりしません。
検証範囲と公式資料(2026年10月26日)
Python3.12.10/Django5.2.18/mysqlclient2.2.7で、SQLiteメモリDBとPC上の専用MySQL8.4.13を使い、同じORMモデルとSQLを比較しました。MySQLは一意名/labelの新規container、データはtmpfs、接続はPCの127.0.0.1の自動割当ポートだけで、既存DB/volume/資格情報は使いません。暗黙id・明示pk/db_column・managed=False・遅延評価/投影/raw/inspectdb、実際の1054/1146/1356、表別名・相関・JOIN重複・集計別名を検査しました。
失敗SQLが診断ログへ必ず完全に入るとも限りません。検体ではCaptureQueriesContextのSQL欄がNoneの文字列表現になる失敗経路もあり、コンパイル結果と実行時の番号を別に照合しました。SQL/paramsやエラー文には秘密値・スキーマ情報が含まれ得るため、公開ページやddへ出しません。
実アプリの全モデル/関連・複合キー・権限・全DB版/sql_mode、実サーバーのmigration・性能・Laravel/Doctrineの動作は未検証です。危険な旧例の保持は実行の推奨ではありません。SQL計測はDjangoのSQLログと計測、ページングはDjangoのLIMIT/OFFSETを参照してください。
公式資料:Djangoの自動主キー、managed/db_table、db_column、既存DBとinspectdb、rawの主キー、MySQLのエラー番号/SQLSTATE、相関サブクエリ、識別子と別名。
子ページはありません
- Invalid HTTP_HOST header: '...'. You may need to add '...' to ALLOWED_HOSTS
- CommandError: You must set settings.ALLOWED_HOSTS if DEBUG is False.
- django.utils.datastructures.MultiValueDictKeyError
- Forbidden (403) CSRF verification failed. Request aborted.
- ModuleNotFoundError: No module named 'MySQLdb'
- WARNINGS: ?: (mysql.W002) MySQL Strict Mode is not set for database connection
- Unknown column 'table_name.id' in 'field list'
- RuntimeError: Model class ~ doesn't declare an explicit app_label and isn't in an application in INSTALLED_APPS.
- get() returned more than one MynumberRegist -- it returned 2!
- django.db.utils.OperationalError: (2006, "Can't connect to MySQL server")
- 'include' is not defined
人気ページ
- 1 Eclipseで「サーバーに追加または除去できるリソースがありません。」の原因と対処法
- 2 tomcat の起動 / 停止ログと catalina.log・catalina.out の違い
- 3 JavaScript で base URL を取得する方法|window.location.origin
- 4 YouTube Data API v3 エラー一覧|403・400・404 の原因と対処
- 5 Laravel エラー一覧|500/Blade/DB 接続/ルーティングの代表エラー
- 6 3Dグラフィックスとは|モデリング/レンダリング/主要ソフトウェア (Blender / Maya)
- 7 Spring Frameworkのアノテーション一覧
- 8 【Spring】@Valueアノテーションとは
- 9 CATALINA_HOME の確認方法 (Linux / Mac)
- 10 【Spring】@Autowiredアノテーションとは
最近更新/作成されたページ
- Django REST FrameworkでAPIを作る|URL接続・入力検証・所有者の認可 2026-10-25 21:03:21
- DjangoモデルをJSONで返す|型・公開フィールド・DRF認可 2026-10-25 20:46:35
- precompile standard library とは(Pythonインストール) 2026-10-25 20:30:17
- ANACONDA NAVIGATORの起動方法 2026-10-25 20:30:17
- DjangoのAjax POST|CSRF・JSON入力検証・DRFの認可 2026-10-25 20:30:16
- Django/MySQLのUnknown column ...id|主キー・実列・別名の確認 2026-10-25 20:08:53
- 【django】limit, offsetの指定方法 2026-10-25 20:08:51
- Djangoの本番で変更が反映されないとき|プロセス・テンプレート・静的配信の確認 2026-10-25 19:40:35
- Django のファイルアップロード|ファイル名と拡張子の取得 2026-10-25 19:40:35
- djangoにおけるHTTPステータスコードの返し方 2026-10-25 19:40:34
- PythonのResponseオブジェクトの中身の確認 2026-10-25 19:17:54
- Python で Cookie を扱う|requests の Session と http.cookies 2026-10-25 19:17:54
- Python で HTTP リクエスト|requests・urllib・httpx と curl の対応 2026-10-25 19:17:53
- Django で SQL ログを出力する方法|django.db.backends と N+1 の発見 2026-10-25 18:59:32
- djangoにおけるログのローテート設定【Python】 2026-10-25 18:59:31