Zadanie 9: Zaawansowane zapytania i manipulacja danymi w SQL

Cel zadania

Głównym celem tego zadania jest ugruntowanie i znaczne rozszerzenie praktycznych umiejętności studenta w posługiwaniu się językiem SQL. Pracując na w pełni znormalizowanym, relacyjnym schemacie bazy danych `biblioteka`, student przejdzie od prostych zapytań do konstruowania złożonych, wielotabelowych kwerend, które rozwiązują realne problemy biznesowe. Zadanie to obejmuje pełne spektrum operacji: od zaawansowanego odpytywania danych z wykorzystaniem różnych typów złączeń (`JOIN`), agregacji (`GROUP BY`), filtrowania (`WHERE`, `HAVING`) i sortowania (`ORDER BY`), poprzez operacje manipulacji danymi (CRUD: `INSERT`, `UPDATE`, `DELETE`), aż po modyfikację samej struktury bazy danych w locie (`ALTER TABLE`). Dodatkowo, wprowadzone zostaną bardziej zaawansowane koncepcje, takie jak tworzenie widoków (`VIEW`) i indeksów (`INDEX`) w celu optymalizacji. Po ukończeniu tego zadania, student powinien czuć się swobodnie, tworząc złożone zapytania SQL, które efektywnie wydobywają i przetwarzają informacje z bazy danych.

Scenariusz

Student, teraz już w roli dewelopera aplikacji bibliotecznej, otrzymuje serię zadań od swojego przełożonego, które wymagają od niego biegłości w SQL. Pierwsze zadanie to stworzenie raportu pokazującego listę wszystkich aktualnie wypożyczonych książek wraz z danymi czytelnika i datą wypożyczenia. Student wie, że te informacje są rozproszone w trzech tabelach, więc konstruuje zapytanie `SELECT` z dwoma złączeniami `JOIN`, elegancko łącząc dane z tabel `wypozyczenia`, `czytelnicy` i `ksiazki`. Następnie, otrzymuje prośbę o wygenerowanie listy "najbardziej aktywnych czytelników". To wymaga od niego użycia funkcji agregującej `COUNT()` do policzenia wypożyczeń dla każdego czytelnika, a następnie użycia klauzuli `GROUP BY` do zgrupowania wyników i `ORDER BY ... DESC` do posortowania ich od najlepszego wyniku. Kolejne zadanie jest bardziej operacyjne: bibliotekarz prosi o zaktualizowanie statusu wypożyczenia dla książki, która właśnie została zwrócona. Student używa polecenia `UPDATE`, precyzyjnie identyfikując odpowiedni wiersz w tabeli `wypozyczenia` za pomocą klauzuli `WHERE`. Chwilę później dzwoni czytelnik, który chce usunąć swoje konto. Student próbuje wykonać polecenie `DELETE` na tabeli `czytelnicy`, ale serwer zwraca błąd. Po chwili analizy student orientuje się, że to działanie więzów integralności – nie można usunąć czytelnika, który ma na koncie aktywne wypożyczenia. To dla niego cenna, praktyczna lekcja o sile relacyjnego modelu. W międzyczasie pojawia się nowa potrzeba biznesowa: w systemie brakuje informacji o wydawnictwach książek. Student, bez przerywania pracy aplikacji, wykonuje polecenie `ALTER TABLE ksiazki ADD COLUMN wydawnictwo VARCHAR(150);`, dynamicznie rozszerzając strukturę bazy. Ponieważ zapytanie o aktywne wypożyczenia jest wykonywane bardzo często, student postanawia je zoptymalizować. Tworzy `VIEW` (widok) o nazwie `v_aktywne_wypozyczenia`, który enkapsuluje (zamyka w sobie) złożone zapytanie `JOIN`. Od teraz, aby uzyskać ten raport, wystarczy proste `SELECT * FROM v_aktywne_wypozyczenia`. Na koniec, zauważając, że wyszukiwanie czytelników po nazwisku działa wolno, zakłada `INDEX` na kolumnie `nazwisko` w tabeli `czytelnicy`, co, jak wie, drastycznie przyspieszy przyszłe operacje wyszukiwania.

