Szybki quiz – przypomnienie poprzedniej lekcji
Temat poprzedniej lekcji: programowanie obiektowe.
Pytanie 1. Co zostanie wyświetlone?
class Gracz:
def __init__(self, nick, hp=100):
self.nick = nick
self.hp = hp
g = Gracz("Mage", hp=80)
print(g.nick, g.hp)
Odpowiedź
Odpowiedź: Mage 80 – wartość podana przy tworzeniu obiektu nadpisuje wartość domyślną hp=100.
Pytanie 2. Znajdź błąd. Dlaczego program zgłasza AttributeError?
class Kot:
def __init__(self, imie):
imie = imie
def info(self):
print(self.imie)
Kot("Mruczek").info()
Odpowiedź
Odpowiedź: w konstruktorze imie = imie zmienia tylko zmienną lokalną. Atrybut obiektu trzeba zapisać jako self.imie = imie.
Pytanie 3. Dlaczego ten kod zgłosi AttributeError i jak go naprawić?
class Pojazd:
def __init__(self, marka):
self.marka = marka
class Samochod(Pojazd):
def __init__(self, marka, model):
self.model = model
auto = Samochod("Toyota", "Corolla")
print(auto.marka)
Odpowiedź
Odpowiedź: konstruktor klasy Samochod nie wywołuje konstruktora klasy Pojazd, więc atrybut marka nigdy nie powstał. Naprawa: dodać super().__init__(marka) w __init__ klasy Samochod.
Pytanie 4. Co zostanie wyświetlone?
class Ksiazka:
def __init__(self, tytul):
self.tytul = tytul
def __str__(self):
return f"Książka: {self.tytul}"
k = Ksiazka("Wiedźmin")
print(k)
Odpowiedź
Odpowiedź: Książka: Wiedźmin – print() wywołuje metodę __str__. Bez niej zobaczysz tylko typ obiektu i jego adres w pamięci.
Pytanie 5. Co się stanie po uruchomieniu tego kodu?
class Konto:
def __init__(self):
self.__saldo = 100
k = Konto()
print(k.__saldo)
Odpowiedź
Odpowiedź: AttributeError. Atrybut z dwoma podkreślnikami jest prywatny (Python zmienia jego nazwę – name mangling), więc nie można odczytać go spoza klasy.
Pytanie 6. Co się stanie po uruchomieniu tego kodu?
from abc import ABC, abstractmethod
class Zwierze(ABC):
@abstractmethod
def dzwiek(self):
pass
z = Zwierze()
Odpowiedź
Odpowiedź: TypeError – z klasy abstrakcyjnej nie można tworzyć obiektów. Obiekt można utworzyć dopiero z klasy potomnej, która implementuje dzwiek().
Czym jest SQLite3?
- SQLite3 to lekka, wbudowana relacyjna baza danych SQL
- Nie wymaga osobnego serwera – cała baza to jeden plik
- Python ma wbudowany moduł sqlite3 – nie trzeba nic instalować
- Idealna do nauki, prototypów i małych/średnich aplikacji
SQLite3 to lekka, wbudowana relacyjna baza danych SQL
Nie wymaga osobnego serwera – cała baza to jeden plik
Python ma wbudowany moduł sqlite3 – nie trzeba nic instalować
Idealna do nauki, prototypów i małych/średnich aplikacji
Podstawowe pojęcia:
Połączenie (Connection) – łącze z plikiem bazy danych
Kursor (Cursor) – obiekt do wykonywania poleceń SQL
Commit – zatwierdzenie zmian w bazie
Rollback – cofnięcie niezatwierdzonych zmian
Analogia ze świata rzeczywistego:
Wyobraź sobie, że:
- Baza danych = Biblioteka pełna książek
- Connection (połączenie) = Karta biblioteczna (pozwala Ci wejść do biblioteki)
- Cursor (kursor) = Bibliotekarz, który wykonuje dla Ciebie konkretne zadania
- Dlaczego potrzebujemy kursora?
Nie możesz bezpośrednio rozmawiać z bazą danych! Potrzebujesz „pośrednika”, który:
- Zaniesie Twoje polecenie do bazy danych
- Wykona operację (znajdzie dane, doda coś, usunie)
- Przyniesie Ci wyniki
Tym pośrednikiem jest właśnie KURSOR.
import sqlite3
# KROK 1: Otwierasz drzwi do biblioteki (połączenie)
conn = sqlite3.connect('mojabaza.db')
# Teraz jesteś w bibliotece, ale jeszcze nic nie możesz zrobić
# KROK 2: Wzywasz bibliotekarza (tworzysz kursor)
cursor = conn.cursor()
# Teraz masz "pomocnika", któremu możesz wydawać polecenia
# KROK 3: Wydajesz polecenie bibliotekarzowi
cursor.execute('SELECT * FROM ksiazki WHERE autor LIKE ?', ('%Tolkien%',))
# Bibliotekarz idzie, szuka książek Tolkiena
# KROK 4: Bibliotekarz przynosi Ci wyniki
wyniki = cursor.fetchall()
print(wyniki)
# KROK 5: Wychodzisz z biblioteki
conn.close()
PRZYKŁADY PRAKTYCZNE
Przykład 1: Podstawowe operacje CRUD
tworzymy plik o nazwie crud_basics.py
import sqlite3
# Połączenie z bazą (tworzy plik jeśli nie istnieje)
conn = sqlite3.connect('mojabaza.db')
cursor = conn.cursor()
# CREATE - Tworzenie tabeli
cursor.execute('''
CREATE TABLE IF NOT EXISTS pracownicy (
id INTEGER PRIMARY KEY AUTOINCREMENT,
imie TEXT NOT NULL,
nazwisko TEXT NOT NULL,
wiek INTEGER,
wynagrodzenie REAL
)
''')
# INSERT - Wstawianie pojedynczego rekordu
cursor.execute('''
INSERT INTO pracownicy (imie, nazwisko, wiek, wynagrodzenie)
VALUES (?, ?, ?, ?)
''', ('Jan', 'Kowalski', 35, 5500.50))
# INSERT - Wstawianie wielu rekordów
dane = [
('Anna', 'Nowak', 28, 6200.00),
('Kacper', 'Malinowski', 22, 4100.75),
('Maria', 'Wiśniewska', 45, 7800.00)
]
cursor.executemany('''
INSERT INTO pracownicy (imie, nazwisko, wiek, wynagrodzenie)
VALUES (?, ?, ?, ?)
''', dane)
# Zatwierdzenie zmian
conn.commit()
# SELECT - Pobieranie wszystkich danych
cursor.execute('SELECT * FROM pracownicy')
wyniki = cursor.fetchall()
print("Wszyscy pracownicy:")
for wiersz in wyniki:
print(wiersz)
# SELECT - Pobieranie z warunkiem
cursor.execute('SELECT imie, nazwisko FROM pracownicy WHERE wiek > ?', (30,))
starsi = cursor.fetchall()
print("\nPracownicy powyżej 30 lat:")
for osoba in starsi:
print(f"{osoba[0]} {osoba[1]}")
# UPDATE - Aktualizacja danych
cursor.execute('''
UPDATE pracownicy
SET wynagrodzenie = wynagrodzenie * 1.1
WHERE wiek < 30
''')
conn.commit()
# DELETE - Usuwanie danych
cursor.execute('DELETE FROM pracownicy WHERE id = ?', (1,))
conn.commit()
# Zamknięcie połączenia
conn.close()
Przykład 2: Context Manager (zalecane podejście)
tworzymy plik o nazwie context_manager.py
import sqlite3
# Automatyczny commit (lub rollback przy błędzie) na końcu bloku with
with sqlite3.connect('sklep.db') as conn:
cursor = conn.cursor()
# Tworzenie tabeli produktów
cursor.execute('''
CREATE TABLE IF NOT EXISTS produkty (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nazwa TEXT NOT NULL,
cena REAL NOT NULL,
ilosc INTEGER DEFAULT 0
)
''')
# Dane są automatycznie commitowane przy wyjściu z bloku 'with'
cursor.execute('INSERT INTO produkty (nazwa, cena, ilosc) VALUES (?, ?, ?)',
('Laptop', 3500.00, 5))
# Pobieranie z użyciem row_factory dla dostępu jak do słownika
conn.row_factory = sqlite3.Row
cursor = conn.cursor()
cursor.execute('SELECT * FROM produkty')
for row in cursor:
print(f"Produkt: {row['nazwa']}, Cena: {row['cena']} zł")
Przykład 3: Zaawansowane zapytania
tworzymy plik o nazwie advanced_queries.py
import sqlite3
with sqlite3.connect('firma.db') as conn:
cursor = conn.cursor()
# Tworzenie dwóch powiązanych tabel
cursor.execute('''
CREATE TABLE IF NOT EXISTS dzialy (
id INTEGER PRIMARY KEY,
nazwa TEXT NOT NULL
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS pracownicy (
id INTEGER PRIMARY KEY,
imie TEXT NOT NULL,
nazwisko TEXT NOT NULL,
dzial_id INTEGER,
FOREIGN KEY (dzial_id) REFERENCES dzialy(id)
)
''')
# Wstawienie działów
dzialy = [(1, 'IT'), (2, 'HR'), (3, 'Sprzedaż')]
cursor.executemany('INSERT OR IGNORE INTO dzialy VALUES (?, ?)', dzialy)
# Wstawienie pracowników
pracownicy = [
('Jan', 'Kowalski', 1),
('Anna', 'Nowak', 2),
('Piotr', 'Wiśniewski', 1)
]
cursor.executemany('INSERT INTO pracownicy (imie, nazwisko, dzial_id) VALUES (?, ?, ?)', pracownicy)
# JOIN - łączenie tabel
cursor.execute('''
SELECT p.imie, p.nazwisko, d.nazwa
FROM pracownicy p
JOIN dzialy d ON p.dzial_id = d.id
WHERE d.nazwa = 'IT'
''')
print("Pracownicy działu IT:")
for row in cursor.fetchall():
print(f"{row[0]} {row[1]} - {row[2]}")
# GROUP BY - agregacja
cursor.execute('''
SELECT d.nazwa, COUNT(p.id) as liczba_pracownikow
FROM dzialy d
LEFT JOIN pracownicy p ON d.id = p.dzial_id
GROUP BY d.nazwa
''')
print("\nLiczba pracowników w działach:")
for row in cursor.fetchall():
print(f"{row[0]}: {row[1]}")
Uwaga: SQLite domyślnie nie sprawdza kluczy obcych. Aby FOREIGN KEY faktycznie blokował wpisy z nieistniejącym id, po połączeniu wykonaj:
conn.execute("PRAGMA foreign_keys = ON")
Przykład 4: Obsługa błędów
tworzymy plik o nazwie error_handling.py
import sqlite3
try:
with sqlite3.connect('test.db') as conn:
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY,
email TEXT UNIQUE NOT NULL
)
''')
# Próba wstawienia duplikatu
cursor.execute('INSERT INTO users (email) VALUES (?)', ('jan@example.com',))
cursor.execute('INSERT INTO users (email) VALUES (?)', ('jan@example.com',))
except sqlite3.IntegrityError as e:
print(f"Błąd integralności danych: {e}")
except sqlite3.OperationalError as e:
print(f"Błąd operacyjny: {e}")
except sqlite3.Error as e:
print(f"Błąd bazy danych: {e}")
Typy wyjątków:
| sqlite3.Warning | Ostrzeżenie |
| sqlite3.Error | Bazowy wyjątek dla wszystkich błędów |
| sqlite3.InterfaceError | Błąd interfejsu bazy |
| sqlite3.DatabaseError | Błąd związany z bazą |
| sqlite3.DataError | Błąd danych (np. dzielenie przez zero) |
| sqlite3.OperationalError | Błąd operacyjny (np. brak tabeli) |
| sqlite3.IntegrityError | Naruszenie integralności (np. UNIQUE) |
| sqlite3.InternalError | Błąd wewnętrzny SQLite |
| sqlite3.ProgrammingError | Błąd programowania (np. zły SQL) |
| sqlite3.NotSupportedError | Nieobsługiwana operacja |
Przykład 5:
#https://pypi.org/project/sqlite-database/
#shift + ctrl + p -> wpisujemy sqlite i wybieramy naszą bazę (test.db) - po lewej na dole pokazuje się dostęp do bazy
#tworzymy połączenie z bazą
import sqlite3
conn = sqlite3.connect('test1.db') #tworzymy połączenie z bazą danych przechowywaną w pliku na dysku (test.db, nazwa pliku jest dowolona) lub w pamięci, jeśli podamy ':memory:'
conn.row_factory = sqlite3.Row #dostęp do kolumn (pól tabel) nie tylko przez indeksy, ale również przez nazwy
db = conn.cursor() #obiekt kursora do wykonywania operacji na bazie danych
print(db)
#tworzymy tabele
#pojedyncze polecenia SQL-a wykonujemy za pomocą metody .execute()
db.execute("DROP TABLE IF EXISTS klasa;")
db.execute("""
CREATE TABLE IF NOT EXISTS klasa (
id INTEGER PRIMARY KEY ASC,
nazwa varchar(250) NOT NULL,
profil varchar(250) DEFAULT ''
)""")
#wiele instrukcji wykonujemy za pomocą metody .executescript()
db.executescript("""
DROP TABLE IF EXISTS uczen;
CREATE TABLE IF NOT EXISTS uczen (
id INTEGER PRIMARY KEY ASC,
imie varchar(250) NOT NULL,
nazwisko varchar(250) NOT NULL,
klasa_id INTEGER NOT NULL,
FOREIGN KEY(klasa_id) REFERENCES klasa(id)
)""")
#po uruchomieniu kodu baza pojawi się w katalogu
# wstawiamy jeden rekord danych
db.execute('INSERT INTO klasa VALUES(NULL, ?, ?);', ('1A', 'matematyczny'))
db.execute('INSERT INTO klasa VALUES(NULL, ?, ?);', ('1B', 'humanistyczny'))
# INSERT, UPDATE i DELETE zmieniają dane dopiero po conn.commit().
# Bez commit() zmiany przepadną po zamknięciu połączenia.
# Nie zakładaj, że CREATE TABLE zatwierdzi się samo - dobrą praktyką jest zawsze wywołać conn.commit().
conn.commit()
# Wyświetlamy zawartość tabeli klasa
print("Zawartość tabeli 'klasa':")
for row in db.execute('SELECT * FROM klasa'):
print(dict(row)) # wypisujemy jako słownik, żeby było czytelniej
# wykonujemy zapytanie SQL, które pobierze id klasy "1A" z tabeli "klasa".
db.execute('SELECT id FROM klasa WHERE nazwa = ?', ('1A',))
klasa_id = db.fetchone()[0]
# tupla "uczniowie" zawiera tuple z danymi poszczególnych uczniów
uczniowie = (
(None, 'Tomasz', 'Nowak', klasa_id),
(None, 'Jan', 'Kos', klasa_id),
(None, 'Piotr', 'Kowalski', klasa_id)
)
# wstawiamy wiele rekordów
db.executemany('INSERT INTO uczen VALUES(?,?,?,?)', uczniowie)
# pobieranie danych z bazy
def czytajdane():
"""Funkcja pobiera i wyświetla dane z bazy."""
#poniżej potrójny cudzysłów został wykorzystany do zapisania długielo zapytania SQL
db.execute(
"""
SELECT uczen.id,imie,nazwisko,nazwa FROM uczen,klasa
WHERE uczen.klasa_id=klasa.id
""")
uczniowie = db.fetchall()
for uczen in uczniowie:
print(uczen['id'], uczen['imie'], uczen['nazwisko'], uczen['nazwa'])
print()
czytajdane()
# zmiana klasy ucznia o identyfikatorze 2
db.execute('SELECT id FROM klasa WHERE nazwa = ?', ('1B',))
klasa_id = db.fetchone()[0]
db.execute('UPDATE uczen SET klasa_id=? WHERE id=?', (klasa_id, 2))
# usunięcie ucznia o identyfikatorze 3
# db.execute('DELETE FROM uczen WHERE id=?', (3,))
czytajdane()
# zatwierdzamy zmiany w bazie
conn.commit()
# Zamykamy połączenie
conn.close()
ĆWICZENIA
Ćwiczenie 1: Biblioteka książek (Podstawowe)
Utwórz bazę danych biblioteki z tabelą ksiażki zawierającą:
id (PRIMARY KEY)
tytul (TEXT)
autor (TEXT)
rok_wydania (INTEGER)
dostepna (INTEGER – 0 lub 1)
Zadania:
Dodaj 5 książek do tabeli
Wyświetl wszystkie książki wydane po 2000 roku
Oznacz książkę jako wypożyczoną (dostepna = 0)
Policz ile jest dostępnych książek
Ćwiczenie 2: Sklep internetowy (Średniozaawansowane)
Stwórz system zarządzania produktami z bazą o nazwie sklep.db. Utwórz dwie tabele: kategorie i produkty.
Tabela kategorie: id, nazwa
Tabela produkty: id, nazwa, cena, kategoria_id, stan_magazynowy
Utwórz obie tabele z odpowiednimi kluczami obcymi
Zadania:
Dodaj 3 kategorie (Elektronika, Odzież, Książki)
Dodaj po 3 produkty do każdej kategorii
Napisz zapytanie wyświetlające produkty wraz z nazwą kategorii
Oblicz średnią cenę produktów w każdej kategorii
Znajdź najdroższy produkt w bazie
Ćwiczenie 3: System zarządzania studentami z relacjami wiele-do-wielu: (Zaawansowane)
Utwórz bazę o nazwie uczelnia.db z trzema tabelami: studenci, kursy, zapisy (tabela łącząca)
Utwórz wszystkie trzy tabele z odpowiednimi kluczami obcymi i ograniczeniem UNIQUE(student_id, kurs_id)
Zaimplementuj funkcje:
dodaj_studenta(imie, nazwisko, rok_studiow)
dodaj_kurs(nazwa, punkty_ects)
zapisz_na_kurs(student_id, kurs_id, ocena)
wyswietl_kursy_studenta(student_id)
oblicz_srednia_studenta(student_id)
Użyj transakcji do zapisania studenta na wiele kursów jednocześnie
NAJWAŻNIEJSZE ZASADY
1. Bezpieczeństwo – SQL Injection
#ŹLE - podatne na SQL Injection
nazwa = input("Podaj nazwę: ")
cursor.execute(f"SELECT * FROM users WHERE name = '{nazwa}'")
#DOBRZE - używaj parametrów
nazwa = input("Podaj nazwę: ")
cursor.execute("SELECT * FROM users WHERE name = ?", (nazwa,))
2. Zawsze używaj commit()
cursor.execute('INSERT INTO ...')
conn.commit() # BEZ TEGO dane nie zostaną zapisane!
3. Zamykaj połączenia
conn = sqlite3.connect('db.db')
... operacje ...
conn.close()
Uwaga: blok `with sqlite3.connect(…) as conn:` **nie zamyka** połączenia. Zatwierdza zmiany (`commit`), a gdy w bloku wystąpi wyjątek, cofa je (`rollback`). Połączenie zamykasz samodzielnie przez `conn.close()`:
with sqlite3.connect('db.db') as conn:
... operacje ... # commit automatycznie, rollback przy błędzie
conn.close()
PRZYDATNE METODY
| execute(sql, params) | Wykonuje pojedyncze polecenie SQL z opcjonalnymi parametrami |
| executemany(sql, seq_of_parameters) | Wstawia wiele rekordów |
| executescript(sql) | Wykonuje wiele poleceń SQL |
| fetchone() | Pobiera jeden wiersz |
| fetchall() | Pobiera wszystkie wiersze |
| fetchmany(n) | Pobiera n wierszy |
| lastrowid | ID ostatnio wstawionego rekordu |
| rowcount | Liczba zmienionych wierszy |
Dlaczego dwa kroki? (Connection + Cursor)
Dlaczego nie może być tak:
conn = sqlite3.connect('baza.db')
conn.execute('SELECT...') # Czy to nie wystarczy?
Może być! Ale to jest skrót:
#Sposób 1: SKRÓT (Python robi to za Ciebie)
conn.execute('SELECT * FROM users')
# Sposób 2: PEŁNY (tak naprawdę działa)
cursor = conn.cursor()
cursor.execute('SELECT * FROM users')
# Python w skrócie automatycznie tworzy kursor, ale go nie widzisz!
Możesz mieć WIELU BIBLIOTEKARZY naraz (wiele kursorów) i każdy może wykonywać różne zadania, ale wszyscy korzystają z tej samej karty bibliotecznej (połączenia). To pozwala na równoczesną pracę z bazą danych.
conn = sqlite3.connect('baza.db')
# Pierwszy bibliotekarz zajmuje się użytkownikami
cursor1 = conn.cursor()
cursor1.execute('SELECT * FROM users')
# Drugi bibliotekarz zajmuje się zamówieniami
cursor2 = conn.cursor()
cursor2.execute('SELECT * FROM orders')
# Każdy robi swoją robotę niezależnie!
users = cursor1.fetchall()
orders = cursor2.fetchall()
# Kursor "pamięta" wyniki swoich zapytań, więc możesz mieć wielu kursorów, a każdy z nich będzie miał swoje własne wyniki.
cursor = conn.cursor()
cursor.execute('SELECT * FROM users')
# Pobierasz wyniki STOPNIOWO
pierwszy = cursor.fetchone() # Pobiera 1 wiersz
drugi = cursor.fetchone() # Pobiera kolejny wiersz
reszta = cursor.fetchall() # Pobiera wszystkie pozostałe
# Gdybyś użył conn.execute(), nie miałbyś takiej kontroli nad kursorami i wynikami.
Pamiętaj
- pojedyncze polecenia SQL-a wykonujemy za pomocą metody .execute() np.
DROP TABLE IF EXISTS uczen; - wiele instrukcji wykonujemy za pomocą metody .executescript() np.
DROP TABLE IF EXISTS uczen; CREATE TABLE IF NOT EXISTS uczen (...); - dodanie wielu rekordów do tabeli wykonujemy za pomocą metody .executemany()
- Polecenia INSERT, UPDATE, DELETE zmieniają dane w bazie dopiero po jawnym wywołaniu conn.commit(). Bez tego zmiany znikną po zamknięciu połączenia. Polecenie SELECT tylko odczytuje dane i nie wymaga commit().
ROZWIĄZANIA
Rozwiązanie ćwiczenia 1: Biblioteka książek
import sqlite3
with sqlite3.connect('biblioteka.db') as conn:
c = conn.cursor()
# 1. Tworzenie tabeli
c.execute('''
CREATE TABLE IF NOT EXISTS ksiazki (
id INTEGER PRIMARY KEY AUTOINCREMENT,
tytul TEXT NOT NULL,
autor TEXT NOT NULL,
rok_wydania INTEGER,
dostepna INTEGER DEFAULT 1
)
''')
# 2. Dodawanie książek
ksiazki = [
('Władca Pierścieni', 'J.R.R. Tolkien', 1954, 1),
('Harry Potter', 'J.K. Rowling', 1997, 1),
('Wiedźmin', 'Andrzej Sapkowski', 1990, 1),
('Metro 2033', 'Dmitry Glukhovsky', 2005, 1),
('Kod Da Vinci', 'Dan Brown', 2003, 1)
]
c.executemany('INSERT INTO ksiazki (tytul, autor, rok_wydania, dostepna) VALUES (?, ?, ?, ?)', ksiazki)
# 3. Książki po 2000 roku
print("Książki wydane po 2000 roku:")
c.execute('SELECT tytul, autor, rok_wydania FROM ksiazki WHERE rok_wydania > 2000')
for row in c.fetchall():
print(f"- {row[0]} ({row[1]}, {row[2]})")
# 4. Wypożyczenie książki
c.execute('UPDATE ksiazki SET dostepna = 0 WHERE tytul = ?', ('Metro 2033',))
# 5. Liczba dostępnych książek
c.execute('SELECT COUNT(*) FROM ksiazki WHERE dostepna = 1')
liczba = c.fetchone()[0]
print(f"\nDostępnych książek: {liczba}")
Rozwiązanie ćwiczenia 2: Sklep internetowy
import sqlite3
with sqlite3.connect('sklep.db') as conn:
c = conn.cursor()
# 1. Tworzenie tabel
c.execute('''
CREATE TABLE IF NOT EXISTS kategorie (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nazwa TEXT NOT NULL UNIQUE
)
''')
c.execute('''
CREATE TABLE IF NOT EXISTS produkty (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nazwa TEXT NOT NULL,
cena REAL NOT NULL,
kategoria_id INTEGER,
stan_magazynowy INTEGER DEFAULT 0,
FOREIGN KEY (kategoria_id) REFERENCES kategorie(id)
)
''')
# 2. Dodawanie kategorii
kategorie = [('Elektronika',), ('Odzież',), ('Książki',)]
c.executemany('INSERT OR IGNORE INTO kategorie (nazwa) VALUES (?)', kategorie)
# 3. Dodawanie produktów
produkty = [
('Laptop', 3500.00, 1, 10),
('Smartphone', 2200.00, 1, 15),
('Słuchawki', 450.00, 1, 25),
('Koszulka', 89.99, 2, 50),
('Spodnie', 199.99, 2, 30),
('Kurtka', 499.00, 2, 20),
('Python dla każdego', 59.99, 3, 100),
('Clean Code', 89.00, 3, 75),
('Algorytmy', 120.00, 3, 40)
]
c.executemany('INSERT INTO produkty (nazwa, cena, kategoria_id, stan_magazynowy) VALUES (?, ?, ?, ?)', produkty)
# 4. Produkty z kategoriami
print("Produkty z kategoriami:")
c.execute('''
SELECT p.nazwa, p.cena, k.nazwa
FROM produkty p
JOIN kategorie k ON p.kategoria_id = k.id
ORDER BY k.nazwa
''')
for row in c.fetchall():
print(f"{row[0]} ({row[2]}) - {row[1]} zł")
# 5. Średnia cena w kategoriach
print("\nŚrednia cena w kategoriach:")
c.execute('''
SELECT k.nazwa, ROUND(AVG(p.cena), 2) as srednia
FROM kategorie k
JOIN produkty p ON k.id = p.kategoria_id
GROUP BY k.nazwa
''')
for row in c.fetchall():
print(f"{row[0]}: {row[1]} zł")
# 6. Najdroższy produkt
c.execute('SELECT nazwa, cena FROM produkty ORDER BY cena DESC LIMIT 1')
najdrozszy = c.fetchone()
print(f"\nNajdroższy produkt: {najdrozszy[0]} ({najdrozszy[1]} zł)")
Rozwiązanie ćwiczenia 3: System zarządzania studentami
import sqlite3
class SystemStudentow:
def __init__(self, db_name='uczelnia.db'):
# Polaczenie z baza danych
self.conn = sqlite3.connect(db_name)
# Ustawienie row_factory dla dostepu do kolumn jak do slownika
self.conn.row_factory = sqlite3.Row
self._utworz_tabele()
def _utworz_tabele(self):
# Utworzenie kursora
cursor = self.conn.cursor()
# Tworzenie tabeli studenci
cursor.execute('''
CREATE TABLE IF NOT EXISTS studenci (
id INTEGER PRIMARY KEY AUTOINCREMENT,
imie TEXT NOT NULL,
nazwisko TEXT NOT NULL,
rok_studiow INTEGER
)
''')
# Tworzenie tabeli kursy
cursor.execute('''
CREATE TABLE IF NOT EXISTS kursy (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nazwa TEXT NOT NULL,
punkty_ects INTEGER
)
''')
# Tworzenie tabeli zapisy (relacja wiele-do-wielu)
cursor.execute('''
CREATE TABLE IF NOT EXISTS zapisy (
id INTEGER PRIMARY KEY AUTOINCREMENT,
student_id INTEGER,
kurs_id INTEGER,
ocena REAL,
FOREIGN KEY (student_id) REFERENCES studenci(id),
FOREIGN KEY (kurs_id) REFERENCES kursy(id),
UNIQUE(student_id, kurs_id)
)
''')
# Zatwierdzenie zmian
self.conn.commit()
def dodaj_studenta(self, imie, nazwisko, rok_studiow):
cursor = self.conn.cursor()
# Wstawienie studenta
cursor.execute(
'INSERT INTO studenci (imie, nazwisko, rok_studiow) VALUES (?, ?, ?)',
(imie, nazwisko, rok_studiow)
)
self.conn.commit()
# Zwrocenie ID dodanego rekordu
return cursor.lastrowid
def dodaj_kurs(self, nazwa, punkty_ects):
cursor = self.conn.cursor()
# Wstawienie kursu
cursor.execute(
'INSERT INTO kursy (nazwa, punkty_ects) VALUES (?, ?)',
(nazwa, punkty_ects)
)
self.conn.commit()
return cursor.lastrowid
def zapisz_na_kurs(self, student_id, kurs_id, ocena=None):
cursor = self.conn.cursor()
try:
# Wstawienie zapisu studenta na kurs
cursor.execute(
'INSERT INTO zapisy (student_id, kurs_id, ocena) VALUES (?, ?, ?)',
(student_id, kurs_id, ocena)
)
self.conn.commit()
return True
except sqlite3.IntegrityError:
print("Student jest juz zapisany na ten kurs")
return False
def ustaw_ocene(self, student_id, kurs_id, ocena):
cursor = self.conn.cursor()
# Aktualizacja oceny
cursor.execute('''
UPDATE zapisy
SET ocena = ?
WHERE student_id = ? AND kurs_id = ?
''', (ocena, student_id, kurs_id))
# Sprawdzenie czy rekord zostal zaktualizowany
if cursor.rowcount == 0:
print("Student nie jest zapisany na ten kurs")
return False
self.conn.commit()
return True
def wyswietl_kursy_studenta(self, student_id):
cursor = self.conn.cursor()
# Zapytanie JOIN laczace tabele zapisy i kursy
cursor.execute('''
SELECT k.nazwa, k.punkty_ects, z.ocena
FROM zapisy z
JOIN kursy k ON z.kurs_id = k.id
WHERE z.student_id = ?
''', (student_id,))
print(f"\nKursy studenta (ID: {student_id}):")
# Odczyt wszystkich wierszy
for row in cursor.fetchall():
ocena_str = f"{row['ocena']}" if row['ocena'] else "brak"
print(f"- {row['nazwa']} ({row['punkty_ects']} ECTS) - Ocena: {ocena_str}")
def oblicz_srednia_studenta(self, student_id):
cursor = self.conn.cursor()
# Zapytanie z funkcja agregujaca AVG
cursor.execute('''
SELECT AVG(ocena) as srednia
FROM zapisy
WHERE student_id = ? AND ocena IS NOT NULL
''', (student_id,))
# Odczyt jednego wiersza
wynik = cursor.fetchone()
return round(wynik['srednia'], 2) if wynik['srednia'] else 0
def zapisz_na_wiele_kursow(self, student_id, kursy_ids):
try:
cursor = self.conn.cursor()
# Transakcja - wstawienie wielu rekordow
for kurs_id in kursy_ids:
cursor.execute(
'INSERT INTO zapisy (student_id, kurs_id) VALUES (?, ?)',
(student_id, kurs_id)
)
self.conn.commit()
print(f"Student zapisany na {len(kursy_ids)} kursow")
except sqlite3.Error as e:
# Wycofanie zmian w przypadku bledu
self.conn.rollback()
print(f"Blad: {e}")
def __del__(self):
# Zamkniecie polaczenia
self.conn.close()
if __name__ == '__main__':
import os
# Usuniecie starej bazy danych
if os.path.exists('uczelnia.db'):
os.remove('uczelnia.db')
system = SystemStudentow()
# Dodawanie studentow
student1 = system.dodaj_studenta('Jan', 'Kowalski', 2)
student2 = system.dodaj_studenta('Anna', 'Nowak', 1)
student3 = system.dodaj_studenta('Piotr', 'Wisniewski', 3)
# Dodawanie kursow
kurs1 = system.dodaj_kurs('Bazy Danych', 6)
kurs2 = system.dodaj_kurs('Algorytmy', 5)
kurs3 = system.dodaj_kurs('Sieci Komputerowe', 4)
# Zapisanie studenta 1 na wszystkie kursy
system.zapisz_na_wiele_kursow(student1, [kurs1, kurs2, kurs3])
# Ustawienie ocen dla studenta 1
system.ustaw_ocene(student1, kurs1, 4.5)
system.ustaw_ocene(student1, kurs2, 5.0)
system.ustaw_ocene(student1, kurs3, 3.5)
# Zapisanie studenta 2 na 2 kursy z ocenami
system.zapisz_na_kurs(student2, kurs1, 4.0)
system.zapisz_na_kurs(student2, kurs2, 5.0)
# Zapisanie studenta 3 na 1 kurs bez oceny
system.zapisz_na_kurs(student3, kurs1)
# Wyswietlanie kursow studentow
system.wyswietl_kursy_studenta(student1)
srednia1 = system.oblicz_srednia_studenta(student1)
print(f"\nSrednia ocen: {srednia1}")
system.wyswietl_kursy_studenta(student2)
srednia2 = system.oblicz_srednia_studenta(student2)
print(f"\nSrednia ocen: {srednia2}")
system.wyswietl_kursy_studenta(student3)
srednia3 = system.oblicz_srednia_studenta(student3)
print(f"\nSrednia ocen: {srednia3}")
PODSUMOWANIE
Jak działa praca z bazą w Pythonie – zawsze ta sama kolejność: połączenie → kursor → polecenie SQL → odbiór wyniku (fetch…) → commit() → close()
- SQLite to relacyjna baza w jednym pliku. Moduł
sqlite3jest wbudowany w Pythona, nic nie instalujesz.':memory:'tworzy bazę tylko w pamięci. - Connection to połączenie z plikiem bazy. Cursor wykonuje polecenia SQL i przechowuje ich wyniki. Na jednym połączeniu możesz mieć wiele kursorów.
conn.execute(...)to skrót, który sam tworzy kursor. - Dane do zapytania przekazujesz przez
?i krotkę parametrów. Nigdy nie wstawiaj ich do SQL-a przez f-string (SQL Injection). - Krotka z jednym elementem wymaga przecinka:
(nazwa,). Zapis(nazwa)to zwykły napis i da błąd. INSERT,UPDATE,DELETEzmieniają dane tylko w trakcie transakcji. Bezconn.commit()zmiany przepadną po zamknięciu połączenia.rollback()cofa zmiany niezatwierdzone.with sqlite3.connect(...) as conn:zatwierdza zmiany (commit) po poprawnym wyjściu z bloku i cofa je (rollback), jeśli w bloku wystąpił wyjątek. Samowithnie zamyka połączenia – zamykasz je przezconn.close().- Wyniki zapytania to krotki, nawet gdy pytasz o jedną kolumnę:
[('Ola',), ('Ewa',)].fetchone()zwraca jeden wiersz alboNone, gdy wierszy już nie ma.fetchall()zwraca listę wszystkich pozostałych wierszy. conn.row_factory = sqlite3.Rowpozwala czytać kolumny po nazwie:row['imie'],dict(row).- Metody:
execute(jedno polecenie),executemany(jedno polecenie dla wielu zestawów danych),executescript(wiele poleceń SQL naraz). lastrowidto id ostatnio wstawionego rekordu.rowcountto liczba zmienionych wierszy (0= nic nie zmieniono).- Relacje między tabelami:
FOREIGN KEY ... REFERENCES, łączenie tabel przezJOIN ... ON.LEFT JOINzachowuje też wiersze bez dopasowania (np. dział bez pracowników).GROUP BYzCOUNT,AVGgrupuje wyniki. - Błędy bazy to wyjątki z
sqlite3.sqlite3.Errorjest bazowy dla wszystkich. Konkretne wyjątki (IntegrityError,OperationalError) wpisuj wexceptprzed ogólnymsqlite3.Error. - Typowe pułapki: brak
commit(),("Ola")zamiast("Ola",), f-string w SQL,UPDATE/DELETEbezWHERE(zmienia lub usuwa wszystkie wiersze), wielokrotne uruchamianie programu dubluje rekordy (dlatego przykład 5 zaczyna się odDROP TABLE IF EXISTS).
Minimum, które powinno wejść w pamięć
Jeżeli masz zapamiętać tylko kilka rzeczy z tej lekcji, zapamiętaj te:
- Kolejność pracy:
connect()→cursor()→execute()→fetch…→commit()→close(). - Dane do SQL-a zawsze przez
?i krotkę:execute("... WHERE x = ?", (x,)). Nigdy przez f-string. - Jeden parametr w krotce wymaga przecinka:
(x,). - Po
INSERT,UPDATE,DELETEwywołajcommit(), inaczej zmiany przepadną. with sqlite3.connect(...) as connzatwierdza lub cofa zmiany, ale nie zamyka połączenia.- Wynik zapytania to krotki:
fetchone()zwraca jeden wiersz lubNone,fetchall()listę krotek. UPDATEiDELETEbezWHEREdziałają na całej tabeli.- Wyjątki: najpierw konkretne (
IntegrityError,OperationalError), na końcu ogólnysqlite3.Error.
Ściągawka – najważniejsze polecenia i składnia
Schemat programu
import sqlite3
conn = sqlite3.connect('baza.db') # ':memory:' = baza w pamięci
conn.row_factory = sqlite3.Row # opcjonalnie: row['kolumna']
c = conn.cursor()
c.execute('POLECENIE SQL', (param1, param2))
conn.commit() # po INSERT / UPDATE / DELETE
conn.close()
Metody kursora i połączenia
| Co chcesz zrobić | Jak |
|---|---|
| jedno polecenie | c.execute(sql, (a, b)) |
| wiele rekordów naraz | c.executemany(sql, lista_krotek) |
| kilka poleceń SQL naraz | c.executescript(sql) |
| jeden wiersz / wszystkie / n wierszy | c.fetchone() / c.fetchall() / c.fetchmany(n) |
| id wstawionego rekordu | c.lastrowid |
| ile wierszy zmieniono | c.rowcount |
| zatwierdzić / cofnąć | conn.commit() / conn.rollback() |
Najczęściej używany SQL
| Operacja | Składnia |
|---|---|
| tabela | CREATE TABLE IF NOT EXISTS t (id INTEGER PRIMARY KEY AUTOINCREMENT, nazwa TEXT NOT NULL, cena REAL, kat_id INTEGER, FOREIGN KEY (kat_id) REFERENCES kategorie(id)) |
| usunięcie tabeli | DROP TABLE IF EXISTS t |
| wstawianie | INSERT INTO t (nazwa, cena) VALUES (?, ?) |
| odczyt | SELECT a, b FROM t WHERE a > ? ORDER BY b DESC LIMIT 1 |
| zmiana | UPDATE t SET cena = ? WHERE id = ? |
| usuwanie | DELETE FROM t WHERE id = ? |
| łączenie tabel | SELECT ... FROM a JOIN b ON a.b_id = b.id |
| grupowanie | SELECT k.nazwa, COUNT(p.id) FROM k LEFT JOIN p ON k.id = p.k_id GROUP BY k.nazwa |
| agregaty | COUNT(*), AVG(kolumna), ROUND(wartosc, 2) |
Ograniczenia kolumn: PRIMARY KEY, NOT NULL, UNIQUE, DEFAULT wartość.
Obsługa błędów
try:
...
except sqlite3.IntegrityError as e: # np. naruszenie UNIQUE
...
except sqlite3.OperationalError as e: # np. brak tabeli
...
except sqlite3.Error as e: # każdy inny błąd bazy
...
Szybka diagnoza
| Objaw | Najczęstsza przyczyna |
|---|---|
| dane znikają po zakończeniu programu | brak conn.commit() |
ProgrammingError: Incorrect number of bindings supplied | ("Ola") zamiast ("Ola",) albo zła liczba ? |
OperationalError: no such table | tabela nie została utworzona albo literówka w nazwie |
IntegrityError | duplikat w kolumnie UNIQUE lub brak wartości w kolumnie NOT NULL |
Quiz – sprawdź się
Do pytań 4–6 i 11 przyjmij, że wykonano już ten kod:
import sqlite3
conn = sqlite3.connect(':memory:')
c = conn.cursor()
c.execute("CREATE TABLE o (id INTEGER PRIMARY KEY, nazwa TEXT, ocena INTEGER)")
c.executemany("INSERT INTO o (nazwa, ocena) VALUES (?, ?)",
[("Ola", 5), ("Jan", 3), ("Ewa", 4)])
Pytanie 1. Dopasuj metodę do opisu.
connect()cursor()commit()rollback()
A. cofa niezatwierdzone zmiany B. zatwierdza zmiany w bazie C. otwiera połączenie z plikiem bazy D. tworzy obiekt, który wykonuje polecenia SQL i przechowuje wyniki
Pytanie 2. Co dzieje się z danymi i z połączeniem po wyjściu z bloku with? Czy trzeba jeszcze wywołać conn.close()?
with sqlite3.connect('sklep.db') as conn:
conn.execute("INSERT INTO produkty (nazwa, cena) VALUES ('Mysz', 59.9)")
Pytanie 3. Która wersja jest poprawna i dlaczego? Co zwróci wersja A, jeśli użytkownik wpisze ' OR '1'='1?
# wersja A
nazwa = input("Podaj nazwę: ")
cursor.execute(f"SELECT * FROM users WHERE name = '{nazwa}'")
# wersja B
nazwa = input("Podaj nazwę: ")
cursor.execute("SELECT * FROM users WHERE name = ?", (nazwa,))
Pytanie 4. Co zostanie wyświetlone?
c.execute("SELECT nazwa FROM o WHERE ocena >= 4")
print(c.fetchall())
Pytanie 5. Co zostanie wyświetlone w dwóch kolejnych liniach?
c.execute("SELECT COUNT(*) FROM o")
wynik = c.fetchone()
print(wynik)
print(wynik[0])
Pytanie 6. Znajdź błąd. Dlaczego ten kod zgłasza ProgrammingError i jak go poprawić?
c.execute("SELECT * FROM o WHERE nazwa = ?", ("Ola"))
Pytanie 7. Znajdź błąd. Program uruchomiono, ale po ponownym otwarciu pliku szkola.db nie ma w nim rekordu „Kasia”. Dlaczego? (Tabela o istniała już w pliku.)
conn = sqlite3.connect('szkola.db')
c = conn.cursor()
c.execute("INSERT INTO o (nazwa, ocena) VALUES (?, ?)", ("Kasia", 5))
conn.close()
Pytanie 8. Uzupełnij kod, aby wstawić dwa rekordy jednym wywołaniem i zapisać je w bazie.
dane = [("Kasia", 5), ("Adam", 2)]
c.________("INSERT INTO o (nazwa, ocena) VALUES (?, ?)", dane)
conn.________()
Pytanie 9. Co zostanie wyświetlone po wykonaniu tego kodu? Dlaczego?
c.execute("CREATE TABLE u (id INTEGER PRIMARY KEY, email TEXT UNIQUE NOT NULL)")
try:
c.execute("INSERT INTO u (email) VALUES (?)", ("a@x.pl",))
c.execute("INSERT INTO u (email) VALUES (?)", ("a@x.pl",))
except sqlite3.IntegrityError:
print("duplikat")
except sqlite3.Error:
print("inny błąd")
Pytanie 10. Uzupełnij zapytanie tak, aby zwracało imiona uczniów i nazwy ich klas, ale tylko dla klasy 1A. Tabela uczen ma kolumnę klasa_id, a tabela klasa ma kolumny id i nazwa.
c.execute("""
SELECT u.imie, k.nazwa
FROM uczen u
JOIN klasa k ON u.klasa_id = k.________
WHERE k.nazwa = ?
""", ('1A',))
Pytanie 11. Zadanie praktyczne. Napisz kod, który dla tabeli o wyświetli nazwy i oceny uczniów z oceną niższą niż wartość zmiennej prog (prog = 4), każdego w osobnej linii w formacie Jan: 3. Użyj zapytania z parametrem.
Odpowiedzi
1. 1 – C, 2 – D, 3 – B, 4 – A.
2. Dane zostaną zatwierdzone (commit). Gdyby w bloku wystąpił wyjątek, zmiany zostałyby cofnięte (rollback). Blok with nie zamyka połączenia, więc conn.close() nadal jest potrzebne.
3. Poprawna jest wersja B. Parametr ? przekazuje dane jako wartość, a nie jako część polecenia SQL. W wersji A zapytanie przyjmie postać WHERE name = '' OR '1'='1', czyli warunek jest zawsze prawdziwy i zwrócone zostaną wszystkie wiersze tabeli (SQL Injection).
4. [('Ola',), ('Ewa',)] – wynik to lista krotek, nawet gdy zapytanie dotyczy jednej kolumny.
5. (3,) oraz 3. fetchone() zwraca krotkę, więc liczbę wyciągasz przez wynik[0].
6. ("Ola") to zwykły napis, nie krotka. Python traktuje go jak trzy znaki (O, l, a) i zgłasza błąd liczby parametrów. Poprawnie: ("Ola",).
7. Brak conn.commit(). INSERT nie został zatwierdzony, więc po close() zmiana przepadła. Przed conn.close() trzeba dodać conn.commit().
8. executemany, commit
9. Wyświetli się duplikat. Drugi INSERT łamie ograniczenie UNIQUE, co zgłasza sqlite3.IntegrityError, a ten wyjątek jest obsłużony w pierwszym except. Konkretny wyjątek musi stać przed ogólnym sqlite3.Error.
10. id, czyli JOIN klasa k ON u.klasa_id = k.id. Zapytanie zwróci imiona uczniów z klasy 1A razem z nazwą klasy.
11. Przykładowe rozwiązanie:
prog = 4
c.execute("SELECT nazwa, ocena FROM o WHERE ocena < ?", (prog,))
for nazwa, ocena in c.fetchall():
print(f"{nazwa}: {ocena}")
Wynik dla przykładowych danych: Jan: 3.