Utföra SQL-frågor i råformatLink to this heading

Django ger dig två sätt att utföra råa SQL-frågor: du kan använda Manager.raw() för att utföra råa frågor och returnera modellinstanser, eller så kan du undvika modellskiktet helt och utföra anpassad SQL direkt.

Utföra råa förfrågningarLink to this heading

Hanteringsmetoden raw() kan användas för att utföra råa SQL-frågor som returnerar modellinstanser:

Manager.raw(raw_query, params=(), translations=None)Link to this definition

Den här metoden tar en rå SQL-fråga, kör den och returnerar en django.db.models.query.RawQuerySet-instans. Denna RawQuerySet-instans kan itereras över som en normal QuerySet för att tillhandahålla objektinstanser.

Detta illustreras bäst med ett exempel. Anta att du har följande modell:

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

Du kan sedan köra anpassad SQL på följande sätt:

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

Det här exemplet är inte särskilt spännande - det är exakt samma sak som att köra Person.objects.all(). Men raw() har en massa andra alternativ som gör den mycket kraftfull.

Mappning av frågefält till modellfältLink to this heading

raw() mappar automatiskt fält i frågan till fält i modellen.

Ordningen på fälten i din fråga spelar ingen roll. Med andra ord fungerar båda följande frågor på samma sätt:

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

Matchningen görs med hjälp av namn. Det innebär att du kan använda SQL:s AS-klausuler för att mappa fält i frågan till modellfält. Så om du hade någon annan tabell som hade Person-data i den, kan du enkelt mappa den till Person-instanser:

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
...     """)
...

Så länge namnen stämmer överens kommer modellinstanserna att skapas på rätt sätt.

Alternativt kan du mappa fält i frågan till modellfält med hjälp av argumentet translations till raw(). Detta är en ordbok som mappar namn på fält i frågan till namn på fält i modellen. Till exempel kan ovanstående fråga också skrivas:

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)

IndexuppslagningarLink to this heading

raw() stöder indexering, så om du bara behöver det första resultatet kan du skriva:

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

Indexeringen och skivningen utförs dock inte på databasnivå. Om du har ett stort antal Person-objekt i din databas är det mer effektivt att begränsa frågan på SQL-nivå:

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

Uppskjutande av modellfältLink to this heading

Fält kan också utelämnas:

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

Objekten Person som returneras av denna fråga kommer att vara uppskjutna modellinstanser (se defer()). Detta innebär att de fält som utelämnas från frågan kommer att laddas på begäran. Till exempel:

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

Utåt sett ser det ut som om frågan har hämtat både förnamnet och efternamnet. Men i det här exemplet ställdes faktiskt tre frågor. Endast förnamnen hämtades av raw()-frågan - efternamnen hämtades båda på begäran när de skrevs ut.

Det finns bara ett fält som du inte kan utelämna - fältet för primärnyckeln. Django använder primärnyckeln för att identifiera modellinstanser, så den måste alltid inkluderas i en rå fråga. Ett FieldDoesNotExist undantag kommer att uppstå om du glömmer att inkludera den primära nyckeln.

Lägga till anteckningarLink to this heading

Du kan också köra frågor som innehåller fält som inte är definierade i modellen. Vi kan till exempel använda PostgreSQL’s age() function för att få en lista över personer med deras åldrar beräknade av databasen:

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

Du kan ofta undvika att använda rå SQL för att beräkna annoteringar genom att istället använda ett Func()-uttryck.

Överföra parametrar till raw()Link to this heading

Om du behöver utföra parametriserade frågor kan du använda argumentet params till raw():

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

params är en lista eller en ordbok med parametrar. Du använder %s platshållare i frågesträngen för en lista, eller %(key)s platshållare för en ordbok (där key ersätts av en ordboksnyckel), oavsett vilken databasmotor du använder. Sådana platshållare kommer att ersättas med parametrar från argumentet params.

Exekvera anpassad SQL direktLink to this heading

Ibland räcker inte ens Manager.raw(): du kan behöva utföra frågor som inte mappar rent till modeller, eller direkt köra UPDATE, INSERT eller DELETE-frågor.

I dessa fall kan du alltid komma åt databasen direkt och helt undvika modellagret.

Objektet django.db.connection representerar standardanslutningen till databasen. För att använda databasanslutningen, anropa connection.cursor() för att få ett cursor-objekt. Anropa sedan cursor.execute(sql, [params]) för att köra SQL och cursor.fetchone() eller cursor.fetchall() för att returnera de resulterande raderna.

Till exempel:

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

För att skydda mot SQL-injektion får du inte inkludera citattecken runt platshållarna %s i SQL-strängen.

Observera att om du vill inkludera bokstavliga procenttecken i frågan måste du dubbla dem i det fall du skickar parametrar:

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

Om du använder fler än en databas kan du använda django.db.connections för att hämta anslutningen (och markören) för en specifik databas. django.db.connections är ett ordboksliknande objekt som gör att du kan hämta en specifik anslutning med hjälp av dess alias:

Code
from django.db import connections

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

Som standard kommer Python DB API att returnera resultat utan deras fältnamn, vilket innebär att du får en ”lista” med värden, snarare än ett ”dict”. Med en liten prestanda- och minneskostnad kan du returnera resultat som en dict genom att använda något liknande detta:

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

Ett annat alternativ är att använda collections.namedtuple() från Pythons standardbibliotek. En namedtuple är ett tuple-liknande objekt som har fält som är tillgängliga genom attributuppslagning; det är också indexerbart och itererbart. Resultaten är oföränderliga och tillgängliga med fältnamn eller index, vilket kan vara användbart:

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

Exemplen dictfetchall() och namedtuplefetchall() förutsätter unika kolumnnamn, eftersom en markör inte kan skilja på kolumner från olika tabeller.

Här är ett exempel på skillnaden mellan de tre:

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

Anslutningar och markörerLink to this heading

connection och cursor implementerar mestadels standard Python DB-API som beskrivs i PEP 249 - förutom när det gäller transaktionshantering.

Om du inte är bekant med Python DB-API, notera att SQL-satsen i cursor.execute() använder platshållare, "%s", snarare än att lägga till parametrar direkt i SQL. Om du använder den här tekniken kommer det underliggande databasbiblioteket automatiskt att escape dina parametrar vid behov.

Observera också att Django förväntar sig platshållaren "%s", inte platshållaren "?", som används av SQLite Python-bindningar. Detta är för konsekvensens och sanitetens skull.

Använda en markör som kontexthanterare:

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

är likvärdig med:

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

Anropa lagrade procedurerLink to this heading

CursorWrapper.callproc(procname, params=None, kparams=None)Link to this definition

Anropar en databaslagrad procedur med det angivna namnet. En sekvens (params) eller ett lexikon (kparams) med inparametrar kan anges. De flesta databaser stöder inte kparams. Av Djangos inbyggda backends är det bara Oracle som stöder det.

Till exempel, med den här lagrade proceduren i en Oracle-databas:

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;

Detta kommer att kalla det:

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