Wykonanie krok po kroku

  1. Zaawansowane zapytania `SELECT` z `JOIN`

    Podstawą pracy z relacyjną bazą danych jest umiejętność łączenia informacji z wielu tabel. Poniższe zapytanie generuje kompleksowy raport o wypożyczeniach.

    USE biblioteka;
    
    -- Wybieramy imię i nazwisko czytelnika, tytuł książki i datę wypożyczenia.
    -- Używamy aliasów (c, k, w) dla skrócenia zapisu i zwiększenia czytelności.
    SELECT
        c.imie,
        c.nazwisko,
        k.tytul,
        w.data_wypozyczenia
    FROM wypozyczenia AS w
    JOIN czytelnicy AS c ON w.czytelnik_id = c.id
    JOIN ksiazki AS k ON w.ksiazka_id = k.id
    WHERE w.status = 'wypozyczona';
    Wyjaśnienie: Klauzula `JOIN` (a właściwie jej domyślny typ, `INNER JOIN`) zwraca tylko te wiersze, dla których warunek złączenia (`ON ...`) jest prawdziwy w obu tabelach. `WHERE` filtruje już połączone wyniki, pozwalając nam wybrać tylko te wypożyczenia, które mają określony status.
  2. Agregacja danych: `GROUP BY`, `COUNT` i `HAVING`

    Często potrzebujemy nie tylko surowych danych, ale ich podsumowań. Poniższe zapytanie znajduje czytelników, którzy wypożyczyli co najmniej 2 książki.

    SELECT
        c.imie,
        c.nazwisko,
        COUNT(w.id) AS liczba_wypozyczen
    FROM czytelnicy AS c
    JOIN wypozyczenia AS w ON c.id = w.czytelnik_id
    GROUP BY c.id, c.imie, c.nazwisko -- Grupujemy po ID, ale dodajemy imię i nazwisko, aby móc je wyświetlić
    HAVING COUNT(w.id) >= 2
    ORDER BY liczba_wypozyczen DESC;
    Wyjaśnienie: `COUNT(w.id)` liczy liczbę wypożyczeń w każdej grupie. `GROUP BY` tworzy grupy wierszy na podstawie unikalnych wartości w podanych kolumnach (tutaj: dla każdego czytelnika). `HAVING` filtruje te grupy (a nie pojedyncze wiersze jak `WHERE`). `ORDER BY ... DESC` sortuje finalny wynik malejąco.
  3. Manipulacja danymi (CRUD)

    Zobaczmy, jak wyglądają podstawowe operacje modyfikacji danych w praktyce.

    -- CREATE (C): Dodajemy nowego czytelnika.
    INSERT INTO czytelnicy (imie, nazwisko, email) VALUES ('Barbara', 'Kowalska', 'b.kowalska@example.com');
    
    -- READ (R): Odczytujemy dane nowej osoby (zakładając, że dostała ID=3).
    SELECT * FROM czytelnicy WHERE id = 3;
    
    -- UPDATE (U): Aktualizujemy status wypożyczenia o ID=1 na 'zwrocona'.
    UPDATE wypozyczenia
    SET status = 'zwrocona', data_rzeczywistego_zwrotu = CURDATE()
    WHERE id = 1;
    
    -- DELETE (D): Usuwamy książkę o ID=2 (zadziała tylko, jeśli nie jest powiązana z żadnym wypożyczeniem).
    DELETE FROM ksiazki WHERE id = 2;
    Wyjaśnienie: Klauzula `WHERE` jest absolutnie kluczowa w poleceniach `UPDATE` i `DELETE`. Jej pominięcie spowoduje wykonanie operacji na WSZYSTKICH wierszach w tabeli, co może prowadzić do katastrofalnej utraty danych. `CURDATE()` to funkcja MySQL zwracająca bieżącą datę.
  4. Modyfikacja struktury tabeli (`ALTER TABLE`)

    Schemat bazy danych "żyje" i często wymaga modyfikacji w trakcie rozwoju aplikacji. `ALTER TABLE` jest narzędziem do tego służącym.

    -- Dodanie nowej kolumny do tabeli 'ksiazki'.
    ALTER TABLE ksiazki ADD COLUMN liczba_stron INT AFTER tytul;
    
    -- Zmiana typu danych istniejącej kolumny.
    ALTER TABLE czytelnicy MODIFY COLUMN email VARCHAR(254) NOT NULL;
    
    -- Zmiana nazwy kolumny.
    ALTER TABLE pracownicy CHANGE COLUMN email adres_email VARCHAR(150);
    Wyjaśnienie: `ADD COLUMN` dodaje nową kolumnę (opcja `AFTER` pozwala określić jej pozycję). `MODIFY COLUMN` zmienia definicję istniejącej kolumny (np. typ, `NULL`/`NOT NULL`). `CHANGE COLUMN` pozwala na jednoczesną zmianę nazwy i definicji.
  5. Tworzenie widoków (`VIEW`) i indeksów (`INDEX`)

    To dwa ważne narzędzia do optymalizacji i upraszczania pracy z bazą danych.

    -- Tworzymy widok, który ukrywa złożoność zapytania JOIN.
    CREATE VIEW v_pelny_katalog AS
    SELECT
        k.tytul,
        k.rok_wydania,
        a.imie,
        a.nazwisko
    FROM ksiazki k
    JOIN ksiazki_autorzy ka ON k.id = ka.ksiazka_id
    JOIN autorzy a ON ka.autor_id = a.id;
    
    -- Teraz możemy odpytywać widok jak zwykłą tabelę.
    SELECT * FROM v_pelny_katalog WHERE nazwisko = 'Tolkien';
    
    -- Tworzymy indeks na kolumnie 'tytul', aby przyspieszyć wyszukiwanie książek.
    CREATE INDEX idx_tytul ON ksiazki(tytul);
    Wyjaśnienie: Widok (`VIEW`) nie przechowuje fizycznie danych, jest to jedynie zapisane, nazwane zapytanie. Upraszcza on pracę i pozwala na nadanie uprawnień do widoku, a nie do bazowych tabel. Indeks (`INDEX`) to dodatkowa struktura danych, która działa jak skorowidz w książce – pozwala serwerowi na błyskawiczne odnalezienie wierszy na podstawie wartości w indeksowanej kolumnie, drastycznie przyspieszając zapytania `SELECT` z klauzulą `WHERE`.

