Выполнение произвольных запросов 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=None, 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" плейсхолдер, а не "?" плейсхолдер, который используется связками 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/3.1/topics/db/sql/