Выполнение необработанных запросов SQL
Django предоставляет два способа выполнения необработанных запросов SQL: можно использовать Manager.raw(), чтобы выполнить необработанные запросы и вернуть экземпляры модели, или же полностью обойти уровень модели и непосредственно выполнить пользовательские запросы SQL.
Изучите ORM перед использованием необработанного SQL!
Django ORM предоставляет множество инструментов для выражения запросов без написания необработанного 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"
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"
desc = cursor.description
nt_result = namedtuple('Result', [col[0] for col in desc])
return [nt_result(*row) for row in cursor.fetchall()]
Вот пример различия между этими тремя вариантами:
>>> 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", а не заполнитель "?", который используется SQLite Python-связями. Это сделано для обеспечения согласованности и корректности.
Использование курсора как контекстного менеджера:
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/4.1/topics/db/sql/