Vybírání dat z tabulky
Naše databáze by měla obsahovat z minula několik záznamů, které si odtamtud zkusíme získat. To se dělá pomocí kolekce objektů QuerySet
. Výstupem této třídy je instance objektu, která se chová podobně jako seznam. Metody výběru dat se volají přes atribut objects
a dají se kombinovat. K nejpoužívanějším patří:
all
: vybere všechny záznamy z tabulkyfilter
: omezuje výběr podle jednoho nebo více parametrůexclude
: vybere doplněk toho, co by vybrala metodafilter
order_by
: řazení výběru podle určitého atributu
Nejjednodušší je výběr všech záznamů:
>>> from hrajeme_si.video_store.models import Store >>> Store.objects.all() [<Store: Videostore Praha 1>, <Store: Videostore Praha 2>, <Store: Videostore Brno>]
Omezování záznamů pomocí metod filter
a exclude
se převádí na SQL klauzuli WHERE
, respektive WHERE NOT
. Při zadání více parametrů se jednotlivá omezení řetězí pomocí logické spojky AND
. Za názvem atributu může následovat dvojité podtržítko spolu s nějakým specifikátorem omezení, který magicky zadává klíčové argumenty WHERE
klauzule. Ukážeme si to na několika komentovaných příkladech (mřížka #
uvozuje komentář):
>>> Store.objects.filter(id=1) # záznam s id = 1 (odpovídá id__exact=1) [<Store: Videostore Praha 1>] >>> Store.objects.filter(id__gt=1) # záznamy s id > 1 [<Store: Videostore Praha 2>, <Store: Videostore Brno>] >>> Store.objects.filter(id__gte=1) # záznamy s id ≥ 1 [<Store: Videostore Praha 1>, <Store: Videostore Praha 2>, <Store: Videostore Brno>] >>> Store.objects.filter(city='Praha') # město odpovídá přesně řetězci 'Praha' [] >>> Store.objects.filter(city__startswith='Praha') # město začíná řetězcem 'Praha' [<Store: Videostore Praha 1>, <Store: Videostore Praha 2>] >>> Store.objects.filter(city__istartswith='prAhA') # to stejné, jenom ignorujeme velikost písmen [<Store: Videostore Praha 1>, <Store: Videostore Praha 2>] >>> Store.objects.filter(address__icontains='náměstí') # pobočka sídlí na náměstí [<Store: Videostore Brno>] >>> Store.objects.filter(email='') # záznamy s nevyplněným e-mailem [<Store: Videostore Praha 2>, <Store: Videostore Brno>] >>> Store.objects.exclude(email='') # záznamy s vyplněným e-mailem [<Store: Videostore Praha 1>]
Záznamy lze řadit podle jednoho či více atributů, výběr se implicitně řadí od prvního záznamu po poslední, stejně jako u SQL klauzule ORDER BY
. Když potřebujeme položky seřadit obráceně, napíšeme znak mínus před název atributu. Není potřeba nejprve volat metodu all
, stačí zavolat order_by
rovnou:
>>> Store.objects.order_by('id') # řazení dle sloupce id vzestupně [<Store: Videostore Praha 1>, <Store: Videostore Praha 2>, <Store: Videostore Brno>] >>> Store.objects.order_by('-id') # řazení dle sloupce id sestupně [<Store: Videostore Brno>, <Store: Videostore Praha 2>, <Store: Videostore Praha 1>] >>> Store.objects.order_by('?') # náhodné řazení [<Store: Videostore Praha 1>, <Store: Videostore Brno>, <Store: Videostore Praha 2>]
Kombinování metod výběru vypadá například takhle:
>>> Store.objects.exclude(city='Brno').filter(id__gt=0, id__lt=10).order_by('-city') [<Store: Videostore Praha 2>, <Store: Videostore Praha 1>]
Tento příkaz vybere všechny pobočky, které nejsou v Brně, mají id
větší než nula a menší než deset a seřadí je podle města, sestupně. Je dobré vědět, že se QuerySet
chová líně, tedy že se SQL dotaz zavolá, až když data v aplikaci skutečně potřebujeme. Můžeme tak SQL dotaz postupně sestavovat, aniž by došlo k výraznému zpomalení.
Limitování a zjišťování počtů záznamů
Když už jsme si ukázali ekvivalenty klauzulí WHERE
a ORDER BY
, přidáme k tomu ještě klauzuli LIMIT
. Ta má stejnou syntaxi jako indexování seznamů, protože se instance objektu QuerySet
chová podobně jako pythonový seznam. Pomocí ní vybíráme podmnožinu z našich záznamů. Nejlépe to ilustrujeme na několika příkladech:
>>> Store.objects.order_by('id')[0] # první záznam v tabulce <Store: Videostore Praha 1> >>> Store.objects.order_by('-id')[0] # poslední záznam v tabulce <Store: Videostore Brno> >>> Store.objects.order_by('id')[:2] # první dva záznamy z tabulky [<Store: Videostore Praha 1>, <Store: Videostore Praha 2>] >>> Store.objects.order_by('-id')[:2] # poslední dva záznamy z tabulky [<Store: Videostore Brno>, <Store: Videostore Praha 2>] >>> Store.objects.exclude(city='Brno')[1] # druhý pobočka, která není v Brně <Store: Videostore Praha 2>
Stejně jako u seznamů je potřeba si dát pozor na indexování mimo rozsah, protože pak je vyhozena výjimka IndexError
, kterou je potřeba odchytit a zpracovat. To se naučíme v dalších dílech. Vyhození této výjimky nastává například při vybrání nulového počtu záznamů:
>>> Store.objects.filter(city='Ostrava')[0] Traceback (most recent call last): File "<console>", line 1, in <module> File "/usr/lib/python2.5/site-packages/django/db/models/query.py", line 159, in __getitem__ return list(qs)[0] IndexError: list index out of range
A jak zjistit počet záznamů? Stačí použít zabudovanou funkci len
:
>>> len(Store.objects.all()) 3 >>> len(Store.objects.filter(city='Brno')) 1 >>> len(Store.objects.filter(city='Ostrava'))
Upravování a mazání dat
Záznamy, které z databáze vybereme, jsou opět instancí našeho modelu Store
. Ten jsme minule použili k přidání záznamů do tabulky. Podobně se záznamy dají upravovat a mazat. Vyzkoušíme si přestěhovat brněnskou pobočku:
>>> store_brno = Store.objects.filter(id=3)[0] >>> store_brno.address = 'Kounicova 15' >>> store_brno.postal_code = '611 00' >>> store_brno.save()
První příkaz nám vybere záznam s číslem 3, tedy naší brněnskou pobočku, a přiřadí ji do proměnné store_brno
. Kratšího zápisu lze docílit i pomocí metody get, která se ovšem chová trochu jinak. Na dalších třech řádcích můžeme vidět, že došlo k upravení atributů a uložení záznamu do tabulky. Podobně funguje i mazání, jenom místo metody save
zavoláme metodu delete
:
>>> store_praha2 = Store.objects.filter(id=2)[0] >>> store_praha2.delete()
Tabulka teď obsahuje jenom dva záznamy, protože jsme druhou pražskou pobočku zrušili:
>>> Store.objects.all() [<Store: Videostore Praha 1>, <Store: Videostore Brno>]
Ladění SQL dotazů
Často je užitečné vědět, jaký SQL dotaz ORM transformace v Djangu vyprodukovala a jak dlouho jeho vykonání trvalo. Tyto informace je průběžně ukládají do seznamu queries
z objektu django.db.connection
. Takto vypadá jednoduché použití:
>>> from django.db import connection >>> Store.objects.exclude(id=1).order_by('city')[0] <Store: Videostore Brno> >>> connection.queries[-1] {'time': '0.038', 'sql': u'SELECT "video_store_store"."id", "video_store_store"."store", "video_store_store"."address", "video_store_store"."city", "video_store_store"."postal_code", "video_store_store"."email", "video_store_store"."description" FROM "video_store_store" WHERE NOT ("video_store_store"."id" = 1 ) ORDER BY "video_store_store"."city" ASC LIMIT 1'}
Tento dotaz trval 38 setin sekundy a byl při něm použit uvedený SQL kód.
Vztahy mezi modely
Rozšíříme si funkcionalitu naší aplikace přidáním modelu představujícího položku v katalogu filmů videopůjčovny. Otevřeme si soubor video_store/models.py
a přidáme na jeho konec definice modelů. Tentokrát to bude o něco složitější:
FORMAT_CHOICES = ( (0, 'Ostatní'), (1, 'VHS'), (2, 'DVD'), (3, 'Blu-ray'), ) class Film(models.Model): store = models.ManyToManyField('Store') name_czech = models.CharField('Český název filmu', max_length=100) name_original = models.CharField('Původní název filmu', max_length=100, blank=True) year = models.PositiveIntegerField('Rok natočení') director = models.ForeignKey('Director', verbose_name='Režisér', null=True, blank=True) price = models.DecimalField('Cena za půjčení', max_digits=4, decimal_places=2, default=20) format = models.PositiveSmallIntegerField('Formát', choices=FORMAT_CHOICES) description = models.TextField('Popis', blank=True) added = models.DateTimeField('Čas přidání', auto_now_add=True) def __unicode__(self): return self.name_czech class Meta: ordering = ['name_czech'] verbose_name = 'film' verbose_name_plural = 'filmy' class Director(models.Model): name = models.CharField('Jméno a příjmení', max_length=100) def __unicode__(self): return self.name class Meta: verbose_name = 'režisér' verbose_name_plural = 'režiséři'
N-tice FORMAT_CHOICES
je pro výběr formátu filmu v modelu Film
. Ten je provázan přes cizí klíč relací 1:N (one-to-many) s pomocným modelem Director
, který obsahuje režiséry. Vytváříme ho proto, abychom mohli jednoduše zobrazovat související filmy od toho stejného režiséra. Pro zjednodušení může být u každého filmu uveden nanejvýše jeden režisér. Tabulky Store
a Film
jsou propojené relací M:N, často nazývanou many-to-many. Každý film se může vyskytovat ve více videopůjčovnách a každá videopůjčovna zpravidla obsahuje více než jeden film. Pro lepší pochopení vztahů je dobré prostudovat přiložený diagram.
Z atributů tady máme několik nových polí: PositiveIntegerField
a PositiveSmallIntegerField
se používají pro ukládání přirozených čísel, liší se jenom v maximální hodnotě, která se do nich dá uložit. Pole DecimalField
je určeno pro desetinná čísla (je potřeba nastavit maximální počet číslic a počet desetinných míst). Další pole DateTimeField
slouží k ukládání času a data, použitý parametr auto_now_add
nám usnadňuje práci, protože nastaví automaticky aktuální čas při založení. Parametr default
nastavuje výchozí hodnotu atributu price
. Za zmínku stojí i meta vlastnost ordering
, která nastavuje výchozí řazení při výběru dat.
A na závěr si opět synchronizujeme modely s databází, abychom je měli nachystané pro další pokračování:
$ python manage.py syncdb
Creating table video_store_film
Creating table video_store_director
Installing index for video_store.Film model
Související odkazy
- Modely a tutoriál na Djangoproject.com
- Tutoriál na Djangoproject.cz
- Pátá kapitola v The Definitive Guide to Django
- Ukázkový příklad ke stažení.
Příště si z našich databázových modelů necháme vygenerovat rozhraní pro jednoduchou správu projektu.
Přehled komentářů