Выполнение необработанных запросов 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")
Сопоставление выполняется по имени. Это означает, что вы можете использовать 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.
Добавление аннотаций
Вы также можете выполнять запросы, содержащие поля, которые не определены в модели. Например, мы можем использовать функцию PostgreSQL age(), чтобы получить список людей с вычисленным возрастом в базе данных:
>>> 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", а не "?", который используется 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/5.0/topics/db/sql/