| この記事の要点 |
|
どちらを使うべきか
| 方法 | 戻り値 | 向いている用途 |
|---|---|---|
Model.objects.raw() | モデルのインスタンス(RawQuerySet) | 複雑な SELECT だが、結果はモデルとして扱いたい |
connection.cursor() | タプル(行) | 集計結果、モデルに対応しない列、INSERT / UPDATE / DELETE、DB 固有の命令 |
まずは ORM(filter、annotate、Subquery など)で書けないか検討し、書けない・遅い場合に生の SQL を使うのが一般的な方針です。
モデル使用:Manager.raw()
from myapp.models import User
id = 1
users = User.objects.raw('SELECT id, name FROM user WHERE id = %s', [id])
for u in users:
print(u.id, u.name)
raw() はクエリを実行して結果をモデルのインスタンスにマッピングします。1 件だけ取りたい場合は users[0] のようにインデックスで取れますが、0 件だと IndexError になります。
raw() の注意点
- 主キー列を必ず SELECT する: 含めないと「Raw query must include the primary key」というエラーになる
- テーブル名: Django が作るテーブル名は既定で「アプリ名_モデル名の小文字」(例:
myapp_user)。Meta.db_tableを指定していない場合はUser._meta.db_tableで確認して書く - SELECT しなかったフィールドは遅延読み込みになり、アクセスした時点で追加のクエリが発行される
- モデルにない列(
COUNT(*) AS cntなど)も、別名を付ければu.cntのように属性として読める raw()の結果にfilter()などの ORM メソッドは続けて使えない
モデル未使用:connection.cursor()
from django.db import connection
id = 1
with connection.cursor() as cursor:
cursor.execute('SELECT id, name FROM user WHERE id = %s', [id])
row = cursor.fetchone() # (1, 'Taro') または None
print(row)
複数レコードを取得する場合は fetchone ではなく fetchall を使用します。
from django.db import connection
with connection.cursor() as cursor:
cursor.execute('SELECT id, name FROM user WHERE age >= %s', [20])
rows = cursor.fetchall() # [(1, 'Taro'), (2, 'Hanako'), ...]
結果を辞書で受け取る
カーソルの結果はタプルなので、列名でアクセスしたい場合は cursor.description から列名を取り出して辞書に変換します。
def dictfetchall(cursor):
columns = [col[0] for col in cursor.description]
return [dict(zip(columns, row)) for row in cursor.fetchall()]
with connection.cursor() as cursor:
cursor.execute('SELECT id, name FROM user')
users = dictfetchall(cursor) # [{'id': 1, 'name': 'Taro'}, ...]
INSERT / UPDATE / DELETE
from django.db import connection, transaction
with transaction.atomic():
with connection.cursor() as cursor:
cursor.execute('UPDATE user SET name = %s WHERE id = %s', ['Jiro', 1])
print(cursor.rowcount) # 更新された行数
Django は既定で自動コミット(autocommit)なので、execute した時点で確定します。複数の更新をまとめて成功・失敗させたい場合は transaction.atomic() で囲みます。
複数データベースを使っている場合
from django.db import connections
with connections['analytics'].cursor() as cursor:
cursor.execute('SELECT COUNT(*) FROM access_log')
total = cursor.fetchone()[0]
# raw() の場合
User.objects.using('analytics').raw('SELECT id, name FROM user')
パラメータの渡し方(SQL インジェクション対策)
値は必ず第 2 引数のリストで渡し、Django(DB ドライバ)にエスケープを任せます。
name = request.GET.get('name')
# OK: パラメータで渡す
cursor.execute('SELECT id FROM user WHERE name = %s', [name])
# NG: 文字列に埋め込む(SQL インジェクションの危険)
cursor.execute(f"SELECT id FROM user WHERE name = '{name}'")
cursor.execute("SELECT id FROM user WHERE name = '%s'" % name)
# NG: %s をクォートで囲む(値が二重にクォートされる)
cursor.execute("SELECT id FROM user WHERE name = '%s'", [name])
- プレースホルダは SQLite・PostgreSQL・MySQL いずれでも
%s(SQLite の?は使わない) - 名前付きの
%(name)sと辞書による指定は、DB バックエンドやバージョンによって対応が異なるため、リストと%sの組み合わせが最も無難 - パラメータを渡すとき、SQL 内のリテラルの
%は%%と書く必要がある。LIKE 検索は'%' + keyword + '%'をパラメータ側で組み立てるのが簡単 - テーブル名・列名はパラメータにできない。動的に変える場合は許可リストで検証してから組み立てる
keyword = 'ta'
cursor.execute('SELECT id, name FROM user WHERE name LIKE %s', ['%' + keyword + '%'])
確認方法
- 実際に発行された SQL は、
DEBUG = Trueのときconnection.queriesで確認できる python manage.py dbshellで DB に直接接続し、同じ SQL を手で実行して結果を比較するraw()の場合はprint(users.query)でパラメータを埋めた SQL を表示できる
from django.db import connection
print(connection.queries[-1]) # {'sql': 'SELECT ...', 'time': '0.001'}
関連
- SQL 副問い合わせ完全ガイド — スカラー / インライン / 相関 / EXISTS / ANY / ALL / CTE
- Django で SQL ログを出力する方法|django.db.backends と N+1 の発見
- Django Model の定義方法完全ガイド — フィールド型と Meta
- Django のテーブル名を変更する方法|アプリ名 + モデル名
子ページ
子ページはありません
人気ページ
- 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アノテーションとは
最近更新/作成されたページ
- プロジェクトをTomcatプロジェクトとして認識させる方法 2026-10-07 22:32:50
- MySQLの1366 Incorrect string value|Laravelの文字コード・絵文字エラー 2026-10-07 21:54:03
- curlの証明書ホスト名不一致|旧エラー51・現行60の確認と対処 2026-10-07 21:54:03
- LaravelのMassAssignmentException|fillableの原因と安全な対処 2026-10-07 21:54:03
- Eclipse で Tomcat の起動ログがコンソールに出ない時の確認手順 2026-10-07 21:54:02
- MySQLにおける中央値(Median)の導き方(バージョン8未満) 2026-10-07 13:49:45
- getInputForward 2026-10-07 13:41:15
- JSONから配列に変換 2026-10-07 13:41:15
- ビューから値をモデルに格納しコントローラーで受け取る方法 2026-10-07 13:23:41
- Laravelのテーブル作成と定義変更|マイグレーション・up/down・注意点 2026-10-07 13:23:41
- NumPy 配列に要素を追加する方法 (append / concatenate) 2026-10-07 13:23:41
- MariaDB・MySQLで現在日時を取得する方法|NOW・タイムゾーン・保存型 2026-10-07 13:13:36
- 【django】テンプレートで定数を使用する方法 2026-10-07 13:10:15
- Spring BootにおけるApplication.propertiesの環境依存設定の分割方法 2026-10-07 12:09:35
- Not supported for DML operations【Springエラー】 2026-10-07 11:09:38