17. Python – bazy danych – sqlite


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
 # Automatyczne zarządzanie połączeniem
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]}")

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.WarningOstrzeżenie
sqlite3.ErrorBazowy wyjątek dla wszystkich błędów
sqlite3.InterfaceErrorBłąd interfejsu bazy
sqlite3.DatabaseErrorBłąd związany z bazą
sqlite3.DataErrorBłąd danych (np. dzielenie przez zero)
sqlite3.OperationalErrorBłąd operacyjny (np. brak tabeli)
sqlite3.IntegrityErrorNaruszenie integralności (np. UNIQUE)
sqlite3.InternalErrorBłąd wewnętrzny SQLite
sqlite3.ProgrammingErrorBłąd programowania (np. zły SQL)
sqlite3.NotSupportedErrorNieobsł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'))

#Polecenia CREATE TABLE (a także DROP TABLE, ALTER TABLE) są traktowane przez SQLite jako DDL (Data Definition Language) i są automatycznie zatwierdzane — SQLite implicitnie wykonuje COMMIT po każdej takiej operacji.Ale już INSERT, UPDATE, DELETE tego nie robią automatycznie: Te operacje to DML (Data Manipulation Language) i wymagają jawnego conn.commit(), jeśli chcesz, żeby zmiany zostały zapisane na stałe.
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 2res
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

#Sposób 1: Manualnie
conn = sqlite3.connect('db.db')
... operacje ...
conn.close()
 
#Sposób 2: Context manager (zalecane)
with sqlite3.connect('db.db') as conn:
    ... operacje ...
    Automatyczne zamknięcie

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
lastrowidID ostatnio wstawionego rekordu
rowcountLiczba 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 CREATE TABLE (a także DROP TABLE, ALTER TABLE) oraz SELECT są traktowane przez SQLite jako DDL (Data Definition Language) i są automatycznie zatwierdzane — SQLite implicitnie wykonuje COMMIT po każdej takiej operacji.Ale już INSERT, UPDATE, DELETE tego nie robią automatycznie: Te operacje to DML (Data Manipulation Language) i wymagają jawnego conn.commit(), jeśli chcesz, żeby zmiany zostały zapisane na stałe.

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}")