Podsumowanie i wnioski

Opanowanie języka SQL jest procesem ciągłym, ale to zadanie stanowi solidny fundament, który pozwala na swobodną i efektywną pracę z danymi. Zrozumieliśmy, że siła relacyjnych baz danych tkwi nie w przechowywaniu danych w izolowanych tabelach, ale w możliwości ich dynamicznego łączenia (`JOIN`) w celu tworzenia użytecznych informacji. Nauczyliśmy się, jak wyjść poza proste odpytywanie i wykonywać złożone analizy z użyciem funkcji agregujących i grupowania, co jest podstawą każdego raportowania i analityki biznesowej. Kluczowym wnioskiem jest to, że SQL to język kompletny – pozwala nie tylko na odczyt i modyfikację danych (DML - Data Manipulation Language), ale także na dynamiczne zarządzanie strukturą samej bazy (DDL - Data Definition Language). Wprowadzenie do widoków i indeksów otworzyło nam oczy na kwestie optymalizacji i bezpieczeństwa. Widoki upraszczają złożoność i pozwalają na kontrolę dostępu, podczas gdy indeksy są najważniejszym narzędziem w walce o wydajność aplikacji. Biegłość w tworzeniu zapytań, świadomość konsekwencji każdej operacji modyfikującej oraz umiejętność optymalizacji schematu to kompetencje, które definiują profesjonalnego dewelopera i administratora baz danych.