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