16. 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.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'))

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