Spec-Zone.ru › Django 1.9

Выполнение необработанных запросов SQL

Когда API запросов модели не подходят, вы можете перейти к написанию необработанного SQL. Django предоставляет два способа выполнения необработанных запросов SQL: вы можете использовать Manager.raw() для выполнения необработанных запросов и возврата экземпляров модели или полностью обойти уровень модели и непосредственно выполнить пользовательский SQL.

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

Следует проявлять особую осторожность при написании необработанного 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, обе строки будут соответствовать. Чтобы избежать этого, выполните правильное приведение типов перед использованием значения в запросе.

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

Хотя экземпляр RawQuerySet может быть перебираем, как обычный QuerySet, RawQuerySet не реализует все методы, которые можно использовать с QuerySet. Например, __bool__() и __len__() не определены в RawQuerySet, и поэтому все экземпляры RawQuerySet считаются True. Причина, по которой эти методы не реализованы в RawQuerySet заключается в том, что их реализация без внутренней кэширования была бы недостатком производительности, а добавление такого кэширования было бы несовместимым с обратной совместимостью.

Сопоставление полей запроса с полями модели

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 использует первичный ключ для идентификации экземпляров моделей, поэтому он всегда должен включаться в необработанный запрос. Будет поднята исключение InvalidQuery , если вы забудете включить первичный ключ.

Добавление аннотаций

Вы также можете выполнить запросы, содержащие поля, которые не определены в модели. Например, мы можем использовать 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.
...

Передача параметров в raw()

Если вам необходимо выполнить параметризованные запросы, вы можете использовать аргумент params к raw():

>>> lname = 'Doe'
>>> Person.objects.raw('SELECT * FROM myapp_person WHERE last_name = %s', [lname])

params — это список или словарь параметров. Вы будете использовать %s плейсхолдеры в строке запроса для списка или %(key)s плейсхолдеры для словаря (где key заменяется ключом словаря, конечно), независимо от используемого вами движка базы данных. Такие плейсхолдеры будут заменены параметрами из аргумента params.

Примечание

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

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

Не используйте форматирование строк в необработанных запросах!

Искушение написать вышеприведенный запрос как:

>>> query = 'SELECT * FROM myapp_person WHERE last_name = %s' % lname
>>> Person.objects.raw(query)

Не делайте этого.

Использование аргумента params полностью защищает вас от атак SQL-инъекции, распространённого метода взлома, при котором злоумышленники вставляют произвольный SQL в вашу базу данных. Если вы используете интерполяцию строк, рано или поздно вы станете жертвой 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):
    cursor = connection.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

Обратите внимание, что если вы хотите включить в запрос литеры процента, вам нужно удвоить их в случае передачи параметров:

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
cursor = connections['my_db_alias'].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", а не заполнитель "?", который используется привязками SQLite к Python. Это сделано для поддержания согласованности.

Использование курсора как контекстного менеджера:

with connection.cursor() as c:
    c.execute(...)

эквивалентно:

c = connection.cursor()
try:
    c.execute(...)
finally:
    c.close()

© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/1.9/topics/db/sql/

Spec-Zone.ru

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