Spec-Zone.ru › Django 4.2

Выполнение необработанных запросов 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, p.last_name  # This will be retrieved by the original query
...     )  # 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.

Примечание

Параметры словаря не поддерживаются с back-эндом SQLite; с этим back-эндом вы должны передавать параметры в виде списка.

Предупреждение

Не используйте форматирование строк в необработанных запросах или не заключайте заполнители в кавычки в ваших строках 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
    ...
END_OF_DOCUMENT_MARKER

По умолчанию, 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/4.2/topics/db/sql/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API