Lancement de requêtes SQL brutesLien vers cette rubrique

Django propose trois manières d’exécuter des requêtes SQL brutes : vous pouvez intégrer des fragments de SQL bruts dans les requêtes ORM en utilisant RawSQL (voir Fragments SQL bruts), utiliser Manager.raw() pour exécuter des requêtes brutes et renvoyer des instances de modèles, ou outrepasser complètement la couche des modèles et exécuter directement du code SQL personnalisé.

Fragments SQL brutsLien vers cette rubrique

Dans certains cas, il peut être nécessaire d’intégrer des fragments de code SQL brut directement dans les requêtes ORM, par exemple dans des appels à annotate() ou filter(). Utilisez des expressions Func() pour appeler des fonctions de base de données indépendamment du type de base de données, ou RawSQL pour des fragments directs de SQL paramétrisé.

Lancement de requêtes brutesLien vers cette rubrique

La méthode de gestionnaire raw() peut être utilisée pour exécuter des requêtes SQL brutes qui renvoient des instances de modèles :

Manager.raw(raw_query, params=(), translations=None)Lien vers cette définition

Cette méthode accepte une requête SQL brute, l’exécute et renvoie une instance django.db.models.query.RawQuerySet. Il est possible alors d’effectuer une boucle sur cette instance RawQuerySet comme pour un objet QuerySet normal afin d’accéder aux instances d’objets.

Un exemple vaut mieux que mille mots. Supposons que vous ayez créé le modèle suivant :

Code
class Person(models.Model):
    first_name = models.CharField(...)
    last_name = models.CharField(...)
    birth_date = models.DateField(...)

Vous pouvez alors exécuter du code SQL personnalisé comme ceci :

Python console
>>> for p in Person.objects.raw("SELECT * FROM myapp_person"):
...     print(p)
...
John Smith
Jane Jones

Cet exemple n’est pas des plus passionnants, car il correspond exactement à l’expression Person.objects.all(). Toutefois, raw() comporte quelques autres options qui en font un outil très puissant.

Correspondance entre champs de requête et champs de modèleLien vers cette rubrique

raw() fait automatiquement correspondre les champs de la requête avec les champs du modèle.

L’ordre des champs dans la requête n’est pas important. En d’autres termes, les deux requêtes suivantes donneront le même résultat :

Python console
>>> 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")

La correspondance se fait sur le nom. Cela signifie que vous pouvez utiliser les clauses SQL AS pour faire correspondre les champs de la requête aux champs du modèle. Ainsi, si vous disposez d’une autre table contenant les données de Person, vous pouvez facilement faire correspondre ces données avec des instances de Person:

Python console
>>> Person.objects.raw("""
...     SELECT first AS first_name,
...            last AS last_name,
...            bd AS birth_date,
...            pk AS id,
...     FROM some_other_table
...     """)
...

Tant que les noms correspondent, les instances de modèle seront créées correctement.

Il est aussi possible de faire correspondre les champs de requête aux champs de modèle en utilisant le paramètre translations de raw(). Il s’agit d’un dictionnaire faisant correspondre les noms des champs de la requête aux noms des champs du modèle. Par exemple, la requête ci-dessus aurait aussi pu être écrite de cette manière :

Python console
>>> name_map = {"first": "first_name", "last": "last_name", "bd": "birth_date", "pk": "id"}
>>> Person.objects.raw("SELECT * FROM some_other_table", translations=name_map)

Filtrage par indexLien vers cette rubrique

raw() autorise l’utilisation d’index ; dans le cas où vous souhaitez obtenir uniquement le premier résultat, vous pouvez écrire :

Python console
>>> first_person = Person.objects.raw("SELECT * FROM myapp_person")[0]

Cependant, l’indexation et la segmentation ne sont pas effectuées au niveau de la base de données. Si la base de données contient une grande quantité d’objets Person, il est plus efficace de limiter la requête au niveau SQL :

Python console
>>> first_person = Person.objects.raw("SELECT * FROM myapp_person LIMIT 1")[0]

Report des champs de modèleLien vers cette rubrique

Il est aussi possible d’ignorer certains champs :

Python console
>>> people = Person.objects.raw("SELECT id, first_name FROM myapp_person")

Les objets Person renvoyés par cette requête constitueront des instances de modèle différées (voir defer()). Cela signifie que les champs omis dans la requête seront chargés à la demande. Par exemple :

Python console
>>> 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

En apparence, il semble que la requête ait récupéré à la fois le prénom et le nom. Cependant, cet exemple effectue en réalité 3 requêtes. Seuls les prénoms (first_name) ont été obtenus par la requête raw(), les noms (last_name) ont été obtenus chacun à la demande au moment où ils ont été affichés.

