Выполнение произвольных SQL-запросов
Django предоставляет два способа выполнения произвольных SQL-запросов: вы можете использовать Manager.raw(), чтобы выполнить прямой запрос и получить экземпляры модели, или полностью обойти слой модели и непосредственно выполнить пользовательский SQL-запрос.
Изучите ORM перед использованием SQL!
Django ORM предоставляет множество инструментов для выражения запросов без написания SQL. Например:
- API QuerySet очень обширен.
- Вы можете
annotateи агрегировать с использованием многих встроенных функций базы данных. Помимо этого, вы можете создавать пользовательские выражения запросов.
Перед использованием SQL, изучите ORM. Задавайте вопросы на django-users или в #django IRC-канале, чтобы узнать, поддерживает ли 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, обе строки будут соответствовать. Чтобы предотвратить это, выполните корректное приведение типов перед использованием значения в запросе.
Значение по умолчанию аргумента params было изменено с None на пустой кортеж.
Сопоставление полей запроса с полями модели
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 backend; с этим backend, вы должны передавать параметры в виде списка.
Предупреждение
Не используйте форматирование строк в произвольных запросах или заключайте маркеры в кавычки в ваших 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/3.2/topics/db/sql/