Использование чистого SQL¶
Django предоставляет вам два способа выполнения необработанных SQL-запросов: вы можете использовать Manager.raw() для `выполнения необработанных запросов и возврата экземпляров модели`__ или вы можете полностью отказаться от уровня модели и `__ выполнять собственный SQL напрямую`__.
Прежде чем использовать чистый SQL, изучите ORM!
Django ORM предоставляет множество инструментов для выражения запросов без написания чистого SQL. Например:
QuerySet API обширен.
Вы можете
аннотироватьи агрегировать, используя множество встроенных функций базы данных. Помимо этого, вы можете создавать собственные выражения запросов.
Прежде чем использовать необработанный 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 преобразует все значения поля в число перед сравнением. Например, если таблица содержит значения '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() <func-expressions>`.
Передача параметров в 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-кода. При вставке параметров непосредственно в строку запроса, рано или поздно вы станете жертвой атаки. Используя аргумент params вы обезопасите себя от этого.
Непосредственное выполнение 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 вернет результат без названий полей, то есть вы получите список значений, а не словарь. За счет небольшой потери производительности вы можете получить словарь следующим образом:
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"])