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

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

Изучите ORM, прежде чем использовать чистый SQL!

Django ORM предоставляет множество инструментов для выражения запросов без написания необработанного SQL. Например:

Прежде чем использовать необработанный SQL, изучите ORM . Спросите у django-users или IRC-канала #django, чтобы узнать, поддерживает ли 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 startappPersonmyappmyapp_person

Для получения дополнительных сведений ознакомьтесь с документацией по этой db_tableопции, которая также позволяет вам вручную установить имя таблицы базы данных.

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

Проверка переданного оператора SQL не выполняется .raw(). Django ожидает, что оператор вернет набор строк из базы данных, но ничего не делает для этого. Если запрос не возвращает строки, это приведет к (возможно, загадочной) ошибке.

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

Если вы выполняете запросы в MySQL, обратите внимание, что молчаливое приведение типов в MySQL может привести к неожиданным результатам при смешивании типов. Если вы запрашиваете столбец строкового типа, но с целочисленным значением, MySQL будет преобразовывать типы всех значений в таблице к целому числу перед выполнением сравнения. Например, если таблица содержит значения 'abc', 'def'и вы запрашиваете , обе строки будут совпадать. Чтобы предотвратить это, выполните правильное приведение типов перед использованием значения в запросе.WHERE mycolumn=0

Изменено в Django 3.2:

Значение paramsаргумента по умолчанию было изменено с Noneна пустой кортеж.

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

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аргумент to 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()чтобы получить объект курсора. Затем вызовите для выполнения SQL и или для возврата результирующих строк.cursor.execute(sql, [params])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...

По умолчанию API БД Python будет возвращать результаты без имен полей, что означает, что вы получите a listof values, а не a 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. A 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. Если вы воспользуетесь этим методом, соответствующая библиотека базы данных при необходимости автоматически экранирует ваши параметры.

Также отметим , что Джанго ожидает "%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'])

Copyright ©2021 All rights reserved