Djangoではデータベースの操作はO/Rマッパーを利用するのが一般的です。できるだけO/Rマッパーを利用した方がいいのですが、条件の複雑なクエリや集計クエリなどを実行したい場合にどうしても直接SQLを記述して実行させたいこともあります。Vasyworksでも詳細な条件での物件検索など、一部でSQL文を直接記述して実装しています。下記にサンプルコードを記載します。
■サンプルモデル(models.py)
from django.db import models
class SampleModel(models.Model):
"""
サンプルモデル
"""
id = models.AutoField('ID', db_column='id', primary_key=True)
name = models.CharField('Name', db_column='name', max_length=100, db_index=True, null=True, blank=True)
product_code = models.CharField('Product code', db_column='product_code', max_length=5, db_index=True, null=True, blank=True)
price = models.IntegerField('Price', db_column='price', default=0)
@classmethod
def get_raw_query_set(cls):
"""
RawQuerySetの取得
"""
param = {}
sql = 'SELECT id, name, product_code, price'
sql += ' FROM sample_table'
sql += ' WHERE product_code = %(product)s'
sql += ' AND price >= %(money)s'
param['product'] = 'A0001'
param['money'] = 1000
return cls.objects.raw(raw_query=sql, params=params)
上記ではモデルのSampleModelクラスのget_raw_query_setメソッド内でSQL文を記述して、モデルマネージャ(objects)のrawメソッドを使ってクエリーセットを取得しています。rawメソッドはRawQuerySetというクラスのインスタンスを返します。RawQuerySetクラスはモデルマネージャのallメソッドやfilterメソッドなどが返すQuerySetクラスと似ていますが、RawQuerySetにはcountメソッドが無くlen関数でレコード数を取らないといけないなど、若干扱いが異なるところがあるようです。ちなみにDjango RESTFrameworkではPagnationの内部でcountを取る際に、countメソッドでエラーが出たらlen関数で取得するようにして、QuerySetでもRawQuerySetでも扱えるようにしているようです。
モデルクラス内でFieldを記述する際のdb_nameをSELECT文のカラム名と合わせておくと通常のモデルと同様のデータ参照が可能です。SELECT文のカラム名にAS句でエイリアス名を付けて、エイリアス名でFieldのdb_name名を指定することも可能ですので、SQLの集計関数の結果の取得などにも利用できます。
検索条件など、外部から指定可能なパラメータをSQL文中で取り扱う際には注意が必要です。SQL文中に記述する際にformatメソッドなどで直接パラメータを書き加えてしまうとSQLインジェクション発生の原因となってしまいます。 外部から指定可能なパラメータはrawメソッドの引数のparamsを使って渡すことでSQLインジェクションを防ぐことができます。
検索条件などのパラメータ はSQL文中では「 %(パラメータ名)s」として記述し、与えられたパラメータはSQL文中で指定したパラメータ名付きの連想配列にしてrawメソッドのparamsに与えてください。パラメータ名無しの「%s」で指定してパラメータ名を省くこともできますが、パラメータ名を使った方が多数のパラメータを指定する際に分かりやすくなります。
VasyworksではVasyworksAPIの物件検索用(search)APIやVasyworksSEARCHの地図検索用のAPIで直接SQLを記述する実装をしています 。検索条件の指定が細かく、O/Rマッパーでは処理速度が落ちてしまうので、そのようにしています。ご興味がある方はGitHubで公開しているソースコードをご覧ください。