Un seul champ ne peut pas être omis, c’est le champ clé primaire. Django utilise la clé primaire pour identifier les instances de modèle, elle doit donc être obligatoirement incluse dans la requête brute. Une exception FieldDoesNotExist est générée si vous oubliez d’inclure la clé primaire.

Transmission de paramètres dans raw()Lien vers cette rubrique

S’il est nécessaire d’effectuer des requêtes paramétrées, il est possible d’utiliser le paramètre params de raw():

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

params est une liste ou un dictionnaire de paramètres. Dans la chaîne de requête, il faut alors inclure des substituants %s pour une liste ou des substituants``%(clé)s`` pour un dictionnaire (où clé est remplacé par une clé de dictionnaire), quel que soit le moteur de base de données. Ces substituants seront remplacés par le contenu du paramètre params.

Exécution directe de code SQLLien vers cette rubrique

Dans certains cas, même Manager.raw() ne suffit pas : il se peut que des requêtes doivent être effectuées sans correspondre proprement à des modèles ou que vous vouliez exécuter directement des requêtes UPDATE, INSERT ou DELETE.

Dans ces situations, vous pouvez toujours accéder directement à la base de données, outrepassant complètement la couche des modèles.

L’objet django.db.connection représente la connexion à la base de données par défaut. Pour utiliser la connexion à la base de données, appelez connection.cursor() pour obtenir un objet curseur. Puis, appelez cursor.execute(sql, [params]) pour exécuter le code SQL et cursor.fetchone() ou cursor.fetchall() pour obtenir les lignes de résultat.

Par exemple :

Code
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

Pour se protéger des injections SQL, vous devez vous abstenir de placer des guillemets autour des substituants %s dans la chaîne SQL.

Notez que si vous voulez inclure des signes « pour cent » littéraux dans la requête, vous devez les doubler dans le cas où vous transmettez des paramètres :

Code
cursor.execute("SELECT foo FROM bar WHERE baz = '30%'")
cursor.execute("SELECT foo FROM bar WHERE baz = '30%%' AND id = %s", [self.id])

Si vous utilisez plus d’une base de données, vous pouvez utiliser django.db.connections pour obtenir la connexion (et le curseur) pour une base de données spécifique. django.db.connections est un objet de type dictionnaire permettant de récupérer une connexion spécifique en employant son alias :

Code
from django.db import connections

with connections["my_db_alias"].cursor() as cursor:
    # Your code here
    ...

Par défaut, l’API de base de données de Python renvoie les résultats sans les noms de champs, ce qui signifie que vous vous retrouvez avec une liste de valeurs plutôt qu’un dictionnaire. Pour un faible coût en performances et en mémoire, vous pouvez obtenir les résultats sous forme de dictionnaire en écrivant quelque chose comme :

Code
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()]

Une autre option est d’utiliser une structure collections.namedtuple() de la bibliothèque Python standard. Un namedtuple est un objet de type tuple dont les champs sont accessibles sous forme d’attribut ; l’accès par indice est aussi possible et l’objet est itérable. Les résultats sont immuables et accessibles par nom de champ ou par indice, ce qui pourrait être pratique :

Code
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()]

Les exemples dictfetchall() et namedtuplefetchall() supposent que les noms de colonnes sont uniques, car un curseur ne peut pas distinguer les colonnes de tables différentes.

Voici un exemple de la différence entre les trois :

Python console
>>> 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

Connexions et curseursLien vers cette rubrique

connection et cursor implémentent essentiellement l’API de base de données standard de Python décrite dans la PEP 249, à l’exception de ce qui concerne la gestion des transactions.

Si cette API DB de Python ne vous est pas familière, notez que l’instruction SQL dans cursor.execute() utilise des substituants, "%s", plutôt que d’ajouter directement les paramètres dans la chaîne SQL. Si vous utilisez cette technique, la bibliothèque sous-jacente de base de données s’occupe automatiquement d’échapper vos paramètres au besoin.

Notez également que Django compte sur des substituants "%s", pas de substituants "?" qui sont utilisés par la bibliothèque SQLite de Python, pour des raisons de cohérence et de bon sens.

L’utilisation d’un curseur en tant que gestionnaire de contexte :

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

est équivalent à :

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

Appeler des procédures stockéesLien vers cette rubrique

CursorWrapper.callproc(procname, params=None, kparams=None)Lien vers cette définition

Appelle une procédure stockée de base de données ayant le nom indiqué. Une liste (params) ou un dictionnaire (kparams) de paramètres d’entrée peuvent être fournis. La plupart des bases de données n’acceptent pas kparams. Parmi celles qui sont prises en charge nativement par Django, seule Oracle accepte kparams.

Par exemple, avec cette procédure stockée dans une base de données Oracle :

SQL
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;

Ceci l’appellera

Code
with connection.cursor() as cursor:
    cursor.callproc("test_procedure", [1, "test"])