Frågeuttryck¶
Frågeuttryck beskriver ett värde eller en beräkning som kan användas som en del av en uppdatering, ett skapande, ett filter, en ordning efter, en annotering eller ett aggregat. När ett uttryck ger ett booleskt värde kan det användas direkt i filter. Det finns ett antal inbyggda uttryck (dokumenteras nedan) som kan användas för att hjälpa dig att skriva frågor. Uttryck kan kombineras, eller i vissa fall nästlas, för att bilda mer komplexa beräkningar.
Stöd för aritmetik¶
Django stöder negation, addition, subtraktion, multiplikation, division, modulo aritmetik och power-operatorn på frågeuttryck med hjälp av Python-konstanter, variabler och till och med andra uttryck.
Utgångsfält¶
Många av de uttryck som dokumenteras i detta avsnitt stöder en valfri parameter output_field
. Om den anges kommer Django att ladda värdet i det fältet efter att ha hämtat det från databasen.
output_field
tar en modellfältsinstans, som IntegerField()
eller BooleanField()
. Vanligtvis behöver fältet inte några argument, som max_length
, eftersom fältargument avser datavalidering som inte kommer att utföras på uttryckets utdatavärde.
output_field
krävs endast när Django inte automatiskt kan bestämma resultatets fälttyp, till exempel komplexa uttryck som blandar fälttyper. Om du till exempel lägger till en DecimalField()
och en FloatField()
krävs ett utmatningsfält, som output_field=FloatField()
.
Några exempel¶
>>> from django.db.models import Count, F, Value
>>> from django.db.models.functions import Length, Upper
>>> from django.db.models.lookups import GreaterThan
# Find companies that have more employees than chairs.
>>> Company.objects.filter(num_employees__gt=F("num_chairs"))
# Find companies that have at least twice as many employees
# as chairs. Both the querysets below are equivalent.
>>> Company.objects.filter(num_employees__gt=F("num_chairs") * 2)
>>> Company.objects.filter(num_employees__gt=F("num_chairs") + F("num_chairs"))
# How many chairs are needed for each company to seat all employees?
>>> company = (
... Company.objects.filter(num_employees__gt=F("num_chairs"))
... .annotate(chairs_needed=F("num_employees") - F("num_chairs"))
... .first()
... )
>>> company.num_employees
120
>>> company.num_chairs
50
>>> company.chairs_needed
70
# Create a new company using expressions.
>>> company = Company.objects.create(name="Google", ticker=Upper(Value("goog")))
# Be sure to refresh it if you need to access the field.
>>> company.refresh_from_db()
>>> company.ticker
'GOOG'
# Annotate models with an aggregated value. Both forms
# below are equivalent.
>>> Company.objects.annotate(num_products=Count("products"))
>>> Company.objects.annotate(num_products=Count(F("products")))
# Aggregates can contain complex computations also
>>> Company.objects.annotate(num_offerings=Count(F("products") + F("services")))
# Expressions can also be used in order_by(), either directly
>>> Company.objects.order_by(Length("name").asc())
>>> Company.objects.order_by(Length("name").desc())
# or using the double underscore lookup syntax.
>>> from django.db.models import CharField
>>> from django.db.models.functions import Length
>>> CharField.register_lookup(Length)
>>> Company.objects.order_by("name__length")
# Boolean expression can be used directly in filters.
>>> from django.db.models import Exists, OuterRef
>>> Company.objects.filter(
... Exists(Employee.objects.filter(company=OuterRef("pk"), salary__gt=10))
... )
# Lookup expressions can also be used directly in filters
>>> Company.objects.filter(GreaterThan(F("num_employees"), F("num_chairs")))
# or annotations.
>>> Company.objects.annotate(
... need_chairs=GreaterThan(F("num_employees"), F("num_chairs")),
... )
Inbyggda uttryck¶
Observera
Dessa uttryck definieras i django.db.models.expressions
och django.db.models.aggregates
, men för enkelhetens skull är de tillgängliga och importeras vanligtvis från django.db.models
.
F()`
uttryck¶
Ett F()
-objekt representerar värdet av ett modellfält, transformerat värde av ett modellfält eller annoterad kolumn. Det gör det möjligt att referera till modellfältsvärden och utföra databasoperationer med hjälp av dem utan att faktiskt behöva dra ut dem från databasen till Python-minnet.
Istället använder Django objektet F()
för att generera ett SQL-uttryck som beskriver den nödvändiga operationen på databasnivå.
Låt oss prova detta med ett exempel. Normalt kan man göra något liknande:
# Tintin filed a news story!
reporter = Reporters.objects.get(name="Tintin")
reporter.stories_filed += 1
reporter.save()
Här har vi hämtat värdet av reporter.stories_filed
från databasen till minnet och manipulerat det med hjälp av välkända Python-operatorer, och sedan sparat objektet tillbaka till databasen. Men istället kunde vi också ha gjort:
from django.db.models import F
reporter = Reporters.objects.get(name="Tintin")
reporter.stories_filed = F("stories_filed") + 1
reporter.save()
Även om reporter.stories_filed = F('stories_filed') + 1
ser ut som en normal Python-tilldelning av värde till ett instansattribut, är det i själva verket en SQL-konstruktion som beskriver en operation i databasen.
När Django stöter på en instans av F()
åsidosätter den Pythons standardoperatorer för att skapa ett inkapslat SQL-uttryck; i det här fallet ett som instruerar databasen att öka databasfältet som representeras av reporter.stories_filed
.
Oavsett vilket värde som finns eller fanns på reporter.stories_filed
får Python aldrig veta något om det - det hanteras helt och hållet av databasen. Allt Python gör, genom Djangos F()
-klass, är att skapa SQL-syntaxen för att hänvisa till fältet och beskriva operationen.
För att komma åt det nya värdet som sparats på detta sätt måste objektet laddas om:
reporter = Reporters.objects.get(pk=reporter.pk)
# Or, more succinctly:
reporter.refresh_from_db()
Förutom att användas i operationer på enskilda instanser som ovan, kan F()
användas med update()
för att utföra massuppdateringar på en QuerySet
. Detta reducerar de två frågor vi använde ovan - get()
och save()
- till bara en:
reporter = Reporters.objects.filter(name="Tintin")
reporter.update(stories_filed=F("stories_filed") + 1)
Vi kan också använda update()
för att öka fältvärdet på flera objekt - vilket kan vara mycket snabbare än att hämta dem alla till Python från databasen, loopa över dem, öka fältvärdet för var och en och spara var och en tillbaka till databasen:
Reporter.objects.update(stories_filed=F("stories_filed") + 1)
F()
kan därför erbjuda prestandafördelar genom:
att få databasen, snarare än Python, att göra jobbet
minska antalet förfrågningar som vissa operationer kräver
Skivning av F()
-uttryck¶
För strängbaserade fält, textbaserade fält och ArrayField
kan du använda Pythons syntax för array-slicing. Indexen är 0-baserade och step
-argumentet till lice
stöds inte. Till exempel:
>>> # Replacing a name with a substring of itself.
>>> writer = Writers.objects.get(name="Priyansh")
>>> writer.name = F("name")[1:5]
>>> writer.save()
>>> writer.refresh_from_db()
>>> writer.name
'riya'
Undvika tävlingsförhållanden med hjälp av F()
¶
En annan fördel med F()
är att om databasen - snarare än Python - uppdaterar ett fälts värde undviks ett race condition.
Om två Python-trådar kör koden i det första exemplet ovan kan den ena tråden hämta, öka och spara ett fälts värde efter att den andra har hämtat det från databasen. Det värde som den andra tråden sparar kommer att baseras på det ursprungliga värdet; den första trådens arbete kommer att gå förlorat.
Om databasen ansvarar för uppdateringen av fältet är processen mer robust: fältet kommer bara att uppdateras baserat på fältets värde i databasen när save()
eller update()
körs, snarare än baserat på dess värde när instansen hämtades.
F()
-uppdrag kvarstår efter Model.save()
¶
F()
-objekt som tilldelats modellfält kvarstår efter att modellinstansen sparats och kommer att tillämpas på varje save()
. Till exempel:
reporter = Reporters.objects.get(name="Tintin")
reporter.stories_filed = F("stories_filed") + 1
reporter.save()
reporter.name = "Tintin Jr."
reporter.save()
stories_filed
kommer att uppdateras två gånger i det här fallet. Om det ursprungligen är 1
kommer det slutliga värdet att vara 3
. Denna persistens kan undvikas genom att ladda om modellobjektet efter att det har sparats, till exempel genom att använda refresh_from_db()
.
Använda F()
i filter¶
F()
är också mycket användbart i QuerySet
-filter, där de gör det möjligt att filtrera en uppsättning objekt mot kriterier baserade på deras fältvärden, snarare än på Python-värden.
Detta finns dokumenterat i :ref:``Använda F()-uttryck i frågor <using-f-expressions-in-filters>`.
Använda F()
med annotationer¶
F()
kan användas för att skapa dynamiska fält i dina modeller genom att kombinera olika fält med aritmetik:
company = Company.objects.annotate(chairs_needed=F("num_employees") - F("num_chairs"))
Om de fält som du kombinerar är av olika typer måste du tala om för Django vilken typ av fält som ska returneras. De flesta uttryck stöder :ref:output_field<output-field>
för detta fall, men eftersom F()
inte gör det, måste du omsluta uttrycket med ExpressionWrapper
:
from django.db.models import DateTimeField, ExpressionWrapper, F
Ticket.objects.annotate(
expires=ExpressionWrapper(
F("active_at") + F("duration"), output_field=DateTimeField()
)
)
Vid hänvisning till relationsfält som ForeignKey
returnerar F()
primärnyckelvärdet i stället för en modellinstans:
>>> car = Company.objects.annotate(built_by=F("manufacturer"))[0]
>>> car.manufacturer
<Manufacturer: Toyota>
>>> car.built_by
3
Använda F()
för att sortera nollvärden¶
Använd F()
och nyckelordsargumentet nulls_first
eller nulls_last
till Expression.asc()
eller desc()
för att styra ordningsföljden för ett fälts null-värden. Som standard beror ordningsföljden på din databas.
Till exempel:, för att sortera företag som inte har kontaktats (last_contacted
är null) efter företag som har kontaktats:
from django.db.models import F
Company.objects.order_by(F("last_contacted").desc(nulls_last=True))
Använda F()
med logiska operationer¶
F()
-uttryck som ger BooleanField
kan logiskt negeras med inversionsoperatorn ~F()
. Till exempel:, för att byta aktiveringsstatus för företag:
from django.db.models import F
Company.objects.update(is_active=~F("is_active"))
Func()
uttryck¶
Func()
-uttryck är bastypen för alla uttryck som involverar databasfunktioner som COALESCE
och LOWER
, eller aggregat som SUM
. De kan användas direkt:
from django.db.models import F, Func
queryset.annotate(field_lower=Func(F("field"), function="LOWER"))
eller så kan de användas för att bygga upp ett bibliotek med databasfunktioner:
class Lower(Func):
function = "LOWER"
queryset.annotate(field_lower=Lower("field"))
Men båda fallen kommer att resultera i en frågeuppsättning där varje modell är annoterad med ett extra attribut field_lower
som produceras, ungefär, från följande SQL:
SELECT
...
LOWER("db_table"."field") as "field_lower"
Se Databasfunktioner för en lista över inbyggda databasfunktioner.
API:et Func
är uppbyggt enligt följande:
- class Func(*expressions, **extra)[source]¶
- function¶
Ett klassattribut som beskriver den funktion som ska genereras. Specifikt kommer
funktionen
att interpoleras somfunktion
platshållare inomtemplate
. Standardvärdet ärNone
.
- template¶
Ett klassattribut, i form av en formatsträng, som beskriver den SQL som genereras för denna funktion. Standardvärdet är
'%(function)s(%(expressions)s)'
.Om du konstruerar SQL som
strftime('%W', 'date')
och behöver ett bokstavligt%
-tecken i frågan, fyrdubblar du det (%%%%
) i attributettemplate
eftersom strängen interpoleras två gånger: en gång under mallinterpolationen ias_sql()
och en gång i SQL-interpolationen med frågeparametrarna i databasmarkören.
- arg_joiner¶
Ett klassattribut som anger det tecken som används för att sammanfoga listan med
uttryck
. Standardvärden är', '
.
- arity¶
Ett klassattribut som anger antalet argument som funktionen accepterar. Om detta attribut är inställt och funktionen anropas med ett annat antal uttryck, kommer
TypeError
att uppstå. Standardvärde ärNone
.
- as_sql(compiler, connection, function=None, template=None, arg_joiner=None, **extra_context)[source]¶
Genererar SQL-fragmentet för databasfunktionen. Returnerar en tupel
(sql, params)
, därsql
är SQL-strängen ochparams
är listan eller tupeln med frågeparametrar.Metoderna
as_vendor()
bör använda parametrarnafunction
,template
,arg_joiner
och alla andra**extra_context
för att anpassa SQL efter behov. Ett exempel:django/db/modeller/functions.py
¶class ConcatPair(Func): ... function = "CONCAT" ... def as_mysql(self, compiler, connection, **extra_context): return super().as_sql( compiler, connection, function="CONCAT_WS", template="%(function)s('', %(expressions)s)", **extra_context )
För att undvika en SQL-injektionssårbarhet får
extra_context
:ref:``inte innehålla otillförlitlig användarinmatning <avoiding-sql-injection-in-query-expressions>` eftersom dessa värden interpoleras in i SQL-strängen i stället för att skickas som frågeparametrar, där databasdrivrutinen skulle undkomma dem.
Argumentet *expressions
är en lista med positionella uttryck som funktionen ska tillämpas på. Uttrycken konverteras till strängar, sammanfogas med arg_joiner
och interpoleras sedan in i mallen
som platshållaren expressions
.
Positionella argument kan vara uttryck eller Python-värden. Strängar antas vara kolumnreferenser och kommer att packas in i F()
uttryck medan andra värden kommer att packas in i Value()
uttryck.
De **extra
kwargs är key=value
par som kan interpoleras in i template
attributet. För att undvika en SQL-injektionssårbarhet, extra
:ref:``får inte innehålla otillförlitlig användarinmatning <avoiding-sql-injection-in-query-expressions>` eftersom dessa värden interpoleras in i SQL-strängen i stället för att skickas som frågeparametrar, där databasdrivrutinen skulle undkomma dem.
Nyckelorden function
, template
och arg_joiner
kan användas för att ersätta attribut med samma namn utan att behöva definiera en egen klass. output_field kan användas för att definiera den förväntade returtypen.
uttryck för Aggregate()
¶
Ett aggregatuttryck är ett specialfall av ett Func()-uttryck som informerar frågan om att en GROUP BY
-klausul krävs. Alla aggregate-funktioner, som Sum()
och Count()
, ärver från Aggregate()
.
Eftersom Aggregate
är uttryck och omslagsuttryck kan du representera vissa komplexa beräkningar:
from django.db.models import Count
Company.objects.annotate(
managers_required=(Count("num_employees") / 4) + Count("num_managers")
)
API:et för Aggregate
är som följer:
- class Aggregate(*expressions, output_field=None, distinct=False, filter=None, default=None, **extra)[source]¶
- template¶
Ett klassattribut, i form av en formatsträng, som beskriver den SQL som genereras för detta aggregat. Standardvärdet är
'%(function)s(%(distinct)s%(expressions)s)'
.
- function¶
Ett klassattribut som beskriver den aggregerade funktion som ska genereras. Specifikt kommer
funktionen
att interpoleras somfunktion
platshållare inomtemplate
. Standardvärdet ärNone
.
- window_compatible¶
Standardvärdet är
True
eftersom de flesta aggregerade funktioner kan användas som källuttryck iWindow
.
- allow_distinct¶
Ett klassattribut som avgör om denna aggregatfunktion tillåter att ett argument med nyckelordet
distinct
skickas eller inte. Om det är satt tillFalse
(standard), kommerTypeError
att uppstå omdistinct=True
skickas.
- empty_result_set_value¶
Standardvärdet är
None
eftersom de flesta aggregeringsfunktioner resulterar iNULL
när de tillämpas på en tom resultatmängd.
De positionella argumenten expressions
kan innehålla uttryck, transformationer av modellfältet eller namn på modellfält. De konverteras till en sträng och används som platshållare för expressions
i template
.
Argumentet distinct
avgör om aggregatfunktionen ska anropas för varje distinkt värde i expressions
(eller uppsättning värden, för flera expressions
). Argumentet stöds endast av aggregat som har allow_distinct
satt till True
.
Argumentet filter
tar ett Q-objekt
som används för att filtrera de rader som aggregeras. Se Villkorlig aggregering och Filtrering av anteckningar för exempel på användning.
Argumentet default
tar ett värde som kommer att skickas tillsammans med aggregatet till Coalesce
. Detta är användbart för att ange ett annat värde som ska returneras än None
när frågeuppsättningen (eller grupperingen) inte innehåller några poster.
De **extra
kwargs är nyckel=värde
-par som kan interpoleras in i template
-attributet.
Skapa dina egna aggregerade funktioner¶
Du kan också skapa dina egna aggregerade funktioner. Som ett minimum måste du definiera function
, men du kan också helt anpassa den SQL som genereras. Här är ett kort exempel:
from django.db.models import Aggregate
class Sum(Aggregate):
# Supports SUM(ALL field).
function = "SUM"
template = "%(function)s(%(all_values)s%(expressions)s)"
allow_distinct = False
arity = 1
def __init__(self, expression, all_values=False, **extra):
super().__init__(expression, all_values="ALL " if all_values else "", **extra)
uttryck för Value()
¶
Ett Value()
-objekt representerar den minsta möjliga komponenten i ett uttryck: ett enkelt värde. När du behöver representera värdet av ett heltal, en boolean eller en sträng i ett uttryck, kan du packa in värdet i ett Value()
.
Du kommer sällan att behöva använda Value()
direkt. När du skriver uttrycket F('field') + 1
omsluter Django implicit 1
i en Value()
, vilket gör att enkla värden kan användas i mer komplexa uttryck. Du kommer att behöva använda Value()
när du vill skicka en sträng till ett uttryck. De flesta uttryck tolkar ett strängargument som namnet på ett fält, t.ex. Lower('name')
.
Argumentet value
beskriver det värde som ska ingå i uttrycket, t.ex. 1
, True
eller None
. Django vet hur man konverterar dessa Python-värden till deras motsvarande databastyp.
Om ingen output_field<output-field>` anges, kommer den att härledas från typen av det angivna värdet
för många vanliga typer. Om du till exempel skickar en instans av datetime.datetime
som value
blir standardvärdet för output_field
DateTimeField
.
ExpressionWrapper()
uttryck¶
ExpressionWrapper
omger ett annat uttryck och ger tillgång till egenskaper, t.ex. :ref:output_field<output-field>
, som kanske inte är tillgängliga för andra uttryck. ExpressionWrapper
är nödvändig när man använder aritmetik på F()
-uttryck med olika typer enligt beskrivningen i :ref:using-f-with-annotations
.
Villkorliga uttryck¶
Med villkorliga uttryck kan du använda if
… elif
… else
logik i frågor. Django har inbyggt stöd för SQL CASE
uttryck. För mer detaljer se Villkorliga uttryck.
uttryck för Subquery()
¶
Du kan lägga till en explicit underfråga till en QuerySet
med hjälp av uttrycket Subquery
.
Till exempel: för att kommentera varje inlägg med e-postadressen till författaren till den nyaste kommentaren till det inlägget:
>>> from django.db.models import OuterRef, Subquery
>>> newest = Comment.objects.filter(post=OuterRef("pk")).order_by("-created_at")
>>> Post.objects.annotate(newest_commenter_email=Subquery(newest.values("email")[:1]))
På PostgreSQL ser SQL ut som:
SELECT "post"."id", (
SELECT U0."email"
FROM "comment" U0
WHERE U0."post_id" = ("post"."id")
ORDER BY U0."created_at" DESC LIMIT 1
) AS "newest_commenter_email" FROM "post"
Observera
Exemplen i detta avsnitt är utformade för att visa hur man tvingar Django att utföra en underfråga. I vissa fall kan det vara möjligt att skriva ett likvärdigt queryset som utför samma uppgift tydligare eller effektivare.
Hänvisning till kolumner från den yttre queryset¶
Använd OuterRef
när en queryset i en Subquery
behöver referera till ett fält från den yttre queryn eller dess transform. Det fungerar som ett F
-uttryck förutom att kontrollen för att se om det hänvisar till ett giltigt fält inte görs förrän den yttre frågeuppsättningen har lösts upp.
Instanser av OuterRef
kan användas tillsammans med nästlade instanser av Subquery
för att hänvisa till en innehållande frågeuppsättning som inte är den omedelbara föräldern. Till exempel: skulle denna frågeuppsättning behöva vara inom ett nästlat par av Subquery
-instanser för att lösas korrekt:
>>> Book.objects.filter(author=OuterRef(OuterRef("pk")))
Begränsa en underfrågeställning till en enda kolumn¶
Det finns tillfällen då en enda kolumn måste returneras från en Subquery
, t.ex. för att använda en Subquery
som mål för en __in
-uppslagning. För att returnera alla kommentarer för inlägg som publicerats under den senaste dagen:
>>> from datetime import timedelta
>>> from django.utils import timezone
>>> one_day_ago = timezone.now() - timedelta(days=1)
>>> posts = Post.objects.filter(published_at__gte=one_day_ago)
>>> Comment.objects.filter(post__in=Subquery(posts.values("pk")))
I det här fallet måste underfrågan använda values()
för att bara returnera en enda kolumn: inläggets primärnyckel.
Begränsa underfrågeställningen till en enda rad¶
För att förhindra att en underfråga returnerar flera rader används en del ([:1]
) av frågeuppsättningen:
>>> subquery = Subquery(newest.values("email")[:1])
>>> Post.objects.annotate(newest_commenter_email=subquery)
I det här fallet får subquery bara returnera en enda kolumn och en enda rad: e-postadressen till den senast skapade kommentaren.
(Att använda get()
istället för en slice skulle misslyckas eftersom OuterRef
inte kan lösas förrän queryset används inom en Subquery
)
Existerar()
underfrågor¶
Exists
är en Subquery
-underklass som använder en SQL EXISTS
-sats. I många fall kommer den att fungera bättre än en subquery eftersom databasen kan stoppa utvärderingen av subquery när en första matchande rad hittas.
Till exempel: för att markera varje inlägg med om det har en kommentar från den senaste dagen eller inte:
>>> from django.db.models import Exists, OuterRef
>>> from datetime import timedelta
>>> from django.utils import timezone
>>> one_day_ago = timezone.now() - timedelta(days=1)
>>> recent_comments = Comment.objects.filter(
... post=OuterRef("pk"),
... created_at__gte=one_day_ago,
... )
>>> Post.objects.annotate(recent_comment=Exists(recent_comments))
På PostgreSQL ser SQL ut som:
SELECT "post"."id", "post"."published_at", EXISTS(
SELECT (1) as "a"
FROM "comment" U0
WHERE (
U0."created_at" >= YYYY-MM-DD HH:MM:SS AND
U0."post_id" = "post"."id"
)
LIMIT 1
) AS "recent_comment" FROM "post"
Det är onödigt att tvinga Exists
att hänvisa till en enda kolumn, eftersom kolumnerna kasseras och ett booleskt resultat returneras. På samma sätt, eftersom ordning är oviktigt inom en SQL EXISTS
subquery och bara skulle försämra prestanda, tas det automatiskt bort.
Du kan ställa frågor med hjälp av NOT EXISTS
med ~Exists()
.
Filtrering på ett Subquery()
- eller Exists()
-uttryck¶
Subquery()
som returnerar ett booleskt värde och Exists()
kan användas som ett villkor
i When
-uttryck, eller för att direkt filtrera en frågeuppsättning:
>>> recent_comments = Comment.objects.filter(...) # From above
>>> Post.objects.filter(Exists(recent_comments))
Detta säkerställer att subquery inte läggs till i SELECT
-kolumnerna, vilket kan leda till bättre prestanda.
Använda aggregat inom ett Subquery
-uttryck¶
Aggregat kan användas i en Subquery
, men de kräver en specifik kombination av filter()
, values()
och annotate()
för att grupperingen av subquery ska bli korrekt.
Förutsatt att båda modellerna har ett längd
-fält, för att hitta inlägg där inläggslängden är större än den totala längden på alla kombinerade kommentarer:
>>> from django.db.models import OuterRef, Subquery, Sum
>>> comments = Comment.objects.filter(post=OuterRef("pk")).order_by().values("post")
>>> total_comments = comments.annotate(total=Sum("length")).values("total")
>>> Post.objects.filter(length__gt=Subquery(total_comments))
Den inledande filter(...)
begränsar underfrågan till de relevanta parametrarna. order_by()
tar bort standard ordering
(om någon) på Comment
-modellen. values('post')
aggregerar kommentarer efter Post
. Slutligen utför annotate(...)
aggregeringen. Den ordning i vilken dessa queryset-metoder tillämpas är viktig. I det här fallet, eftersom underfrågan måste begränsas till en enda kolumn, krävs values('total')
.
Detta är det enda sättet att utföra en aggregering inom en Subquery
, eftersom användning av aggregate()
försöker utvärdera queryset (och om det finns en OuterRef
kommer detta inte att vara möjligt att lösa).
Råa SQL-uttryck¶
Ibland kan databasuttryck inte enkelt uttrycka en komplex WHERE
klausul. I dessa kantfall använder du RawSQL
-uttrycket. Till exempel:
>>> from django.db.models.expressions import RawSQL
>>> queryset.annotate(val=RawSQL("select col from sometable where othercol = %s", (param,)))
Dessa extra uppslagningar kanske inte är portabla till olika databasmotorer (eftersom du uttryckligen skriver SQL-kod) och bryter mot DRY-principen, så du bör undvika dem om möjligt.
RawSQL
-uttryck kan också användas som mål för __in
-filter:
>>> queryset.filter(id__in=RawSQL("select id from sometable where col = %s", (param,)))
Varning
För att skydda mot SQL injection attacks, måste du undkomma alla parametrar som användaren kan kontrollera genom att använda params
. params
är ett obligatoriskt argument för att tvinga dig att erkänna att du inte interpolerar din SQL med användartillhandahållna data.
Du får inte heller använda citattecken för platshållare i SQL-strängen. Det här exemplet är sårbart för SQL-injektion på grund av citaten runt %s
:
RawSQL("select col from sometable where othercol = '%s'") # unsafe!
Du kan läsa mer om hur Djangos SQL injection protection fungerar.
Fönsterfunktioner¶
Fönsterfunktioner är ett sätt att tillämpa funktioner på partitioner. Till skillnad från en vanlig aggregeringsfunktion som beräknar ett slutresultat för varje uppsättning som definieras av group by, arbetar fönsterfunktioner med ramar och partitioner och beräknar resultatet för varje rad.
Du kan ange flera fönster i samma fråga, vilket i Django ORM skulle motsvara att inkludera flera uttryck i ett QuerySet.annotate()-anrop. ORM använder sig inte av namngivna fönster, utan de är en del av de valda kolumnerna.
- class Window(expression, partition_by=None, order_by=None, frame=None, output_field=None)[source]¶
- template¶
Standardvärdet är
%(expression)s OVER (%(window)s)
. Om endast argumentetexpression
anges, kommer window-satsen att vara tom.
Klassen Window
är huvuduttrycket för en OVER
-sats.
Argumentet expression
är antingen en :ref:``window function <window-functions>`, en :ref:``aggregate function <aggregation-functions>`, eller ett uttryck som är kompatibelt i en window clause.
Argumentet partition_by
accepterar ett uttryck eller en sekvens av uttryck (kolumnnamnen bör vara inkapslade i ett F
-objekt) som styr partitioneringen av raderna. Partitioneringen begränsar vilka rader som används för att beräkna resultatuppsättningen.
:ref:output_field<output-field>
anges antingen som ett argument eller genom ett uttryck.
Argumentet order_by
accepterar ett uttryck som du kan anropa asc()
och desc()
, en sträng med ett fältnamn (med ett valfritt prefix "-"
som anger fallande ordning), eller en tupel eller lista med strängar och/eller uttryck. Ordningsföljden styr i vilken ordning uttrycket tillämpas. Om du t.ex. summerar över raderna i en partition är det första resultatet värdet på den första raden, det andra är summan av den första och den andra raden.
Parametern frame
anger vilka andra rader som ska användas i beräkningen. Se Ramar för mer information.
Till exempel: för att kommentera varje film med det genomsnittliga betyget för filmer från samma studio i samma genre och utgivningsår:
>>> from django.db.models import Avg, F, Window
>>> Movie.objects.annotate(
... avg_rating=Window(
... expression=Avg("rating"),
... partition_by=[F("studio"), F("genre")],
... order_by="released__year",
... ),
... )
Detta gör att du kan kontrollera om en film har fått bättre eller sämre betyg än sina konkurrenter.
Du kanske vill använda flera uttryck över samma fönster, dvs. samma partition och ram. Du kan t.ex. ändra det föregående exemplet så att det även inkluderar det bästa och sämsta betyget i varje filmgrupp (samma studio, genre och utgivningsår) genom att använda tre fönsterfunktioner i samma fråga. Partitionen och ordningen från föregående exempel extraheras till en ordbok för att minska upprepning:
>>> from django.db.models import Avg, F, Max, Min, Window
>>> window = {
... "partition_by": [F("studio"), F("genre")],
... "order_by": "released__year",
... }
>>> Movie.objects.annotate(
... avg_rating=Window(
... expression=Avg("rating"),
... **window,
... ),
... best=Window(
... expression=Max("rating"),
... **window,
... ),
... worst=Window(
... expression=Min("rating"),
... **window,
... ),
... )
Filtrering mot fönsterfunktioner stöds så länge som uppslagningar inte är disjunktiva (inte använder OR
eller XOR
som en koppling) och mot en frågeuppsättning som utför aggregering.
Exempelvis stöds inte en fråga som bygger på aggregering och som har ett filter med OR
mot en fönsterfunktion och ett fält. Om kombinerade predikat tillämpas efter aggregering kan det leda till att rader som normalt inte ingår i grupper inkluderas:
>>> qs = Movie.objects.annotate(
... category_rank=Window(Rank(), partition_by="category", order_by="-rating"),
... scenes_count=Count("actors"),
... ).filter(Q(category_rank__lte=3) | Q(title__contains="Batman"))
>>> list(qs)
NotImplementedError: Heterogeneous disjunctive predicates against window functions
are not implemented when performing conditional aggregation.
Bland Djangos inbyggda databasbackends stöder MySQL, PostgreSQL och Oracle fönsteruttryck. Stödet för olika funktioner i fönsteruttryck varierar mellan de olika databaserna. Till exempel: kanske alternativen i asc()
och desc()
inte stöds. Konsultera dokumentationen för din databas vid behov.
Ramar¶
För en fönsterram kan du välja antingen en intervallbaserad sekvens av rader eller en vanlig sekvens av rader.
- class ValueRange(start=None, end=None, exclusion=None)[source]¶
- frame_type¶
Detta attribut är inställt på
'RANGE'
.
PostgreSQL har begränsat stöd för
ValueRange
och stöder endast användning av standardstart- och slutpunkter, såsomCURRENT ROW
ochUNBOUNDED FOLLOWING
.Changed in Django 5.1:Argumentet
exkludering
har lagts till.
- class RowRange(start=None, end=None, exclusion=None)[source]¶
- frame_type¶
Detta attribut är inställt på
'ROWS'
.
Changed in Django 5.1:Argumentet
exkludering
har lagts till.
Båda klasserna returnerar SQL med mallen:
%(frame_type)s BETWEEN %(start)s AND %(end)s
Med argumentet exclusion
kan du utesluta rader (CURRENT_ROW
), grupper (GROUP
) och band (TIES
) från fönsterramarna i databaser som stöds:
%(frame_type)s BETWEEN %(start)s AND %(end)s EXCLUDE %(exclusion)s
Ramar begränsar de rader som används för att beräkna resultatet. De flyttas från en viss startpunkt till en viss angiven slutpunkt. Ramar kan användas med eller utan partitioner, men det är ofta en bra idé att ange en ordning för fönstret för att säkerställa ett deterministiskt resultat. I en ram är en peer i en ram en rad med ett likvärdigt värde, eller alla rader om det inte finns någon ordningsbestämmelse.
Standardstartpunkten för en ram är UNBOUNDED PRECEDING
, vilket är den första raden i partitionen. Slutpunkten inkluderas alltid explicit i den SQL som genereras av ORM och är som standard UNBOUNDED FOLLOWING
. Standardramen omfattar alla rader från partitionen till den sista raden i uppsättningen.
De accepterade värdena för argumenten start
och end
är None
, ett heltal eller noll. Ett negativt heltal för start
resulterar i N PRECEDING
, medan None
ger UNBOUNDED PRECEDING
. I läget ROWS
kan ett positivt heltal användas för start
, vilket resulterar i N FOLLOWING
. Positiva heltal accepteras för slut
och resulterar i N FOLLOWING
. I läget ROWS
kan ett negativt heltal användas för end
vilket resulterar i N PRECEDING
. För både start
och end
kommer noll att returnera CURRENT ROW
.
Det finns en skillnad i vad CURRENT ROW
inkluderar. När det anges i läget ROWS
börjar eller slutar ramen med den aktuella raden. När den anges i läget RANGE
startar eller slutar ramen vid den första eller sista peer enligt orderklausulen. Således utvärderar RANGE CURRENT ROW
uttrycket för rader som har samma värde som specificeras av ordningsföljden. Eftersom mallen innehåller både start
- och slut
-punkter kan detta uttryckas med:
ValueRange(start=0, end=0)
Om en films ”jämlikar” beskrivs som filmer som släppts av samma studio i samma genre under samma år, kommenterar detta RowRange
-exempel varje film med det genomsnittliga betyget för filmens två föregående och två efterföljande jämlikar:
>>> from django.db.models import Avg, F, RowRange, Window
>>> Movie.objects.annotate(
... avg_rating=Window(
... expression=Avg("rating"),
... partition_by=[F("studio"), F("genre")],
... order_by="released__year",
... frame=RowRange(start=-2, end=2),
... ),
... )
Om databasen har stöd för det kan du ange start- och slutpunkter baserat på värden för ett uttryck i partitionen. Om fältet released
i modellen Movie
lagrar utgivningsmånaden för varje film, anger detta exempel på ValueRange
för varje film det genomsnittliga betyget för andra filmer som släppts mellan tolv månader före och tolv månader efter varje film:
>>> from django.db.models import Avg, F, ValueRange, Window
>>> Movie.objects.annotate(
... avg_rating=Window(
... expression=Avg("rating"),
... partition_by=[F("studio"), F("genre")],
... order_by="released__year",
... frame=ValueRange(start=-12, end=12),
... ),
... )
Stöd för positivt heltal start
och negativt heltal slut
har lagts till för RowRange
.
Teknisk information¶
Nedan hittar du tekniska implementeringsdetaljer som kan vara användbara för biblioteksförfattare. Det tekniska API:et och exemplen nedan hjälper till att skapa generiska frågeuttryck som kan utöka den inbyggda funktionaliteten som Django tillhandahåller.
Uttryck API¶
Frågeuttryck implementerar :ref:query expression API <query-expression>
, men exponerar också ett antal extra metoder och attribut som listas nedan. Alla frågeuttryck måste ärva från Expression()
eller en relevant underklass.
När ett frågeuttryck omsluter ett annat uttryck är det ansvarigt för att anropa lämpliga metoder för det omslutna uttrycket.
- class Expression[source]¶
- allowed_default¶
Talar om för Django att detta uttryck kan användas i
Field.db_default
. Standardvärdet ärFalse
.
- constraint_validation_compatible¶
- New in Django 5.1.
Talar om för Django att detta uttryck kan användas under en begränsningsvalidering. Uttryck med
constraint_validation_compatible
inställt påFalse
får endast ha ett källuttryck. Standardvärdet ärTrue
.
- contains_aggregate¶
Talar om för Django att detta uttryck innehåller ett aggregat och att en
GROUP BY
-klausul måste läggas till i frågan.
- contains_over_clause¶
Talar om för Django att detta uttryck innehåller ett
Window
-uttryck. Det används till exempel för att inte tillåta fönsterfunktionsuttryck i frågor som modifierar data.
- filterable¶
Talar om för Django att detta uttryck kan refereras till i
QuerySet.filter()
. Standardvärdet ärTrue
.
- window_compatible¶
Talar om för Django att detta uttryck kan användas som källuttryck i
Window
. Standardvärdet ärFalse
.
- empty_result_set_value¶
Talar om för Django vilket värde som ska returneras när uttrycket används för att tillämpa en funktion över en tom resultatmängd. Standardvärdet är
NotImplemented
vilket tvingar uttrycket att beräknas i databasen.
- set_returning¶
- New in Django 5.2.
Talar om för Django att detta uttryck innehåller en set-returneringsfunktion, vilket tvingar fram utvärdering av underfrågor. Det används t.ex. för att tillåta vissa Postgres set-returning-funktioner (t.ex.
JSONB_PATH_QUERY
,UNNEST
, etc.) att hoppa över optimering och utvärderas korrekt när annotationer skapar rader själva. Standardvärde ärFalse
.
- allows_composite_expressions¶
- New in Django 5.2.
Talar om för Django att detta uttryck tillåter sammansatta uttryck, till exempel för att stödja :ref:
sammansatta primärnycklar <cpk-and-database-functions>
. Standardvärdet ärFalse
.
- resolve_expression(query=None, allow_joins=True, reuse=None, summarize=False, for_save=False)¶
Ger möjlighet att göra förbehandling eller validering av uttrycket innan det läggs till i frågan.
resolve_expression()
måste också anropas på alla nästlade uttryck. Encopy()
avself
bör returneras med alla nödvändiga transformationer.query
är implementeringen av backend-frågan.allow_joins
är en boolean som tillåter eller förnekar användning av joins i frågan.reuse
är en uppsättning återanvändbara joins för scenarier med flera joins.`summarize
är en boolean som, när den ärTrue
, signalerar att den fråga som beräknas är en terminal aggregerad fråga.for_save
är en boolean som, när den ärTrue
, signalerar att den fråga som körs utför en skapelse eller uppdatering.
- get_source_expressions()¶
Returnerar en ordnad lista med inre uttryck. Till exempel:
>>> Sum(F("foo")).get_source_expressions() [F('foo')]
- set_source_expressions(expressions)¶
Tar emot en lista med uttryck och lagrar dem så att
get_source_expressions()
kan returnera dem.
- relabeled_clone(change_map)¶
Returnerar en klon (kopia) av
self
, med eventuella kolumnaliaser ommärkta. Kolumnaliaser byter namn när underförfrågningar skapas.relabeled_clone()
bör också anropas på alla nästlade uttryck och tilldelas klonen.change_map
är en ordlista som mappar gamla alias till nya alias.Exempel:
def relabeled_clone(self, change_map): clone = copy.copy(self) clone.expression = self.expression.relabeled_clone(change_map) return clone
- convert_value(value, expression, connection)¶
En krok som gör det möjligt för uttrycket att tvinga
värde
till en mer lämplig typ.uttryck
är detsamma somsjälv
.
- get_group_by_cols()¶
Ansvarig för att returnera listan över kolumner som refereras av detta uttryck.
get_group_by_cols()
bör anropas på alla nästlade uttryck. i synnerhetF()
-objekt innehåller en referens till en kolumn.
- asc(nulls_first=None, nulls_last=None)¶
Returnerar det uttryck som är klart att sorteras i stigande ordning.
nulls_first
ochnulls_last
definierar hur null-värden sorteras. Se Använda F() för att sortera nollvärden för exempel på användning.
- desc(nulls_first=None, nulls_last=None)¶
Returnerar det uttryck som är klart att sorteras i fallande ordning.
nulls_first
ochnulls_last
definierar hur null-värden sorteras. Se Använda F() för att sortera nollvärden för exempel på användning.
- reverse_ordering()¶
Returnerar
self
med alla ändringar som krävs för att vända sorteringsordningen i ettorder_by
-anrop. Som ett exempel, ett uttryck som implementerarNULLS LAST
skulle ändra sitt värde tillNULLS FIRST
. Modifieringar krävs endast för uttryck som implementerar sorteringsordning somOrderBy
. Denna metod anropas närreverse()
anropas på en queryset.
Skriva dina egna frågeuttryck¶
Du kan skriva dina egna query expression-klasser som använder och kan integreras med andra query expressions. Låt oss gå igenom ett exempel genom att skriva en implementation av SQL-funktionen COALESCE
, utan att använda de inbyggda Func()-uttrycken.
SQL-funktionen COALESCE
definieras som att den tar en lista med kolumner eller värden. Den returnerar den första kolumnen eller värdet som inte är NULL
.
Vi börjar med att definiera den mall som ska användas för SQL-generering och en __init__()
-metod för att ställa in några attribut:
import copy
from django.db.models import Expression
class Coalesce(Expression):
template = "COALESCE( %(expressions)s )"
def __init__(self, expressions, output_field):
super().__init__(output_field=output_field)
if len(expressions) < 2:
raise ValueError("expressions must have at least 2 elements")
for expression in expressions:
if not hasattr(expression, "resolve_expression"):
raise TypeError("%r is not an Expression" % expression)
self.expressions = expressions
Vi gör en del grundläggande validering av parametrarna, inklusive att kräva minst 2 kolumner eller värden och säkerställa att de är uttryck. Vi kräver :ref:``output_field<output-field>` här så att Django vet vilken typ av modellfält som ska tilldelas det slutliga resultatet.
Nu implementerar vi förbehandling och validering. Eftersom vi inte har någon egen validering vid denna tidpunkt delegerar vi till de nästlade uttrycken:
def resolve_expression(
self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False
):
c = self.copy()
c.is_summary = summarize
for pos, expression in enumerate(self.expressions):
c.expressions[pos] = expression.resolve_expression(
query, allow_joins, reuse, summarize, for_save
)
return c
Därefter skriver vi den metod som ansvarar för att generera SQL:
def as_sql(self, compiler, connection, template=None):
sql_expressions, sql_params = [], []
for expression in self.expressions:
sql, params = compiler.compile(expression)
sql_expressions.append(sql)
sql_params.extend(params)
template = template or self.template
data = {"expressions": ",".join(sql_expressions)}
return template % data, sql_params
def as_oracle(self, compiler, connection):
"""
Example of vendor specific handling (Oracle in this case).
Let's make the function name lowercase.
"""
return self.as_sql(compiler, connection, template="coalesce( %(expressions)s )")
metoderna as_sql()
kan stödja anpassade nyckelordsargument, vilket gör att metoderna as_vendorname()
kan åsidosätta data som används för att generera SQL-strängen. Att använda as_sql()
nyckelordsargument för anpassning är att föredra framför att mutera self
inom as_vendorname()
-metoder eftersom det senare kan leda till fel när det körs på olika databasbackends. Om din klass förlitar sig på klassattribut för att definiera data, överväg att tillåta åsidosättningar i din as_sql()
-metod.
Vi genererar SQL för vart och ett av uttrycken
med hjälp av metoden compiler.compile()
och sammanfogar resultatet med kommatecken. Sedan fylls mallen i med våra data och SQL och parametrar returneras.
Vi har också definierat en anpassad implementering som är specifik för Oracle-backend. Funktionen as_oracle()
kommer att anropas istället för as_sql()
om Oracle-backend används.
Slutligen implementerar vi resten av de metoder som gör att vårt frågeuttryck kan fungera bra tillsammans med andra frågeuttryck:
def get_source_expressions(self):
return self.expressions
def set_source_expressions(self, expressions):
self.expressions = expressions
Låt oss se hur det fungerar:
>>> from django.db.models import F, Value, CharField
>>> qs = Company.objects.annotate(
... tagline=Coalesce(
... [F("motto"), F("ticker_name"), F("description"), Value("No Tagline")],
... output_field=CharField(),
... )
... )
>>> for c in qs:
... print("%s: %s" % (c.name, c.tagline))
...
Google: Do No Evil
Apple: AAPL
Yahoo: Internet Company
Django Software Foundation: No Tagline
Undvika SQL-injektion¶
Eftersom nyckelordsargumenten i en Func
för __init__()
(**extra
) och as_sql()
(**extra_context
) interpoleras in i SQL-strängen i stället för att skickas som frågeparametrar (där databasdrivrutinen skulle undkomma dem), får de inte innehålla otillförlitlig användarinmatning.
Om till exempel substring
är användartillhandahållen, är denna funktion sårbar för SQL-injektion:
from django.db.models import Func
class Position(Func):
function = "POSITION"
template = "%(function)s('%(substring)s' in %(expressions)s)"
def __init__(self, expression, substring):
# substring=substring is an SQL injection vulnerability!
super().__init__(expression, substring=substring)
Denna funktion genererar en SQL-sträng utan några parametrar. Eftersom substring
skickas till super().__init__()
som ett nyckelordsargument, interpoleras det till SQL-strängen innan frågan skickas till databasen.
Här är en korrigerad omskrivning:
class Position(Func):
function = "POSITION"
arg_joiner = " IN "
def __init__(self, expression, substring):
super().__init__(substring, expression)
Om substring
istället skickas som ett positionellt argument kommer det att skickas som en parameter i databasfrågan.
Lägga till stöd för databasbackends från tredje part¶
Om du använder en databasbackend som använder en annan SQL-syntax för en viss funktion kan du lägga till stöd för den genom att apa till en ny metod till funktionens klass.
Låt oss säga att vi skriver en backend för Microsofts SQL Server som använder SQL LEN
istället för LENGTH
för funktionen Length
. Vi kommer att apa patch en ny metod som heter as_sqlserver()
på klassen Length
:
from django.db.models.functions import Length
def sqlserver_length(self, compiler, connection):
return self.as_sql(compiler, connection, function="LEN")
Length.as_sqlserver = sqlserver_length
Du kan också anpassa SQL genom att använda parametern template
i as_sql()
.
Vi använder as_sqlserver()
eftersom django.db.connection.vendor
returnerar sqlserver
för backend.
Tredjeparts backends kan registrera sina funktioner i toppnivån __init__.py
-filen i backend-paketet eller i en toppnivå expressions.py
-fil (eller paket) som importeras från toppnivån __init__.py
.
För användarprojekt som vill patcha den backend som de använder bör den här koden finnas i en AppConfig.ready()
-metod.