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:
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:
>>> 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:
>>> 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:
>>> 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:
>>> 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:
>>> 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å:
>>> 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:
>>> 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:
>>> 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:
>>> 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():
>>> 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:
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:
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:
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:
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:
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:
>>> 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:
with connection.cursor() as c:
c.execute(...)
är likvärdig med:
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 intekparams. Av Djangos inbyggda backends är det bara Oracle som stöder det.Till exempel, med den här lagrade proceduren i en Oracle-databas:
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:
with connection.cursor() as cursor: cursor.callproc("test_procedure", [1, "test"])