Выполнение необработанных SQL-запросов
Django предоставляет два способа выполнения необработанных SQL-запросов: можно использовать Manager.raw(), чтобы выполнять необработанные запросы и возвращать экземпляры моделей, либо полностью обойти уровень моделей и выполнять пользовательский SQL напрямую.
Изучите ORM, прежде чем использовать необработанный SQL!
ORM Django предоставляет множество инструментов для составления запросов без написания необработанного SQL. Например:
- API QuerySet обладает широкими возможностями.
- Вы можете использовать
annotateи агрегацию со множеством встроенных функций базы данных. Кроме того, вы можете создавать пользовательские выражения запросов.
Прежде чем использовать необработанный SQL, изучите ORM. Спросите на одном из каналов поддержки, поддерживает ли ORM ваш сценарий использования.
Предупреждение
При написании необработанного SQL следует соблюдать особую осторожность. Каждый раз, когда вы его используете, необходимо надлежащим образом экранировать все параметры, контролируемые пользователем, с помощью params, чтобы защититься от атак с внедрением SQL-кода. Подробнее см. в разделе Защита от внедрения SQL-кода.
Выполнение необработанных запросов
Метод менеджера raw() можно использовать для выполнения необработанных SQL-запросов, возвращающих экземпляры моделей:
-
Manager.raw(raw_query, params=(), translations=None)
Этот метод принимает необработанный SQL-запрос, выполняет его и возвращает экземпляр django.db.models.query.RawQuerySet. Этот экземпляр RawQuerySet можно перебирать, как обычный QuerySet, получая экземпляры объектов.
Лучше всего пояснить это на примере. Предположим, у вас есть следующая модель:
class Person(models.Model):
first_name = models.CharField(...)
last_name = models.CharField(...)
birth_date = models.DateField(...)
Тогда вы можете выполнить пользовательский SQL следующим образом:
>>> for p in Person.objects.raw("SELECT * FROM myapp_person"):
... print(p)
...
John Smith
Jane Jones
Этот пример не слишком интересен — он делает ровно то же, что и Person.objects.all(). Однако у raw() есть множество других параметров, благодаря которым этот метод обладает широкими возможностями.
Имена таблиц моделей
Откуда в этом примере взялось имя таблицы Person?
По умолчанию Django формирует имя таблицы базы данных, соединяя «метку приложения» модели — имя, указанное вами в manage.py startapp, — с именем класса модели, разделяя их символом подчёркивания. В примере мы предполагаем, что модель Person находится в приложении с именем myapp, поэтому её таблица будет называться myapp_person.
Подробнее см. документацию по параметру db_table, который также позволяет вручную задать имя таблицы базы данных.
Предупреждение
SQL-оператор, передаваемый в .raw(), не проверяется. Django предполагает, что оператор вернёт набор строк из базы данных, но никак этого не обеспечивает. Если запрос не возвращает строки, возникнет (возможно, малопонятная) ошибка.
Предупреждение
Если вы выполняете запросы к MySQL, учтите, что неявное преобразование типов в MySQL может привести к неожиданным результатам при смешивании типов. Если вы выполняете запрос по столбцу строкового типа, передав целочисленное значение, MySQL преобразует все значения в таблице к целым числам, прежде чем выполнить сравнение. Например, если в таблице содержатся значения 'abc', 'def', а вы выполняете запрос для WHERE mycolumn=0, совпадут обе строки. Чтобы этого избежать, перед использованием значения в запросе выполните корректное приведение типа.
Сопоставление полей запроса с полями модели
raw() автоматически сопоставляет поля запроса с полями модели.
Порядок полей в запросе не имеет значения. Иными словами, оба следующих запроса работают одинаково:
>>> Person.objects.raw("SELECT id, first_name, last_name, birth_date FROM myapp_person")
>>> Person.objects.raw("SELECT last_name, birth_date, first_name, id FROM myapp_person")
Сопоставление выполняется по имени. Это означает, что вы можете использовать предложения AS в SQL для сопоставления полей запроса с полями модели. Поэтому, если у вас есть другая таблица с данными Person, их можно легко сопоставить с экземплярами Person:
>>> Person.objects.raw( ... """ ... SELECT first AS first_name, ... last AS last_name, ... bd AS birth_date, ... pk AS id, ... FROM some_other_table ... """ ... )
Пока имена совпадают, экземпляры модели будут созданы правильно.
В качестве альтернативы можно сопоставить поля запроса с полями модели с помощью аргумента translations метода raw(). Это словарь, сопоставляющий имена полей в запросе с именами полей модели. Например, приведённый выше запрос можно также записать так:
>>> name_map = {"first": "first_name", "last": "last_name", "bd": "birth_date", "pk": "id"}
>>> Person.objects.raw("SELECT * FROM some_other_table", translations=name_map)
Обращение по индексу
raw() поддерживает обращение по индексу, поэтому, если вам нужен только первый результат, можно написать:
>>> first_person = Person.objects.raw("SELECT * FROM myapp_person")[0]
Однако индексация и срезы не выполняются на уровне базы данных. Если в вашей базе данных хранится большое количество объектов Person, эффективнее ограничить запрос на уровне SQL:
>>> first_person = Person.objects.raw("SELECT * FROM myapp_person LIMIT 1")[0]
Отложенная загрузка полей модели
Поля также можно не указывать:
>>> people = Person.objects.raw("SELECT id, first_name FROM myapp_person")
Объекты Person, возвращённые этим запросом, будут экземплярами моделей с отложенной загрузкой полей (см. defer()). Это означает, что поля, не включённые в запрос, будут загружаться по требованию. Например:
>>> for p in Person.objects.raw("SELECT id, first_name FROM myapp_person"):
... print(
... p.first_name, # This will be retrieved by the original query
... p.last_name, # This will be retrieved on demand
... )
...
John Smith
Jane Jones
На первый взгляд кажется, что запрос получил и имя, и фамилию. Однако в действительности этот пример выполняет 3 запроса. Запрос raw() получает только имена, а фамилии загружаются по требованию при выводе на экран.
Есть только одно поле, которое нельзя не указывать, — поле первичного ключа. Django использует первичный ключ для идентификации экземпляров модели, поэтому он всегда должен присутствовать в необработанном запросе. Если вы забудете включить первичный ключ, будет вызвано исключение FieldDoesNotExist.
Добавление аннотаций
Вы также можете выполнять запросы, содержащие поля, не определённые в модели. Например, можно использовать функцию age() в PostgreSQL, чтобы получить список людей с возрастом, вычисленным базой данных:
>>> people = Person.objects.raw("SELECT *, age(birth_date) AS age FROM myapp_person")
>>> for p in people:
... print("%s is %s." % (p.first_name, p.age))
...
John is 37.
Jane is 42.
...
Часто вычислять аннотации с помощью необработанного SQL не нужно: вместо этого можно использовать выражение Func().
Передача параметров в raw()
Если вам нужно выполнять параметризованные запросы, можно использовать аргумент params метода raw():
>>> lname = "Doe"
>>> Person.objects.raw("SELECT * FROM myapp_person WHERE last_name = %s", [lname])
params — это список или словарь параметров. Для списка в строке запроса используются заполнители %s, а для словаря — заполнители %(key)s (где key заменяется ключом словаря), независимо от используемого движка базы данных. Эти заполнители будут заменены параметрами из аргумента params.
Примечание
Параметры в виде словаря не поддерживаются бэкендом SQLite; для этого бэкенда необходимо передавать параметры в виде списка.
Предупреждение
Не используйте форматирование строк для необработанных запросов и не заключайте заполнители в SQL-строках в кавычки!
Может показаться заманчивым записать приведённый выше запрос так:
>>> query = "SELECT * FROM myapp_person WHERE last_name = %s" % lname >>> Person.objects.raw(query)
Вы также можете подумать, что запрос следует записать так (взяв %s в кавычки):
>>> query = "SELECT * FROM myapp_person WHERE last_name = '%s'"
Не допускайте ни одной из этих ошибок.
Как объясняется в разделе Защита от внедрения SQL-кода, использование аргумента params и незаключённые в кавычки заполнители защищают вас от атак с внедрением SQL-кода — распространённого способа эксплуатации уязвимости, при котором злоумышленники внедряют произвольный SQL-код в вашу базу данных. Если вы используете интерполяцию строк или заключаете заполнитель в кавычки, вы рискуете подвергнуться атаке с внедрением SQL-кода.
Непосредственное выполнение пользовательского SQL
Иногда даже Manager.raw() недостаточно: возможно, вам потребуется выполнять запросы, которые нельзя однозначно сопоставить с моделями, или напрямую выполнять запросы UPDATE, INSERT или DELETE.
В таких случаях вы всегда можете напрямую обратиться к базе данных, полностью обойдя уровень моделей.
Объект django.db.connection представляет подключение к базе данных по умолчанию. Чтобы использовать подключение к базе данных, вызовите connection.cursor() для получения объекта курсора. Затем вызовите cursor.execute(sql, [params]) для выполнения SQL и cursor.fetchone() или cursor.fetchall() для получения результирующих строк.
Например:
from django.db import connection
def my_custom_sql(self):
with connection.cursor() as cursor:
cursor.execute("UPDATE bar SET foo = 1 WHERE baz = %s", [self.baz])
cursor.execute("SELECT foo FROM bar WHERE baz = %s", [self.baz])
row = cursor.fetchone()
return row
Для защиты от внедрения SQL-кода не заключайте заполнители %s в SQL-строке в кавычки.
Обратите внимание: если вы хотите включить в запрос символы процента в буквальном виде и передаёте параметры, необходимо удвоить эти символы:
cursor.execute("SELECT foo FROM bar WHERE baz = '30%'")
cursor.execute("SELECT foo FROM bar WHERE baz = '30%%' AND id = %s", [self.id])
Если вы используете несколько баз данных, можно воспользоваться django.db.connections, чтобы получить подключение (и курсор) к определённой базе данных. django.db.connections — это объект, похожий на словарь, который позволяет получить конкретное подключение по его псевдониму:
from django.db import connections
with connections["my_db_alias"].cursor() as cursor:
# Your code here
...
По умолчанию Python DB API возвращает результаты без имён полей, поэтому вы получаете list значений вместо dict. С небольшими затратами производительности и памяти можно возвращать результаты в виде dict, например, так:
def dictfetchall(cursor):
"""
Return all rows from a cursor as a dict.
Assume the column names are unique.
"""
columns = [col[0] for col in cursor.description]
return [dict(zip(columns, row)) for row in cursor.fetchall()]
Ещё один вариант — использовать collections.namedtuple() из стандартной библиотеки Python. namedtuple — это объект, похожий на кортеж, поля которого доступны по атрибутам; он также поддерживает индексацию и перебор. Результаты неизменяемы и доступны по именам полей или индексам, что может быть полезно:
from collections import namedtuple
def namedtuplefetchall(cursor):
"""
Return all rows from a cursor as a namedtuple.
Assume the column names are unique.
"""
desc = cursor.description
nt_result = namedtuple("Result", [col[0] for col in desc])
return [nt_result(*row) for row in cursor.fetchall()]
В примерах с dictfetchall() и namedtuplefetchall() предполагаются уникальные имена столбцов, поскольку курсор не может различать столбцы из разных таблиц.
Вот пример, показывающий различия между этими тремя вариантами:
>>> cursor.execute("SELECT id, parent_id FROM test LIMIT 2")
>>> cursor.fetchall()
((54360982, None), (54360880, None))
>>> cursor.execute("SELECT id, parent_id FROM test LIMIT 2")
>>> dictfetchall(cursor)
[{'parent_id': None, 'id': 54360982}, {'parent_id': None, 'id': 54360880}]
>>> cursor.execute("SELECT id, parent_id FROM test LIMIT 2")
>>> results = namedtuplefetchall(cursor)
>>> results
[Result(id=54360982, parent_id=None), Result(id=54360880, parent_id=None)]
>>> results[0].id
54360982
>>> results[0][0]
54360982
Подключения и курсоры
connection и cursor в основном реализуют стандартный Python DB-API, описанный в PEP 249, — за исключением обработки транзакций.
Если вы не знакомы с Python DB-API, обратите внимание, что в SQL-операторе cursor.execute() используются заполнители "%s", а параметры не вставляются непосредственно в SQL. При использовании этого способа нижележащая библиотека базы данных автоматически экранирует параметры по мере необходимости.
Также обратите внимание, что Django ожидает заполнитель "%s", а не заполнитель "?", используемый привязками Python для SQLite. Это сделано для согласованности и удобства.
Использование курсора в качестве менеджера контекста:
with connection.cursor() as c:
c.execute(...)
эквивалентно следующему:
c = connection.cursor()
try:
c.execute(...)
finally:
c.close()
Вызов хранимых процедур
-
CursorWrapper.callproc(procname, params=None, kparams=None) -
Вызывает хранимую процедуру базы данных с указанным именем. Можно передать последовательность (
params) или словарь (kparams) входных параметров. Большинство баз данных не поддерживаютkparams. Среди встроенных бэкендов Django его поддерживает только Oracle.Например, если в базе данных Oracle есть следующая хранимая процедура:
CREATE PROCEDURE "TEST_PROCEDURE"(v_i INTEGER, v_text NVARCHAR2(10)) AS p_i INTEGER; p_text NVARCHAR2(10); BEGIN p_i := v_i; p_text := v_text; ... END;вызвать её можно так:
with connection.cursor() as cursor: cursor.callproc("test_procedure", [1, "test"])
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/6.0/topics/db/sql/