DjangoのORMでは表現しきれない複雑なクエリや、パフォーマンス重視の処理には、SQLを直接実行する方法があります。raw()メソッドやconnection.cursor()を使ったSQL直接実行の方法を解説します。
基本的な使い方
views.py
persons = Person.objects.raw('SELECT * FROM myapp_person WHERE age > %s', [25])
for person in persons:
print(person.name)
説明
Step 1Raw SQLの基本
DjangoのORMは多くの場合十分強力ですが、特殊なデータベース機能を使用したい場合や、非常に複雑なクエリが必要な場合は、直接SQLを実行することができます。Djangoでは、raw()メソッドを使用して生のSQL文を実行できます。
# 基本的な使い方 モデル名.objects.raw(SQL文, パラメータ)
raw()メソッドは、モデルインスタンスのイテレータを返します。
Step 2基本的な使用例
例えば、特定のIDを持つPersonモデルのレコードを取得する場合:
# Personテーブルから特定のIDのデータを取得
person = Person.objects.raw('SELECT * FROM myapp_person WHERE id = %s', [person_id])
# 結果は反復処理可能
for p in person:
print(p.name)
# 通常は1件だけの場合、最初の要素を取得
first_person = list(person)[0] # or person[0]
注意: raw()メソッドの結果は、リストではなくイテレータです。すべての結果が必要な場合はlist()で変換します。
Step 3SQLインジェクション対策
Raw SQLを使用する際は、SQLインジェクション攻撃に対する対策が重要です。パラメータは文字列内に直接埋め込まず、パラメータ化クエリを使用します:
# 安全な方法(推奨)
Person.objects.raw('SELECT * FROM myapp_person WHERE last_name = %s', [last_name])
# 危険な方法(避けるべき)
Person.objects.raw('SELECT * FROM myapp_person WHERE last_name = "' + last_name + '"') # 危険!
パラメータはデータベースバックエンドによって適切にエスケープされます。
Step 4モデルフィールドのマッピング
raw()メソッドは、SQLの結果をモデルインスタンスにマッピングします。このとき、少なくともモデルの主キーを選択する必要があります:
# 必要なフィールドだけを選択(主キーは必須)
persons = Person.objects.raw('SELECT id, name, age FROM myapp_person')
# 別名を使用する場合
persons = Person.objects.raw('''
SELECT
id,
first_name || ' ' || last_name AS full_name,
EXTRACT(YEAR FROM CURRENT_DATE) - EXTRACT(YEAR FROM birth_date) AS calculated_age
FROM myapp_person
''')
# 結果へのアクセス
for person in persons:
print(f"{person.full_name} is {person.calculated_age} years old")
SQLで計算したフィールドや別名を付けたフィールドも、モデルインスタンスのプロパティとしてアクセスできます。
Step 5複雑なクエリの例
Raw SQLは、ORMでは表現しにくい複雑なクエリに特に有用です:
# 複雑なJOINとサブクエリ
products = Product.objects.raw('''
SELECT
p.id,
p.name,
p.price,
(SELECT AVG(price) FROM myapp_product) AS avg_price,
p.price - (SELECT AVG(price) FROM myapp_product) AS price_diff,
COUNT(s.id) AS sales_count
FROM
myapp_product p
LEFT JOIN
myapp_sale s ON p.id = s.product_id
GROUP BY
p.id, p.name, p.price
HAVING
COUNT(s.id) > 5
ORDER BY
sales_count DESC
''')
# データベース固有の関数を使用
from django.db import connection
if connection.vendor == 'postgresql':
# PostgreSQL固有の関数を使用
persons = Person.objects.raw('''
SELECT
id,
name,
age,
EXTRACT(EPOCH FROM last_login) AS login_epoch
FROM myapp_person
WHERE
last_login > NOW() - INTERVAL '30 days'
''')
elif connection.vendor == 'mysql':
# MySQL固有の構文
persons = Person.objects.raw('''
SELECT
id,
name,
age,
UNIX_TIMESTAMP(last_login) AS login_epoch
FROM myapp_person
WHERE
last_login > DATE_SUB(NOW(), INTERVAL 30 DAY)
''')
Step 6実践的な使用例
views.pyでのRaw SQLの使用例:
from django.shortcuts import render
from django.db import connection
from .models import Product, Sale
def sales_report(request):
# 標準のORMクエリでは複雑すぎる分析クエリ
products = Product.objects.raw('''
WITH monthly_sales AS (
SELECT
product_id,
DATE_TRUNC('month', date) AS month,
SUM(amount) AS monthly_total
FROM
myapp_sale
GROUP BY
product_id, DATE_TRUNC('month', date)
)
SELECT
p.id,
p.name,
p.price,
SUM(s.amount) AS total_sales,
COUNT(DISTINCT s.customer_id) AS unique_customers,
MAX(ms.monthly_total) AS best_month_sales
FROM
myapp_product p
LEFT JOIN
myapp_sale s ON p.id = s.product_id
LEFT JOIN
monthly_sales ms ON p.id = ms.product_id
GROUP BY
p.id, p.name, p.price
ORDER BY
total_sales DESC
''')
return render(request, 'products/sales_report.html', {
'products': products
})
def database_info(request):
# データベースのメタデータ取得
with connection.cursor() as cursor:
if connection.vendor == 'postgresql':
cursor.execute('''
SELECT
table_name,
pg_size_pretty(pg_total_relation_size(quote_ident(table_name))) AS total_size
FROM
information_schema.tables
WHERE
table_schema = 'public'
ORDER BY
pg_total_relation_size(quote_ident(table_name)) DESC
''')
tables = cursor.fetchall()
elif connection.vendor == 'mysql':
cursor.execute('''
SELECT
table_name,
ROUND(((data_length + index_length) / 1024 / 1024), 2) AS size_in_mb
FROM
information_schema.tables
WHERE
table_schema = DATABASE()
ORDER BY
(data_length + index_length) DESC
''')
tables = cursor.fetchall()
return render(request, 'admin/database_info.html', {
'tables': tables
})
テンプレートでの使用例(sales_report.html):
<h1>商品売上レポート</h1>
<table>
<tr>
<th>商品名</th>
<th>価格</th>
<th>総売上</th>
<th>顧客数</th>
<th>最高月間売上</th>
</tr>
{% for product in products %}
<tr>
<td>{{ product.name }}</td>
<td>{{ product.price|floatformat:0 }}円</td>
<td>{{ product.total_sales|floatformat:0 }}円</td>
<td>{{ product.unique_customers }}人</td>
<td>{{ product.best_month_sales|floatformat:0 }}円</td>
</tr>
{% endfor %}
</table>
重要ポイント:
raw()メソッドを使用する場合は、少なくともモデルの主キー(通常はid)をSELECTに含める必要があります。- SQLインジェクションを防ぐため、常にパラメータ化クエリを使用してください。
- データベース固有の機能を使用する場合は、
connection.vendorでデータベースタイプを確認し、適切なSQL構文を使用してください。 - 複雑なデータベース操作が必要な場合は、
connection.cursor()を使用して直接カーソルを取得することもできます。 - Raw SQLはDjangoのORMと比較して可読性やメンテナンス性が低下する可能性があるため、必要な場合にのみ使用するのが良いでしょう。
- データベースの移行時にRaw SQLは変更が必要になる可能性があることに注意してください。
まとめ
Model.objects.raw()でSQLを直接実行し、モデルインスタンスとして取得できるconnection.cursor()で任意のSQLを実行できる- SQLインジェクション対策として、必ずパラメータ化クエリを使用する
- ORMで表現できない複雑なクエリやパフォーマンスが必要な場面で使用する
- 保守性の観点から、可能な限りORMを使い、Raw SQLは最後の手段とする
- データベース固有のSQL構文を使う場合はポータビリティに注意する