5.4. SQL vs ORM
Bazy danych są niezbędnym składnikiem większości aplikacji. Poniżej
zwięźle pokażemy, w jaki sposób z wykorzystaniem Pythona można je obsługiwać
przy użyciu modułu sqlite3 i języka SQL, jak i systemów ORM
na przykładzie frameworku Peewee.
Informacja
Niniejszy materiał koncentruje się na poglądowym wyeksponowaniu różnic w kodowaniu, komentarz ograniczono do minimum. Dokładne wyjaśnienia poszczególnych instrukcji znajdziesz w materiale SQL oraz System ORM Peewee i System ORM SQLAlchemy.
5.4.1. Połączenie z bazą
Na początku pliku sql_raw.py umieszczamy kod, który importuje moduł do obsługi bazy SQLite3
i przygotowuje obiekt kursora, który posłuży nam do wydawania poleceń SQL:
1import sqlite3
2
3# utworzenie połączenia z bazą przechowywaną w pamięci RAM
4con = sqlite3.connect(':memory:')
5# dostęp do kolumn przez indeksy i przez nazwy pól
6con.row_factory = sqlite3.Row
7# utworzenie obiektu kursora
8cur = con.cursor()
9
System ORM Peewee inicjujemy w pliku orm_pw.py tworząc klasę bazową, która zapewni połączenie z bazą:
1from peewee import SqliteDatabase, Model
2from peewee import CharField, ForeignKeyField
3
4# tworzymy instancję klasy Database do obsługi bazy przechowywanej w pamięci RAM
5baza = SqliteDatabase(':memory:')
6
7# klasa bazowa dla modeli
8class Base(Model):
9 class Meta:
10 database = baza
11
12
Informacja
Parametr :memory: powoduje utworzenie bazy danych w pamięci operacyjnej,
która istnieje tylko w czasie wykonywania programu. Aby utworzyć trwałą bazę,
zastąp omawiany parametr nazwę pliku, np. baza.db.
5.4.2. Model bazy
Dane w bazie zorganizowane są w tabelach połączonych relacjami.
Aby utworzyć tabele klasa i uczen powiązane relacją jeden-do-wielu,
musimy wydać następujące polecenia SQL:
10# tworzenie tabel
11cur.executescript("""
12 DROP TABLE IF EXISTS klasa;
13 CREATE TABLE IF NOT EXISTS klasa (
14 id INTEGER PRIMARY KEY,
15 nazwa varchar(250) NOT NULL,
16 profil varchar(250) DEFAULT ''
17 );
18 DROP TABLE IF EXISTS uczen;
19 CREATE TABLE IF NOT EXISTS uczen (
20 id INTEGER PRIMARY KEY,
21 imie varchar(250) NOT NULL,
22 nazwisko varchar(250) NOT NULL,
23 klasa_id INTEGER NOT NULL,
24 FOREIGN KEY(klasa_id) REFERENCES klasa(id)
25 )""")
26
Wydawanie poleceń SQL-a wymaga znajomości składni tego języka. Systemy ORM, chociaż pozwalają na wykonywanie zapytań SQL, oferują manipulowanie obiektami, a nie tabelami. W powyższym kodzie zamiast tabel definiujemy więc modele, tj. klasy reprezentujące klasy i uczniów. Na podstawie właściwości modeli system ORM utworzy tabele i odpowiednie pola, tj. wygeneruje i wykona odpowiednie zapytania SQL.
13# klasy Klasa i Uczen opisują rekordy tabel "klasa" i "uczen" oraz relacje między nimi
14class Klasa(Base):
15 nazwa = CharField(null=False)
16 profil = CharField(default='')
17
18
19class Uczen(Base):
20 imie = CharField(null=False)
21 nazwisko = CharField(null=False)
22 klasa = ForeignKeyField(Klasa, related_name='uczniowie')
23
24
25baza.connect() # nawiązujemy połączenie z bazą
26baza.create_tables([Klasa, Uczen]) # tworzymy tabele
27
5.4.2.1. Ćwiczenie 1
Utwórz za pomocą powyższych skryptów bazy w plikach o nazwach sql_raw.db oraz
orm_pw.db. Następnie otwórz te bazy w interpreterze Sqlite i wykonaj
podane niżej polecenia. Porównaj struktury utworzonych tabel.
sqlite> .tables
sqlite> .schema
5.4.3. Dodawanie danych
Chcemy wstawić do naszych tabel dane dwóch klas oraz trzech uczniów. Korzystając z języka SQL użyjemy następujących poleceń:
27# dodajemy dwie klasy
28cur.execute('INSERT INTO klasa VALUES(NULL,?,?)', ('1A', 'matematyczny'))
29klasa1_id = cur.lastrowid
30cur.execute('INSERT INTO klasa VALUES(NULL,?,?)', ('1B', 'humanistyczny'))
31klasa2_id = cur.lastrowid
32
33# lista uczniów, których dane zapisane są w krotkach
34uczniowie = [
35 ('Tomasz', 'Nowak', klasa1_id),
36 ('Jan', 'Kos', klasa2_id),
37 ('Piotr', 'Kowalski', klasa2_id)
38]
39# dodajemy dane wielu uczniów
40cur.executemany('INSERT INTO uczen VALUES(NULL,?,?,?)', uczniowie)
41con.commit()
42
Metoda kursora execute() pozwala wykonać pojedyncze zapytanie SQL. Metoda executemany()
wykonuje jedno zapytanie dla danych podanych w liście krotek. Dane zapisane krotkach odpowiadają rekordom w tabeli.
Znaki zapytania (zamienniki) w poleceniach SQL są zastępowane przez kolejne parametry przekazywane w krotkach.
W systemie ORM tworzymy obiekty (instancje) na podstawie klas modelu. Właściwości obiektów przekazujemy metodzie
create(), która od razu tworzy rekord w bazie, lub konstruktorowi, po którym wywołujemy metodę save().
28# dodajemy dwie klasy
29klasa1 = Klasa.create(nazwa='1A', profil='matematyczny')
30klasa2 = Klasa(nazwa='1B', profil='humanistyczny')
31klasa2.save()
32
33# lista uczniów, których dane zapisane są w słownikach
34uczniowie = [
35 {'imie': 'Tomasz', 'nazwisko': 'Nowak', 'klasa': klasa1},
36 {'imie': 'Jan', 'nazwisko': 'Kos', 'klasa': klasa2},
37 {'imie': 'Piotr', 'nazwisko': 'Kowalski', 'klasa': klasa2}
38]
39# dodajemy dane wielu uczniów
40Uczen.insert_many(uczniowie).execute()
41
Do dawania wielu rekordów używamy metody insert_many() modelu, która przyjmuje jako argument
listę słowników opisujących właściwości kolejnych obiektów.
5.4.4. Pobieranie danych
Pobieranie danych (czyli kwerenda) wymaga polecenia SELECT języka SQL.
Aby wyświetlić dane wszystkich uczniów zapisane w bazie użyjemy kodu:
43def wypisz_listę_uczniow():
44 """ Odczytujemy i wypisujemy dane uczniów, w tym klasę"""
45 cur.execute("SELECT count(*) FROM uczen")
46 if (cur.fetchone()[0]):
47 print('Uczniowie:')
48 cur.execute(
49 """
50 SELECT uczen.id, imie, nazwisko, nazwa FROM uczen
51 INNER JOIN klasa
52 WHERE uczen.klasa_id=klasa.id
53 """)
54 uczniowie = cur.fetchall()
55 for uczen in uczniowie:
56 print(uczen['id'], uczen['imie'], uczen['nazwisko'], uczen['nazwa'])
57 print()
58
59wypisz_listę_uczniow()
60
Funkcja wypisz_liste_uczniow() na początku wykonuje zapytanie o liczbę rekordów w tabeli uczen.
Jeżeli w tabeli są jakieś rekordy (if (cur.fetchone()[0]):), pobieramy informacje o wszystkich uczniach
wykonując zapytanie SQL i wypisujemy je.
W systemie ORM korzystamy z metod modelu select().count(), aby sprawdzić, czy w tabeli uczen są jakieś dane.
Jeżeli tak, wykorzystujemy metody select().join(), aby pobrać dane uczniów i klas, do których należą.
W pętli for odczytujemy zwrócone obiekty i wypisujemy ich właściwości korzystając z notacji z kropką:
42def wypisz_listę_uczniow():
43 """ Odczytujemy i wypisujemy dane uczniów, w tym klasę"""
44 if Uczen.select().count():
45 print('Uczniowie:')
46 uczniowie = Uczen.select().join(Klasa)
47 for uczen in uczniowie:
48 print(uczen.id, uczen.imie, uczen.nazwisko, uczen.klasa.nazwa)
49 print()
50 else:
51 print('Brak uczniów w bazie!')
52
53wypisz_listę_uczniow()
54
5.4.5. Modyfikacja danych
Edycja danych zapisanych już w bazie to kolejna częsta operacja. Jeżeli chcemy przepisać ucznia do innej klasy,
w przypadku SQL-a musimy pobrać identyfikator ucznia. To wymaga wykonania zapytania,
np. SELECT id FROM uczen WHERE nazwisko="Nowak" i pobrania wartości pierwszego zwróconego obiektu:
uczen_id = cur.fetchone()[0].
Podobnie uzyskujemy identyfikator klasy.
Na koniec używamy zapytania aktualizującego: UPDATE uczen SET klasa_id=? WHERE id=?,
któremu podajemy w krotce identyfikator nowej klasy i ucznia.
61# przepisanie wybranego ucznia do innej klasy
62cur.execute('SELECT id FROM uczen WHERE nazwisko=?', ('Nowak',))
63uczen_id = cur.fetchone()[0]
64cur.execute('SELECT id FROM klasa WHERE nazwa=?', ('1B',))
65klasa_id = cur.fetchone()[0]
66cur.execute('UPDATE uczen SET klasa_id=? WHERE id=?', (klasa_id, uczen_id))
67wypisz_listę_uczniow()
68
W systemie ORM tworzymy obiekty reprezentujące ucznia i nową klasę. Następnie zmieniamy klasę ucznia,
przypisując odpowiadającej właściwości obiekt nowej klasy (uczen.klasa = nowa_klasa).
55# zmiana klasy ucznia o identyfikatorze 2
56uczen = Uczen.select().join(Klasa).where(Uczen.id == 2).get()
57nowa_klasa = Klasa.select().where(Klasa.nazwa == '1A').get()
58print('Zmieniam klasę ucznia:', uczen.imie, uczen.nazwisko, uczen.klasa.nazwa)
59uczen.klasa = nowa_klasa
60uczen.save() # zapisanie zmian w bazie
61wypisz_listę_uczniow()
62
5.4.6. Usuwanie danych
Usuwanie danych w języku SQL wymaga użycia klauzuli DELETE, która na podstawie podanych kryteriów wybiera i usuwa
wybrany z tabeli rekord, np.:
69# usunięcie ucznia o identyfikatorze 1
70cur.execute('DELETE FROM uczen WHERE id=?', (1,))
71wypisz_listę_uczniow()
72
73con.close()
W systemach ORM zazwyczaj na początku odczytujemy interesujący nas obiekt za pomocą metody select(),
a następnie usuwamy za pomocą metody delete_instance().
63# usunięcie ucznia o identyfikatorze 1
64uczen = Uczen.select().where(Uczen.id == 1).get()
65print('Usuwam ucznia:', uczen.id, uczen.imie, uczen.nazwisko)
66uczen.delete_instance()
67wypisz_listę_uczniow()
68
69baza.close()
Informacja
Po wykonaniu wszystkich założonych operacji na danych połączenie z bazą należy
zamknąć, zwalniając w ten sposób zarezerwowane zasoby. W przypadku modułu sqlite3
wywołujemy polecenie con.close(), w Peewee baza.close().
5.4.7. Podsumowanie
Bazę danych można obsługiwać za pomocą języka SQL na niskim poziomie. Zyskujemy wtedy na szybkości działania, ale wymaga to znajomości SQL-a, a kod może być mniej czytelny, a jego rozwijanie trudniejsze. W bardziej rozbudowanych projektach zazwyczaj używamy systemów ORM, które pozwalają zarządzać danymi nie w formie tabel, pól i rekordów, ale w postaci obiektów reprezentujących modele, tj. klasy. Takie podejście odpowiada obiektowemu wzorcowi projektowania aplikacji.
Dodatkową zaletą systemów ORM jest większa odporność na błędy i ewentualne ataki na dane w bazie.
Systemy ORM można łatwo integrować z programami desktopowymi i frameworkami przeznaczonymi do tworzenia aplikacji sieciowych. Wśród tych ostatnich znajdziemy również takie, w których system ORM jest podstawowym składnikiem, np. Django.
5.4.8. Zadania
Wykonaj scenariusze aplikacji System ORM Peewee i/lub System ORM SQLAlchemy, aby poznać przykłady użycia systemów ORM.
Wykonaj scenariusz aplikacji internetowej Czat (cz. 1), zbudowanej z użyciem frameworku Django, korzystającego z własnego modelu ORM.
Materiały Python 101
udostępniane przez
Centrum Edukacji Obywatelskiej na licencji
Creative Commons Uznanie autorstwa-Na tych samych warunkach 4.0 Międzynarodowa.
- Utworzony:
2026-05-30 o 19:12 w Sphinx 7.3.7
- Autorzy: