1/60MariaDB i SQL – praktyczny przewodnik

Od uruchomienia serwera po zaawansowane zapytania – kompletny kurs SQL

Zakładamy zerowy poziom wiedzy o MariaDB – zaczniemy od instalacji i uruchomienia serwera.

W sześćdziesięciu slajdach opanujesz wszystkie praktyczne umiejętności: od uruchomienia bazy, przez tworzenie tabel i operacje DML, aż po złączenia JOIN i eksport danych. Każde polecenie jest zilustrowane przykładem.

MariaDB to jeden z najpopularniejszych systemów baz danych na świecie – darmowy, open-source i w pełni kompatybilny z MySQL.
Logo MariaDB obok logo SQL – slajd tytułowy prezentacji

MariaDB powstała jako rozwidlenie MySQL-a w 2009 roku, gdy społeczność open-source zaniepokoiła się kierunkiem rozwoju MySQL-a po przejęciu przez Oracle. Założycielem projektu jest Michael "Monty" Widenius, oryginalny twórca MySQL-a. Dziś MariaDB jest rozwijana przez fundację MariaDB Foundation i używana przez gigantów takich jak Wikipedia, Google czy Red Hat. Pełna kompatybilność z MySQL oznacza, że polecenia SQL działają identycznie w obu systemach – wiedza zdobyta na tym kursie jest uniwersalna.

System zarządzania bazą danych MariaDB oferuje kilka silników składowania, z których domyślnym jest InnoDB zapewniający wsparcie dla transakcji ACID, kluczy obcych i mechanizmu przywracania po awarii. Alternatywny silnik MyISAM oferuje wyższą wydajność przy odczycie kosztem braku transakcji. Wybór odpowiedniego silnika zależy od charakterystyki projektowanej aplikacji i powinien być świadomą decyzją projektową.

SQL, czyli Structured Query Language, to deklaratywny język zapytań opracowany w latach 70. XX wieku przez IBM. Mimo upływu lat pozostaje niezmiennie najważniejszym językiem dostępu do danych w systemach relacyjnych. Jego znajomość jest jedną z najbardziej poszukiwanych umiejętności na rynku pracy IT – według raportów Stack Overflow SQL znajduje się w pierwszej piątce języków używanych przez profesjonalnych programistów.

2/60Agenda – plan prezentacji

Co omówimy w ciągu 60 slajdów?

Prezentacja podzielona jest na sześć części:

Część 1 (slajdy 1–10): Wprowadzenie do MariaDB, instalacja, pierwsze logowanie
Część 2 (slajdy 11–20): Użytkownicy, bazy danych, typy, CREATE TABLE
Część 3 (slajdy 21–30): Ograniczenia, ALTER TABLE, INSERT, SELECT
Część 4 (slajdy 31–40): WHERE, GROUP BY, funkcje agregujące, UPDATE, DELETE
Część 5 (slajdy 41–50): JOIN, UNION, podzapytania
Część 6 (slajdy 51–60): Eksport, indeksy, transakcje, widoki, dobre praktyki

Każdy slajd zawiera przykład SQL, opis oraz rozszerzone objaśnienie dostępne po kliknięciu przycisku.
Mapa drogowa prezentacji – 6 części po 10 slajdów

Agenda została zaprojektowana tak, aby prowadzić słuchacza od absolutnych podstaw do zaawansowanych konstrukcji SQL w logicznej sekwencji. Każda część buduje fundament pod kolejną – najpierw uczymy się uruchamiać serwer i logować, potem tworzyć strukturę bazy, wypełniać ją danymi, wreszcie zadawać złożone pytania i eksportować wyniki.

Taki podział pozwala na elastyczne korzystanie z prezentacji – osoby znające już podstawy mogą pominąć pierwsze części i skupić się na JOIN-ach czy transakcjach. Każdy slajd jest samodzielną jednostką, ale razem tworzą spójny kurs obejmujący cały cykl życia bazy danych.

W trakcie prezentacji będziemy budować przykładową bazę biblioteki z tabelami Czytelnicy, Ksiazki i Wypozyczenia. Ten spójny przypadek użycia pozwoli prześledzić pełny proces – od projektu tabel po złożone raporty łączące dane z wielu źródeł.

Rozszerzone objaśnienia pod każdym slajdem zawierają dodatkowy kontekst, historię i szczegóły techniczne, które nie mieszczą się w głównej treści. Zachęcamy do ich odkrywania – to właśnie tam znajdziesz najciekawsze informacje pogłębiające temat.

3/60Czym jest MariaDB?

System zarządzania relacyjnymi bazami danych (RDBMS)

MariaDB to w pełni darmowy, otwartoźródłowy system baz danych, rozwidlony z MySQL w 2009 roku. Jest w 100% kompatybilny z MySQL, co oznacza, że wszystkie narzędzia i aplikacje napisane dla MySQL działają bez zmian.

Główne cechy: obsługa standardu SQL, transakcje ACID (InnoDB), klucze obce, widoki, triggery, procedury składowane, replikacja i wiele silników składowania.

-- Sprawdzenie wersji MariaDB
SELECT VERSION();
MariaDB jest domyślnym serwerem baz danych w wielu dystrybucjach Linuksa, w tym w Debianie, Ubuntu i Arch Linux.
Diagram – MariaDB jako warstwa pomiędzy aplikacją a danymi

Nazwa MariaDB pochodzi od imienia córki Michaela Wideniusa – twórcy MySQL-a. W podobny sposób MySQL został nazwany na cześć jego drugiej córki, My. Ta rodzinna tradycja podkreśla osobisty charakter projektu, który wyrósł z pasji jednego programisty.

MariaDB może być używana jako zamiennik MySQL-a we wszystkich scenariuszach – od prostych aplikacji internetowych po systemy klasy enterprise. Silnik InnoDB, używany domyślnie, zapewnia zgodność z wymogami ACID: atomowość, spójność, izolację i trwałość transakcji. To kluczowe cechy dla systemów bankowych, e-commerce czy ERP.

Wsparcie dla wielu silników składowania pozwala optymalizować wydajność pod konkretne obciążenia. Silnik MyISAM, choć nie wspiera transakcji, jest szybszy przy operacjach odczytu i zajmuje mniej miejsca. Silnik Aria (rozwinięcie MyISAM) dodaje odporność na awarie. Dla danych tymczasowych idealny jest MEMORY, przechowujący tabele w pamięci RAM.

4/60Architektura klient-serwer

Jak działa komunikacja z bazą danych?

MariaDB działa w architekturze klient-serwer. Serwer (mariadbd) nasłuchuje na porcie 3306 i obsługuje wiele równoczesnych połączeń od klientów. Klientem może być konsola mysql, aplikacja w Python/PHP/Java lub narzędzie graficzne.

Każde połączenie wymaga podania hosta, portu, użytkownika i hasła. Serwer autoryzuje klienta i wykonuje przychodzące zapytania SQL, zwracając wyniki.

-- Klient łączy się z serwerem lokalnym
mysql -h localhost -P 3306 -u root -p
-- Po zalogowaniu sprawdzamy aktywne procesy
SHOW PROCESSLIST;
Serwer MariaDB może obsługiwać tysiące równoczesnych połączeń – limit konfiguruje się w pliku my.cnf.
Schemat – komputer kliencki łączy się z serwerem przez TCP/IP

Architektura klient-serwer oznacza, że baza danych działa jako osobny proces, często na dedykowanym serwerze. Klienci łączą się przez sieć, co umożliwia centralne zarządzanie danymi i dostęp z różnych lokalizacji. W praktyce webowej serwer WWW (np. Apache, Nginx) łączy się z serwerem bazy danych przez wewnętrzną sieć.

Protokół komunikacyjny MariaDB jest binarny i wydajny. Domyślny port 3306 można zmienić w pliku konfiguracyjnym, co jest częstą praktyką ze względów bezpieczeństwa. Komunikacja może być szyfrowana przez TLS/SSL, co zapobiega podsłuchiwaniu haseł przesyłanych przez sieć.

Każde połączenie klienta to osobny wątek w serwerze. Polecenie SHOW PROCESSLIST wyświetla wszystkie aktywne połączenia wraz z wykonywanym zapytaniem. To niezbędne narzędzie diagnostyczne – pozwala zidentyfikować blokujące się zapytania i zabić je komendą KILL ID.

5/60Instalacja MariaDB

Jak zainstalować MariaDB w systemie Linux?

MariaDB jest dostępna w repozytoriach większości dystrybucji. Poniżej przykłady dla Debiana/Ubuntu oraz Arch Linux. Po instalacji serwer automatycznie tworzy konto administratora root.

# Instalacja na Debian/Ubuntu
sudo apt update
sudo apt install mariadb-server mariadb-client

# Instalacja na Arch Linux
sudo pacman -S mariadb
sudo mysql_install_db -u root
sudo systemctl enable --now mariadb

# Zabezpieczenie instalacji (pierwsze uruchomienie)
sudo mysql_secure_installation
Po instalacji zawsze uruchom mysql_secure_installation – usuniesz anonimowe konta i testową bazę.
Zrzut ekranu – terminal z procesem instalacji MariaDB

Pakiet mariadb-server instaluje serwer, a mariadb-client – konsolowe narzędzie mysql. W nowszych wersjach systemd uruchamia serwer automatycznie po instalacji. W przypadku Arch Linux wymagane jest ręczne wykonanie mysql_install_db, które tworzy niezbędne tabele systemowe.

Skrypt mysql_secure_installation przeprowadza użytkownika przez podstawowe czynności zabezpieczające: ustawienie hasła root, usunięcie anonimowych użytkowników, zakaz logowania root przez sieć, usunięcie testowej bazy danych i przeładowanie tablic uprawnień. To krytyczny krok pomijany przez początkujących – jego wykonanie znacząco podnosi bezpieczeństwo serwera.

W systemie Windows instalację wykonuje się poprzez instalator MSI pobrany z oficjalnej strony mariadb.org. Instalator zawiera kreator konfiguracji, który pozwala ustawić hasło root, port i typ instalacji. Dla macOS dostępny jest pakiet Homebrew: brew install mariadb.

6/60Uruchamianie serwera MariaDB

Zarządzanie serwerem przez systemd

W nowoczesnych dystrybucjach Linuksa serwerem MariaDB zarządza się przez systemd. Możemy uruchomić, zatrzymać, sprawdzić status i włączyć autostart serwera. Serwer nosi nazwę mariadb.

# Uruchomienie serwera
sudo systemctl start mariadb

# Zatrzymanie serwera
sudo systemctl stop mariadb

# Status serwera
sudo systemctl status mariadb

# Autostart przy starcie systemu
sudo systemctl enable mariadb

# Restart serwera
sudo systemctl restart mariadb
Po zmianie konfiguracji w my.cnf wymagany jest restart serwera, aby zmiany zostały zastosowane.
Zrzut ekranu – systemctl status mariadb pokazujący aktywny serwer

systemd to standardowy system inicjalizacji w większości dystrybucji Linuksa. Komenda systemctl pozwala nie tylko uruchamiać i zatrzymywać usługi, ale także sprawdzać ich logi (journalctl -u mariadb) i konfigurować zachowanie przy starcie systemu. Polecenie enable tworzy dowiązania symboliczne w katalogu /etc/systemd/system, zapewniając automatyczne uruchamianie serwera.

W starszych dystrybucjach używających SysV init stosuje się komendy service mariadb start/stop/status. W systemach bez systemd można uruchomić serwer ręcznie przez mysqld_safe – skrypt startowy, który monitoruje proces i restartuje go w razie awarii. To przydatne podczas debugowania problemów z konfiguracją.

Plik konfiguracyjny my.cnf znajduje się w /etc/mysql/ lub /etc/my.cnf. Zawiera sekcje dla klienta ([client]), serwera ([mysqld]) i narzędzi ([mysqldump]). Najczęściej modyfikowane parametry to port, max_connections, innodb_buffer_pool_size i character-set-server. Po każdej zmianie wymagany jest restart serwera.

7/60Logowanie do MariaDB

Pierwsze logowanie jako root

Po zainstalowaniu i uruchomieniu serwera możemy zalogować się przez konsolę mysql. Konto root (administrator) ma pełen dostęp do wszystkich baz danych. Standardowo logujemy się z localhost.

# Logowanie jako root (bez hasła – tuż po instalacji)
mysql -u root -p

# Logowanie na konkretny host i port
mysql -h 192.168.1.100 -P 3306 -u root -p

# Logowanie bez hasła (jeśli nie jest ustawione)
sudo mysql -u root

# Wyjście z konsoli
EXIT;
W systemie Debian/Ubuntu root często loguje się przez socket systemowy: sudo mysql bez hasła.
Zrzut ekranu – konsola mysql z zachętą MariaDB [(none)> ]

Po udanym logowaniu konsola wyświetla powitanie z wersją MariaDB i zachętę MariaDB [(none)]>. Zachęta wskazuje aktualnie wybraną bazę – (none) oznacza, że żadna baza nie jest wybrana. Można to zmienić komendą USE nazwa_bazy.

Parametr -p (lub --password) wymusza interaktywne zapytanie o hasło. Hasło można też podać bezpośrednio w linii poleceń z -phaslo, ale jest to niebezpieczne – hasło staje się widoczne w historii powłoki i listingu procesów. Zawsze używaj -p bez hasła.

W Debianie i Ubuntu domyślną metodą uwierzytelniania root jest unix_socket – oznacza to, że tylko użytkownik systemowy root może zalogować się bez hasła poprzez sudo mysql. To rozwiązanie jest bezpieczniejsze niż puste hasło, ale może być mylące dla początkujących, którzy oczekują standardowego logowania z hasłem.

8/60Pierwsze polecenia SQL

SELECT, VERSION() i SHOW DATABASES

Po zalogowaniu warto sprawdzić wersję serwera i zobaczyć istniejące bazy danych. Polecenie SELECT jest najczęściej używane w SQL – służy do pobierania danych. Funkcja VERSION() zwraca wersję serwera.

-- Sprawdzenie wersji serwera
SELECT VERSION();

-- Wyświetlenie wszystkich baz danych
SHOW DATABASES;

-- Proste wyrażenie arytmetyczne
SELECT 2 + 2 AS wynik;

-- Aktualna data i czas
SELECT NOW();
SQL nie rozróżnia wielkości liter, ale przyjęło się pisać słowa kluczowe WIELKIMI literami.
Wynik SELECT VERSION() – MariaDB 10.11.x

SELECT VERSION() to pierwsze zapytanie, które wykonuje większość administratorów po instalacji. Wynik zawiera numer wersji, typ kompilacji i system operacyjny. Przykładowo: "10.11.6-MariaDB-0+deb12u1" oznacza MariaDB 10.11.6, pakiet Debiana 12. Numeracja wersji MariaDB różni się od MySQL – MariaDB stosuje własne oznaczanie (10.x, 11.x).

SHOW DATABASES wyświetla listę wszystkich baz na serwerze. Po instalacji widoczne są bazy systemowe: information_schema (schemat informacyjny z metadanymi), mysql (tabele uprawnień i użytkowników) oraz performance_schema (dane wydajnościowe). Baza testowa (test) jest usuwana przez mysql_secure_installation.

Zapytanie SELECT 2 + 2 AS wynik pokazuje, że SQL może działać jak kalkulator. Alias AS nadaje kolumnie wynikowej czytelną nazwę. To przydatna technika – w rzeczywistych zapytaniach aliasy znacznie poprawiają czytelność, szczególnie przy obliczeniach na wielu kolumnach.

9/60Interfejs konsolowy mysql

Praca z wiersza poleceń – skróty i sztuczki

Konsola mysql oferuje wiele udogodnień: historię poleceń (strzałki), autouzupełnianie (Tab), edycję linii i skróty klawiszowe. Znajomość tych funkcji znacznie przyspiesza pracę.

-- Kończenie zapytania (średnik – domyślnie)
SELECT 1;

-- c – anulowanie bieżącego zapytania
SELECT \c

-- g – wykonaj zapytanie (alternatywnie ; lub \G)
SELECT * FROM Czytelnicy \G

-- Wyczyszczenie ekranu
\! clear

-- Edycja domyślnym edytorem
\e
Skrót \G wyświetla wyniki pionowo – przydatne przy tabelach z wieloma kolumnami.
Zrzut ekranu – konsola mysql z autouzupełnianiem tabel

Konsola mysql oparta jest na bibliotece readline, co zapewnia zaawansowane edytowanie linii. Strzałka w górę przywraca poprzednie polecenia, Ctrl+A przesuwa kursor na początek linii, Ctrl+E na koniec. Ctrl+W kasuje poprzednie słowo, Ctrl+U całą linię. Te skróty działają identycznie jak w bashu.

Znak specjalny \c anuluje zapytanie bez wykonywania – kluczowe, gdy przypadkowo zaczniemy pisać długie zapytanie i chcemy zacząć od nowa. \! wykonuje komendę systemową – na przykład \! clear czyści ekran, \! ls pokazuje pliki w bieżącym katalogu. \e otwiera domyślny edytor (vi, nano) z treścią bieżącego zapytania.

Polecenie source (lub \.) wczytuje i wykonuje plik .sql: source /ścieżka/do/pliku.sql. To podstawowa metoda importowania danych i struktur z plików. W połączeniu z tee /ścieżka/log.txt, które zapisuje całą sesję do pliku, daje potężne narzędzie do pracy wsadowej i dokumentowania działań administracyjnych.

10/60HELP i podstawowe funkcje

Jak korzystać z wbudowanej pomocy?

MariaDB zawiera rozbudowany system pomocy dostępny przez polecenie HELP. Możemy wyszukiwać tematy, polecenia i funkcje. System pomocy udostępnia składnię, opisy i przykłady.

-- Lista wszystkich tematów pomocy
HELP CONTENTS;

-- Pomoc dla konkretnego polecenia
HELP SELECT;

-- Pomoc dla funkcji
HELP CONCAT;

-- Wyszukiwanie tematu
HELP JOIN;

-- Funkcja IFNULL – zastąpienie NULL wartością domyślną
SELECT IFNULL(NULL, 'wartość domyślna');
Pomoc w MariaDB pochodzi z tabel systemowych – możesz też przeglądać dokumentację online na mariadb.com/kb.
Zrzut ekranu – wynik HELP SELECT z opisem składni

System pomocy MariaDB jest wyjątkowo rozbudowany – zawiera opisy wszystkich poleceń SQL, funkcji, operatorów i silników składowania. HELP CONTENTS wyświetla hierarchię tematów, a HELP temat – konkretną dokumentację. W przeciwieństwie do suchych podręczników, pomoc MariaDB zawiera przykłady użycia każdego polecenia.

Funkcje wbudowane w MariaDB dzielą się na kategorie: tekstowe (CONCAT, SUBSTRING, REPLACE), matematyczne (ROUND, CEIL, FLOOR), daty (NOW, DATE_FORMAT, DATEDIFF), agregujące (COUNT, SUM, AVG) i warunkowe (IF, CASE, IFNULL, COALESCE). Łączenie funkcji w jednym zapytaniu pozwala na zaawansowane przekształcanie danych już na poziomie bazy.

IFNULL to jedna z najczęściej używanych funkcji w praktyce. NULL w SQL oznacza brak wartości – nie jest to ani 0, ani pusty string. NULL nie uczestniczy w porównaniach: NULL = NULL daje NULL, a nie TRUE. IFNULL zamienia NULL na podaną wartość domyślną. COALESCE jest rozszerzeniem IFNULL – przyjmuje dowolną liczbę argumentów i zwraca pierwszy nie-NULL.

11/60Użytkownicy – wprowadzenie

System autoryzacji w MariaDB

MariaDB zarządza dostępem poprzez system kont użytkowników. Każde konto składa się z nazwy użytkownika i hosta (user@host). Użytkownik root ma pełne uprawnienia, ale do codziennej pracy należy tworzyć osobne konta z ograniczonym dostępem.

-- Wyświetlenie wszystkich użytkowników (tabela mysql.user)
SELECT User, Host, Password_expired
FROM mysql.user;

-- Aktualny użytkownik
SELECT CURRENT_USER();
Użytkownik w MariaDB to para 'user'@'host' – to samo 'jan' z localhost i z 192.168.% to dwa różne konta.
Tabela mysql.user – lista użytkowników z kolumnami User, Host, Password

System uprawnień MariaDB oparty jest na standardzie SQL. Tabela mysql.user przechowuje konta i globalne uprawnienia. Oprócz niej istnieją mysql.db (uprawnienia na poziomie bazy), mysql.tables_priv (poziom tabel), mysql.columns_priv (poziom kolumn) i mysql.procs_priv (procedury). Ta hierarchia pozwala na bardzo precyzyjną kontrolę dostępu.

Kwerenda SELECT User, Host FROM mysql.user pokazuje wszystkie zdefiniowane konta. Kolumna Host może zawierać adres IP (192.168.1.100), maskę (192.168.1.%), domenę (%.example.com) lub localhost. Znak % oznacza dowolny host. Nowi administratorzy często tworzą tylko 'jan'@'localhost', zapominając o 'jan'@'%' dla dostępu zdalnego.

CURRENT_USER() zwraca aktualnie zalogowane konto wraz z hostem. To ważne narzędzie diagnostyczne – pozwala sprawdzić, z jakim kontem faktycznie jesteśmy połączeni, co bywa mylące, gdy istnieje wiele pasujących wpisów w mysql.user dla różnych hostów.

12/60CREATE USER – tworzenie użytkowników

Dodawanie nowych kont do MariaDB

Polecenie CREATE USER tworzy nowe konto. Od razu możemy ustawić hasło i reguły dotyczące autoryzacji. Można też zdefiniować limit połączeń i zasoby.

-- Proste utworzenie użytkownika
CREATE USER 'jan'@'localhost' IDENTIFIED BY 'bezpieczne_haslo';

-- Użytkownik z dostępem z dowolnego hosta
CREATE USER 'anna'@'%' IDENTIFIED BY 'Haslo2025!';

-- Użytkownik z limitem 10 zapytań na godzinę
CREATE USER 'raport'@'localhost'
IDENTIFIED BY 'Raport123'
WITH MAX_QUERIES_PER_HOUR 10;

-- Sprawdzenie czy użytkownik istnieje
SELECT User FROM mysql.user
WHERE User = 'jan';
Hasła w MariaDB są przechowywane w formacie zahashowanym – nigdy w postaci czystego tekstu.
Składnia CREATE USER – diagram elementów polecenia

Polecenie CREATE USER pojawiło się w MySQL 5.0 i jest standardowym sposobem tworzenia kont. Wcześniej użytkowników dodawało się bezpośrednio przez INSERT do mysql.user, co było niebezpieczne i podatne na błędy. CREATE USER gwarantuje poprawną strukturę konta i odpowiednie zahashowanie hasła.

Hasło w IDENTIFIED BY jest automatycznie hashowane funkcją PASSWORD(). W MariaDB 10.4+ domyślnym mechanizmem jest ed25519 lub unix_socket, w zależności od platformy. Hasła nigdy nie są przechowywane w postaci jawnej – przy logowaniu porównywana jest suma kontrolna. To uniemożliwia odczytanie hasła nawet administratorowi bazy.

Opcje WITH pozwalają na ograniczenie zasobów: MAX_QUERIES_PER_HOUR, MAX_UPDATES_PER_HOUR, MAX_CONNECTIONS_PER_HOUR i MAX_USER_CONNECTIONS. To przydatne narzędzie w środowiskach hostingowych, gdzie wielu użytkowników dzieli jeden serwer. Ograniczenia można też modyfikować po utworzeniu konta poleceniem ALTER USER.

13/60GRANT – nadawanie uprawnień

Przyznawanie dostępu do baz i tabel

Nowy użytkownik nie ma żadnych uprawnień – trzeba je jawnie nadać poleceniem GRANT. Uprawnienia mogą dotyczyć całego serwera, konkretnej bazy, tabeli, a nawet kolumny. Nadanie uprawnień musi wykonać administrator (root).

-- Pełny dostęp do bazy biblioteka
GRANT ALL PRIVILEGES
ON biblioteka.*
TO 'jan'@'localhost';

-- Tylko SELECT na jednej tabeli
GRANT SELECT
ON biblioteka.Ksiazki
TO 'anna'@'%';

-- SELECT, INSERT, UPDATE na konkretnych kolumnach
GRANT SELECT (Nr_czyt, Nazwisko, Imie),
      UPDATE (Nazwisko, Imie)
ON biblioteka.Czytelnicy
TO 'pracownik'@'localhost';

-- Odświeżenie uprawnień (po GRANT nie jest konieczne)
FLUSH PRIVILEGES;
GRANT ALL PRIVILEGES nie obejmuje uprawnienia GRANT OPTION – do tego potrzebne jest osobne WITH GRANT OPTION.
Hierarchia uprawnień – od globalnych po kolumny

Poziomy nadawania uprawnień: ON *.* (globalny – wszystkie bazy i tabele), ON biblioteka.* (wszystkie tabele w bazie), ON biblioteka.Ksiazki (konkretna tabela), ON biblioteka.Ksiazki.tytul (kolumna). Im bardziej szczegółowy poziom, tym precyzyjniejsza kontrola dostępu. W praktyce najczęściej używa się poziomu bazy danych.

Lista uprawnień jest długa: SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, INDEX, REFERENCES, CREATE VIEW, TRIGGER, EXECUTE itd. ALL PRIVILEGES nadaje wszystkie uprawnienia z wyjątkiem GRANT OPTION. Aby umożliwić użytkownikowi nadawanie uprawnień innym, dodaje się WITH GRANT OPTION na końcu polecenia GRANT.

W MariaDB 10.5+ pojawiły się dodatkowe uprawnienia: SET_USER (możliwość zmiany kontekstu użytkownika), FEDERATED (zarządzanie tabelami federacyjnymi) i CONNECTION_ADMIN (zarządzanie połączeniami). Nowe uprawnienia są dodawane z każdą wersją w odpowiedzi na potrzeby bardziej szczegółowej kontroli dostępu w złożonych środowiskach.

14/60REVOKE – odbieranie uprawnień

Cofanie przyznanych wcześniej uprawnień

REVOKE działa odwrotnie do GRANT – odbiera wcześniej nadane uprawnienia. Składnia jest analogiczna: definiujemy poziom (baza, tabela) i konkretne uprawnienia do odebrania.

-- Odebranie wszystkich uprawnień do bazy
REVOKE ALL PRIVILEGES
ON biblioteka.*
FROM 'jan'@'localhost';

-- Odebranie konkretnego uprawnienia
REVOKE DELETE
ON biblioteka.Ksiazki
FROM 'pracownik'@'localhost';

-- Odebranie uprawnień na wszystkich bazach
REVOKE ALL PRIVILEGES, GRANT OPTION
FROM 'anna'@'%';
REVOKE nie usuwa konta użytkownika – usuwa tylko uprawnienia. Do usunięcia konta służy DROP USER.
Schemat – GRANT przyznaje, REVOKE odbiera uprawnienia

REVOKE wymaga posiadania uprawnienia GRANT OPTION na tym samym poziomie, na którym odbieramy uprawnienia. Administrator root naturalnie ma to uprawnienie. Po wykonaniu REVOKE zmiana jest natychmiastowa – nie wymaga FLUSH PRIVILEGES, ponieważ MariaDB automatycznie przeładowuje uprawnienia po GRANT/REVOKE.

Odebranie uprawnień nie zamyka aktywnych połączeń użytkownika. Jeśli użytkownik jest aktualnie zalogowany, zmiana uprawnień zostanie zastosowana przy następnym zapytaniu (każde zapytanie sprawdza bieżące uprawnienia). To oznacza, że długo działające zapytanie rozpoczęte przed REVOKE dokończy się, ale kolejne zostanie już odrzucone.

Ważna różnica między GRANT a REVOKE: REVOKE ALL PRIVILEGES, GRANT OPTION FROM user odbiera wszystko, łącznie z prawem do nadawania uprawnień. Po takim poleceniu użytkownik może być zalogowany, ale nie wykona żadnego zapytania poza tymi, które nie wymagają uprawnień (jak SELECT VERSION()).

15/60SHOW GRANTS – podgląd uprawnień

Jak sprawdzić, jakie uprawnienia ma użytkownik?

SHOW GRANTS wyświetla wszystkie uprawnienia nadane użytkownikowi w formie poleceń GRANT. Możemy sprawdzić własne uprawnienia lub dowolnego innego użytkownika (jeśli mamy do tego prawo).

-- Sprawdzenie własnych uprawnień
SHOW GRANTS;

-- Sprawdzenie uprawnień innego użytkownika
SHOW GRANTS FOR 'jan'@'localhost';

-- Sprawdzenie uprawnień dla użytkownika z dowolnego hosta
SHOW GRANTS FOR 'anna'@'%';

-- Alternatywny sposób – tabela mysql.user
SELECT * FROM mysql.user
WHERE User = 'jan'\G
SHOW GRANTS jest bezpieczniejsze niż bezpośrednie czytanie mysql.user – pokazuje tylko istotne informacje.
Zrzut ekranu – wynik SHOW GRANTS dla użytkownika root

SHOW GRANTS FOR CURRENT_USER (lub samo SHOW GRANTS) wyświetla uprawnienia aktualnie zalogowanego użytkownika. Dla root wynik może zawierać wiele linii z GRANT ALL PRIVILEGES ON *.* oraz WITH GRANT OPTION. Dla zwykłych użytkowników wynik jest znacznie krótszy i precyzyjny.

Wynik SHOW GRANTS jest sformatowany jako ciąg poleceń GRANT, które można skopiować i wykonać na innym serwerze. To ułatwia migrację uprawnień między serwerami – wystarczy wyeksportować GRANT-y dla każdego użytkownika i wykonać je na nowym serwerze. Narzędzie mysqldump również oferuje opcję --flush-privileges do migracji uprawnień.

SHOW GRANTS nie pokazuje uprawnień wynikających z ról. W MariaDB 10.2+ wprowadzono role – zbiory uprawnień, które można przypisywać użytkownikom. Aby zobaczyć aktywne role dla bieżącej sesji, używamy SELECT CURRENT_ROLE(). Role są szczególnie przydatne w środowiskach z wieloma użytkownikami o podobnych potrzebach.

16/60DROP USER – usuwanie użytkowników

Bezpieczne usuwanie kont

DROP USER usuwa konto użytkownika i wszystkie jego uprawnienia. Przed usunięciem warto sprawdzić, czy konto posiada jakieś obiekty (tabele, widoki), które staną się osierocone – chociaż w MariaDB to rzadki problem.

-- Usunięcie użytkownika
DROP USER 'jan'@'localhost';

-- Usunięcie wielu użytkowników naraz
DROP USER 'anna'@'%',
            'pracownik'@'localhost',
            'raport'@'localhost';

-- Sprawdzenie czy użytkownik został usunięty
SELECT User FROM mysql.user
WHERE User = 'jan';
Nie można usunąć użytkownika, który jest aktualnie zalogowany – najpierw zakończ jego sesję.
Ilustracja – DROP USER usuwa konto z tabeli mysql.user

DROP USER dostępny jest od MySQL 4.1.1. Przed tą wersją usuwanie użytkowników wymagało ręcznego DELETE z mysql.user i FLUSH PRIVILEGES. DROP USER automatycznie czyści wszystkie tabele uprawnień (user, db, tables_priv, columns_priv, procs_priv), co czyni go bezpiecznym i kompletnym rozwiązaniem.

Jeśli użytkownik jest zalogowany podczas DROP USER, jego bieżące zapytania będą działać, ale po rozłączeniu nie będzie mógł się ponownie zalogować. Aktywne połączenie pozostanie otwarte, dopóki nie zostanie zakończone przez użytkownika lub nie nastąpi restart serwera. Można wymusić zamknięcie połączenia za pomocą KILL CONNECTION ID.

Przed usunięciem użytkownika warto sprawdzić, czy nie jest właścicielem procedur, widoków lub triggerów. W MariaDB obiekty te są powiązane z definiującym je użytkownikiem (DEFINER). Po usunięciu użytkownika obiekty mogą działać niepoprawnie. Rozwiązaniem jest zmiana definiującego użytkownika na innego przed DROP USER.

17/60SHOW DATABASES, USE, SHOW TABLES, DESC

Nawigacja po strukturze serwera

Podstawowe polecenia do poruszania się po strukturze MariaDB: SHOW DATABASES wyświetla bazy, USE wybiera aktywną bazę, SHOW TABLES pokazuje tabele w bieżącej bazie, a DESC (lub DESCRIBE) opisuje strukturę tabeli. To zestaw niezbędny do codziennej pracy.

-- Lista wszystkich baz
SHOW DATABASES;

-- Wybór aktywnej bazy
USE biblioteka;

-- Lista tabel w wybranej bazie
SHOW TABLES;

-- Struktura tabeli – kolumny, typy, klucze
DESC Czytelnicy;

-- Alternatywnie: SHOW COLUMNS FROM
SHOW COLUMNS FROM Czytelnicy;
DESC to skrót od DESCRIBE – obie formy są równoważne. Można też użyć EXPLAIN do opisu tabeli.
Zrzut ekranu – kolejno SHOW DATABASES, USE, SHOW TABLES, DESC

SHOW DATABASES wymaga uprawnienia SHOW DATABASES lub SELECT na którąś z tabel w mysql.*. W wyniku nie są wyświetlane bazy, do których użytkownik nie ma dostępu (z wyjątkiem information_schema i performance_schema, które są widoczne zawsze). To zabezpieczenie przed nieautoryzowanym wglądem w strukturę serwera.

Polecenie USE nie wymaga średnika, ale w praktyce zawsze go dodajemy. USE nie jest standardowym SQL – to komenda specyficzna dla klienta mysql. W innych klientach (np. PHP PDO) bazę wybiera się w parametrach połączenia. Po USE zachęta zmienia się z MariaDB [(none)]> na MariaDB [biblioteka]>, co ułatwia orientację.

DESC wyświetla kolumny tabeli z ich typami, informacją o NULL, kluczach, wartości domyślnej i dodatkowych atrybutach (jak AUTO_INCREMENT). To podstawowe narzędzie do szybkiego sprawdzenia struktury – widzimy od razu, które kolumny są kluczami głównymi (PRI), obcymi (MUL) lub unikalnymi (UNI).

18/60CREATE DATABASE

Tworzenie nowej bazy danych

CREATE DATABASE tworzy nową bazę danych. Możemy określić zestaw znaków (charset) i sortowanie (collation). Dla języka polskiego zalecane jest utf8mb4 z collation utf8mb4_polish_ci – obsługuje polskie znaki diakrytyzowane i porównania zgodne z regułami języka polskiego.

-- Prosta baza z domyślnym charset
CREATE DATABASE biblioteka;

-- Baza z polskim zestawem znaków
CREATE DATABASE biblioteka
CHARACTER SET utf8mb4
COLLATE utf8mb4_polish_ci;

-- Sprawdzenie, czy baza istnieje (przed utworzeniem)
CREATE DATABASE IF NOT EXISTS biblioteka
CHARACTER SET utf8mb4;

-- Wyświetlenie parametrów istniejącej bazy
SHOW CREATE DATABASE biblioteka;
Nazwy baz danych są rozróżniane co do wielkości liter w systemie Linux, ale nie w Windows – to zależy od systemu plików.
Ilustracja – nowo utworzona baza widoczna w SHOW DATABASES

CREATE DATABASE tworzy katalog na dysku w miejscu wskazanym przez zmienną datadir (zazwyczaj /var/lib/mysql). Każda baza ma własny katalog, a tabele InnoDB są przechowywane w plikach .ibd. Dla MyISAM każda tabela tworzy osobne pliki .frm (definicja), .MYD (dane) i .MYI (indeksy). Zrozumienie fizycznego przechowywania pomaga w administracji przestrzenią dyskową.

Wybór odpowiedniego zestawu znaków ma krytyczne znaczenie. utf8mb4 obsługuje wszystkie znaki Unicode, w tym emoji i polskie znaki diakrytyzowane. Starszy utf8mb3 (alias utf8) nie obsługuje znaków spoza Basic Multilingual Plane. Collation utf8mb4_polish_ci zapewnia poprawne sortowanie polskich znaków (ą, ć, ę itd.) zgodne z regułami języka polskiego.

IF NOT EXISTS zapobiega błędowi, gdy baza już istnieje. To przydatne w skryptach instalacyjnych i migracyjnych, które mogą być uruchamiane wielokrotnie. Bez tej klauzuli próba utworzenia istniejącej bazy zwróci błąd ERROR 1007 (HY000): Can't create database 'biblioteka'; database exists.

19/60Typy danych w MariaDB

Jakie dane możemy przechowywać?

MariaDB oferuje bogaty zestaw typów danych podzielonych na kategorie: liczbowe, tekstowe, daty i binarne. Wybór odpowiedniego typu ma wpływ na wydajność i rozmiar bazy. Poniżej najważniejsze typy używane w praktyce.

-- Typy liczbowe całkowite (rozmiar w bajtach)
TINYINT(1)    -- 1 bajt, zakres -128..127 lub 0..255
SMALLINT(5)   -- 2 bajty
INT(10)       -- 4 bajty, najczęściej używany
BIGINT(20)    -- 8 bajtów, dla bardzo dużych liczb

-- Typy zmiennoprzecinkowe i stałoprzecinkowe
DECIMAL(10,2) -- dokładne, np. ceny: 99999999.99
FLOAT         -- przybliżone, 4 bajty

-- Typy tekstowe
VARCHAR(50)  -- zmienna długość, max 65535 znaków
TEXT          -- długi tekst, max 65535 znaków
ENUM('a','b')-- lista wartości do wyboru

-- Daty i czas
DATE          -- '2025-03-20'
DATETIME      -- '2025-03-20 14:30:00'
TIMESTAMP     -- jak DATETIME ale z strefą czasową
DECIMAL jest preferowany nad FLOAT dla danych finansowych – unikamy błędów zaokrągleń.
Tabela z typami danych i ich rozmiarami w bajtach

Wybór odpowiedniego typu danych to jedna z najważniejszych decyzji podczas projektowania bazy. Użycie zbyt dużego typu (np. BIGINT zamiast INT) marnuje miejsce i spowalnia zapytania. Dla licznika książek w bibliotece INT w zupełności wystarczy. Dla identyfikatorów w systemach o bardzo dużej skali (np. ID użytkowników Facebooka) potrzebny jest BIGINT.

DECIMAL(10,2) oznacza liczbę o 10 cyfrach, z czego 2 po przecinku. To typ stałoprzecinkowy – przechowuje wartość dokładnie, bez błędów zaokrągleń. W przeciwieństwie do FLOAT i DOUBLE, które są typami przybliżonymi i mogą dawać nieoczekiwane wyniki przy porównaniach. Dla pieniędzy, podatków i rabatów zawsze używaj DECIMAL.

ENUM to typ przechowujący jedną wartość z predefiniowanej listy. Oszczędza miejsce (przechowywany jako liczba całkowita) i wymusza poprawność danych. Wadą ENUM jest trudność w modyfikacji listy – zmiana wymaga ALTER TABLE. Alternatywą jest osobna tabela słownikowa z kluczem obcym, co jest bardziej elastyczne.

20/60CREATE TABLE – Czytelnicy

Tworzenie pierwszej tabeli z AUTO_INCREMENT

Przejdziemy do praktyki – tworzymy schemat bazy biblioteki. Tabela Czytelnicy będzie przechowywać dane osobowe czytelników. Kolumna Nr_czytel zostanie automatycznie numerowana przez AUTO_INCREMENT – każdy nowy wiersz otrzyma kolejną wartość.

CREATE TABLE Czytelnicy (
    Nr_czytel INT AUTO_INCREMENT PRIMARY KEY,
    Nazwisko  VARCHAR(30) NOT NULL,
    Imie      VARCHAR(20) NOT NULL,
    Adres     VARCHAR(50),
    Miasto    VARCHAR(30),
    Kod       CHAR(6),
    Telefon   CHAR(9),
    Email     VARCHAR(50),
    Data_zap  DATE NOT NULL,
    Kara      DECIMAL(10,2) DEFAULT 0.00
)
ENGINE = InnoDB
DEFAULT CHARACTER SET utf8mb4
COLLATE = utf8mb4_polish_ci;
AUTO_INCREMENT zaczyna od 1 i zwiększa się o 1 – to zachowanie można zmienić przez zmienne @@auto_increment_increment i @@auto_increment_offset.
Diagram tabeli Czytelnicy z kolumnami i typami

Tabela Czytelnicy to podstawowa tabela słownikowa w naszym schemacie biblioteki. Kolumna Nr_czytel to licznik całkowity z AUTO_INCREMENT – MariaDB automatycznie przypisuje kolejne wartości podczas INSERT, eliminując ryzyko duplikatów. AUTO_INCREMENT można ustawić tylko na kolumnie, która jest kluczem (lub jej częścią) i ma typ liczbowy całkowity.

Kolumna Kod wykorzystuje typ CHAR(6) zamiast VARCHAR – to celowy wybór. Dla wartości o stałej długości (polski kod pocztowy ma zawsze 6 znaków w formacie 12-345) CHAR jest szybszy i wydajniejszy niż VARCHAR. CHAR rezerwuje stałą ilość miejsca, ale przy stałej długości nie ma narzutu związanego z przechowywaniem długości łańcucha.

Kolumna Kara ma wartość domyślną 0.00 – DEFAULT zapewnia, że jeśli nie podamy wartości podczas INSERT, MariaDB wstawi 0.00 zamiast NULL. Dla danych finansowych to ważne – unikamy NULL-i w kolumnach, gdzie oczekujemy wartości liczbowych. Użycie DECIMAL(10,2) oznacza maksymalną karę 99999999.99, co w praktyce jest więcej niż wystarczające.

21/60Ograniczenia – PRIMARY KEY, FOREIGN KEY, NOT NULL, UNIQUE

Definiowanie reguł dla danych w tabelach Ksiazki i Wypozyczenia

Ograniczenia (constraints) zapewniają integralność danych. PRIMARY KEY unikalnie identyfikuje wiersz, FOREIGN KEY łączy tabele, NOT NULL zabrania pustych wartości, UNIQUE wymusza unikalność poza kluczem głównym. Tworzymy kolejne tabele biblioteki.

CREATE TABLE Ksiazki (
    Nr_ks     INT AUTO_INCREMENT PRIMARY KEY,
    Tytul     VARCHAR(100) NOT NULL,
    Autor     VARCHAR(50) NOT NULL,
    Wydawnictwo VARCHAR(40),
    Rok_wyd   SMALLINT,
    Isbn      VARCHAR(20) UNIQUE,
    Liczba_egz TINYINT DEFAULT 1,
    Kategoria VARCHAR(30),
    INDEX idx_autor (Autor)
)
ENGINE = InnoDB
DEFAULT CHARSET utf8mb4
COLLATE utf8mb4_polish_ci;

CREATE TABLE Wypozyczenia (
    Nr_wyp    INT AUTO_INCREMENT PRIMARY KEY,
    Nr_czytel INT NOT NULL,
    Nr_ks     INT NOT NULL,
    Data_wyp  DATE NOT NULL,
    Data_zwrot DATE,
    CONSTRAINT fk_wyp_czyt
        FOREIGN KEY (Nr_czytel)
        REFERENCES Czytelnicy(Nr_czytel)
        ON DELETE CASCADE,
    CONSTRAINT fk_wyp_ks
        FOREIGN KEY (Nr_ks)
        REFERENCES Ksiazki(Nr_ks)
        ON DELETE CASCADE
)
ENGINE = InnoDB
DEFAULT CHARSET utf8mb4;
ON DELETE CASCADE oznacza: usuń wypożyczenia, gdy usuniesz czytelnika lub książkę. To zapobiega osieroconym rekordom.
Diagram ERD – trzy tabele z relacjami: Czytelnicy 1--* Wypozyczenia *--1 Ksiazki

Ograniczenia to fundament integralności relacyjnej bazy danych. PRIMARY KEY na Nr_ks w Ksiazki zapewnia, że każda książka ma unikalny identyfikator. UNIQUE na Isbn gwarantuje, że ten sam numer ISBN nie pojawi się dla dwóch różnych książek – to ważne, ponieważ ISBN jest globalnym unikalnym identyfikatorem wydawniczym.

FOREIGN KEY w tabeli Wypozyczenia tworzy więzy między tabelami. Klucz obcy Nr_czytel wskazuje na Czytelnicy(Nr_czytel), a Nr_ks na Ksiazki(Nr_ks). Dzięki temu nie można wypożyczyć książki nieistniejącemu czytelnikowi ani wypożyczyć książki, której nie ma w katalogu. MariaDB wymusza te reguły przy każdej operacji INSERT i UPDATE.

ON DELETE CASCADE oznacza kaskadowe usuwanie – jeśli usuniemy czytelnika, automatycznie usunięte zostaną wszystkie jego wypożyczenia. Alternatywą jest ON DELETE SET NULL (ustawia klucz obcy na NULL) lub ON DELETE RESTRICT (blokuje usunięcie, jeśli istnieją powiązane rekordy). W naszej bibliotece CASCADE ma sens – usunięcie czytelnika powinno usunąć historię jego wypożyczeń.

Indeks idx_autor (INDEX) przyspiesza wyszukiwanie książek po autorze. Indeksy to struktury danych (najczęściej B-drzewa), które pozwalają szybko znaleźć wiersze bez przeszukiwania całej tabeli. W MariaDB indeksy są tworzone automatycznie dla PRIMARY KEY i UNIQUE – dla pozostałych kolumn musimy je zadeklarować jawnie.

22/60DESC i SHOW CREATE TABLE

Podgląd struktury istniejących tabel

Po utworzeniu tabel warto sprawdzić ich strukturę. DESC (DESCRIBE) pokazuje podsumowanie kolumn. SHOW CREATE TABLE wyświetla pełne polecenie CREATE TABLE, które dokładnie odtwarza daną tabelę – łącznie ze wszystkimi ograniczeniami i opcjami.

-- Szybki podgląd kolumn
DESC Wypozyczenia;

-- Pełna definicja tabeli (do odtworzenia)
SHOW CREATE TABLE Wypozyczenia\G

-- Lista kluczy i indeksów
SHOW INDEX FROM Ksiazki;

-- Lista kluczy obcych
SELECT TABLE_NAME, COLUMN_NAME,
       CONSTRAINT_NAME, REFERENCED_TABLE_NAME,
       REFERENCED_COLUMN_NAME
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
WHERE TABLE_SCHEMA = 'biblioteka';
SHOW CREATE TABLE to idealne narzędzie do backupu pojedynczej tabeli – wynik można od razu wykonać na innym serwerze.
Wynik DESC Wypozyczenia – lista kolumn z typami

SHOW CREATE TABLE zwraca dokładne polecenie DDL, które utworzyło tabelę. To cenne narzędzie w kilku sytuacjach: podczas migracji bazy między serwerami (wynik można przekazać do innego administratora), przy debugowaniu (można zobaczyć dokładne ustawienia ENGINE, CHARSET, COLLATION) oraz przy dokumentowaniu schematu bazy.

Kolumnę Engine w DESC można zobaczyć tylko przez SHOW TABLE STATUS lub bezpośrednio w INFORMATION_SCHEMA. DESC nie pokazuje informacji o silniku składowania, opcjach tabeli ani szczegółach dotyczących AUTO_INCREMENT. Dlatego do pełnego obrazu struktury potrzebne są oba narzędzia: DESC do szybkiego podglądu, SHOW CREATE TABLE do pełnej definicji.

INFORMATION_SCHEMA to standardowa baza systemowa zawierająca metadane – informacje o wszystkich tabelach, kolumnach, kluczach i ograniczeniach. Zapytanie do KEY_COLUMN_USAGE pokazuje wszystkie klucze obce w danej bazie. To potężne narzędzie do analizy schematu, szczególnie w dużych bazach z setkami tabel i złożonymi relacjami.

23/60ALTER TABLE – ADD i DROP kolumn

Modyfikacja struktury tabel po utworzeniu

ALTER TABLE pozwala modyfikować istniejące tabele. Możemy dodawać nowe kolumny, usuwać istniejące, zmieniać ich typy i nazwy. To podstawowe narzędzie do ewolucji schematu bazy bez konieczności usuwania i odtwarzania tabeli.

-- Dodanie nowej kolumny (domyślnie na końcu)
ALTER TABLE Czytelnicy
ADD Pesel CHAR(11);

-- Dodanie kolumny na konkretnej pozycji
ALTER TABLE Ksiazki
ADD Strony SMALLINT AFTER Rok_wyd;

-- Dodanie kolumny jako pierwszej
ALTER TABLE Ksiazki
ADD ID_stare INT FIRST;

-- Usunięcie kolumny
ALTER TABLE Czytelnicy
DROP Pesel;

-- Dodanie kolumny z wartością domyślną
ALTER TABLE Ksiazki
ADD Opis TEXT DEFAULT '';
DROP COLUMN usuwa kolumnę i wszystkie jej dane – nie można cofnąć tej operacji (chyba że z backupu).
Schemat – ALTER TABLE ADD / DROP zmieniający strukturę tabeli

ALTER TABLE to polecenie DDL (Data Definition Language). Jego wykonanie blokuje tabelę na czas operacji – w przypadku dużych tabel (miliony wierszy) dodanie kolumny może trwać długo i zablokować dostęp do bazy. W środowiskach produkcyjnych takie operacje planuje się na okna konserwacyjne lub używa narzędzi online schema change (jak pt-online-schema-change).

Pozycja nowej kolumny ma znaczenie praktyczne. Domyślnie kolumna dodawana jest na końcu tabeli (LAST). AFTER kolumna pozwala umieścić ją w konkretnym miejscu, co poprawia czytelność struktury. FIRST wstawia kolumnę na początek – choć w relacyjnym modelu pozycja kolumn nie ma znaczenia logicznego, dla administratorów pracujących w konsoli czytelna kolejność kolumn ułatwia pracę.

Usunięcie kolumny (DROP) jest operacją destrukcyjną – dane w tej kolumnie są bezpowrotnie tracone. Niektóre systemy baz danych oferują "miękkie usunięcie" przez oznaczenie kolumny jako nieużywanej, ale MariaDB tego nie wspiera. Zawsze przed DROP warto sprawdzić, czy kolumna nie jest używana w widokach, procedurach lub triggerach.

24/60DROP TABLE – usuwanie tabel

Bezpieczne usuwanie tabel z bazy

DROP TABLE usuwa tabelę wraz ze wszystkimi danymi i definicją. Operacja jest nieodwracalna – przed wykonaniem zawsze warto zrobić backup. W MariaDB można sprawdzić, czy tabela istnieje, zanim ją usuniemy.

-- Usunięcie pojedynczej tabeli
DROP TABLE stare_dane;

-- Usunięcie z kontrolą istnienia
DROP TABLE IF EXISTS tymczasowa;

-- Usunięcie wielu tabel naraz
DROP TABLE tymcz1, tymcz2, tymcz3;

-- Przykład: czyszczenie całej bazy
DROP TABLE IF EXISTS Wypozyczenia;
DROP TABLE IF EXISTS Ksiazki;
DROP TABLE IF EXISTS Czytelnicy;

-- Sprawdzenie czy tabela istnieje przed usunięciem
SHOW TABLES LIKE 'Wypozyczenia';
DROP TABLE usuwa również wszystkie indeksy, triggery i uprawnienia związane z tabelą – to kompletne usunięcie.
Ilustracja – DROP TABLE usuwa zarówno strukturę jak i dane

DROP TABLE działa natychmiastowo – po wykonaniu tabela znika, a zajmowane przez nią miejsce na dysku jest zwalniane (w przypadku InnoDB plik .ibd jest usuwany). To odróżnia DROP od DELETE, który usuwa wiersze ale zachowuje strukturę. DROP to operacja DDL, więc nie można jej cofnąć ROLLBACK-iem w ramach transakcji.

IF EXISTS zapobiega błędowi przy próbie usunięcia nieistniejącej tabeli. Bez tej klauzuli MariaDB zwróci błąd ERROR 1051 (42S02): Unknown table 'biblioteka.nieistnieje'. IF EXISTS jest szczególnie przydatny w skryptach czyszczących i migracyjnych, które mogą być uruchamiane wielokrotnie.

W MariaDB nie można usunąć tabeli, jeśli istnieją do niej klucze obce z innych tabel. W naszej bibliotece nie można usunąć Ksiazki, jeśli istnieją wypożyczenia odwołujące się do niej przez FOREIGN KEY. Najpierw trzeba usunąć Wypozyczenia (lub tymczasowo wyłączyć sprawdzanie kluczy obcych: SET foreign_key_checks = 0 przed DROP TABLE).

25/60INSERT – wprowadzanie danych

Wypełnianie tabel danymi

INSERT dodaje nowe wiersze do tabeli. Możemy podać wartości dla wszystkich kolumn lub tylko wybranych. Dla kolumn AUTO_INCREMENT pomijamy je – MariaDB przypisze wartości automatycznie. Można też wstawić wiele wierszy jednym poleceniem.

-- INSERT z podaniem wszystkich kolumn (oprócz AUTO_INCREMENT)
INSERT INTO Czytelnicy
    (Nazwisko, Imie, Adres, Miasto, Kod, Telefon, Email, Data_zap)
VALUES
    ('Kowalski', 'Jan', 'Główna 15', 'Warszawa',
     '00-001', '123456789', 'jan@example.com', '2025-01-15');

-- INSERT dla wybranych kolumn (reszta NULL lub DEFAULT)
INSERT INTO Ksiazki (Tytul, Autor, Rok_wyd, Kategoria)
VALUES ('Wiedźmin', 'Andrzej Sapkowski', 1990, 'Fantastyka');

-- INSERT wielu wierszy naraz
INSERT INTO Ksiazki (Tytul, Autor, Rok_wyd, Kategoria)
VALUES
    ('Solaris', 'Stanisław Lem', 1961, 'SF'),
    ('Lalka', 'Bolesław Prus', 1890, 'Powieść'),
    ('Pan Tadeusz', 'Adam Mickiewicz', 1834, 'Poezja');
INSERT wielu wierszy w jednym poleceniu jest wydajniejsze niż wiele osobnych INSERT – mniejszy narzut komunikacji.
Tabela Ksiazki po INSERT – widok przykładowych danych

INSERT to podstawowa operacja DML (Data Manipulation Language). Każdy wiersz dodany poprzez INSERT jest automatycznie sprawdzany pod kątem ograniczeń: NOT NULL, UNIQUE, FOREIGN KEY. Jeśli wiersz narusza któreś z ograniczeń, MariaDB odrzuca cały INSERT i zwraca błąd. W przypadku INSERT wielu wierszy w jednym poleceniu, domyślnie wszystkie są traktowane jako jedna transakcja (w InnoDB) – albo wszystkie zostaną dodane, albo żaden.

Wartość AUTO_INCREMENT jest generowana automatycznie. Można ją jawnie podać w INSERT, ale grozi to konfliktem – jeśli podamy istniejącą wartość, MariaDB zwróci błąd duplikatu klucza głównego. Można też zresetować licznik AUTO_INCREMENT po usunięciu danych: ALTER TABLE nazwa AUTO_INCREMENT = 1.

Dla kolumn z wartością domyślną (DEFAULT) można pominąć je w INSERT – MariaDB wstawi wartość domyślną. Dla kolumn dopuszczających NULL można pominąć wartość (wtedy będzie NULL) lub jawnie wstawić NULL. Różnica między DEFAULT a NULL: DEFAULT to zaprogramowana wartość (np. 0 dla kary), NULL to brak wartości.

26/60Import z pliku .sql (SOURCE)

Wczytywanie skryptów SQL z plików

Gotowe skrypty SQL (z rozszerzeniem .sql) zawierają zestaw poleceń do utworzenia bazy, tabel i wstawienia danych. MariaDB oferuje dwa sposoby importu: polecenie SOURCE w konsoli oraz przekierowanie wejścia z poziomu powłoki.

-- Metoda 1: SOURCE w konsoli mysql
mysql -u root -p
SOURCE /home/user/biblioteka.sql;

-- Metoda 2: przekierowanie z powłoki
mysql -u root -p biblioteka < /home/user/biblioteka.sql

-- Metoda 3: z pominięciem wyboru bazy (jeśli skrypt ma CREATE DATABASE)
mysql -u root -p < /home/user/biblioteka.sql

-- Import z wyświetlaniem postępu
mysql -u root -p --force --verbose biblioteka < dane.sql

-- Sprawdzenie co zostało zaimportowane
SHOW TABLES;
SELECT COUNT(*) FROM Ksiazki;
SOURCE działa tylko wewnątrz klienta mysql. Przekierowanie z < jest szybsze dla dużych plików.
Zrzut ekranu – proces importu pliku .sql z komunikatem Query OK

Pliki SQL to tekstowe skrypty zawierające sekwencję poleceń SQL. Ich struktura jest prosta: każde polecenie kończy się średnikiem, komentarze zaczynają się od -- lub /* */, a całość można edytować w dowolnym edytorze tekstu. To uniwersalny format wymiany danych – każdy system baz danych obsługuje import/eksport SQL.

Metoda z przekierowaniem (mysql < plik.sql) jest zalecana dla dużych plików, ponieważ nie wymaga interaktywnej sesji. MariaDB czyta plik linia po linii i wykonuje polecenia. Opcja --force kontynuuje import nawet po napotkaniu błędów (np. duplikatów), co jest przydatne przy ponownym imporcie. --verbose wyświetla każde wykonywane polecenie.

Plik SQL może zawierać polecenie USE biblioteka, co eliminuje konieczność podawania nazwy bazy w linii poleceń. Jeśli plik zawiera CREATE DATABASE IF NOT EXISTS i USE, to import jest w pełni samodzielny – nie wymaga żadnych parametrów poza danymi logowania. To standardowa praktyka w dystrybucji aplikacji webowych.

27/60LOAD DATA INFILE – import z CSV

Szybkie wczytywanie danych z plików CSV

LOAD DATA INFILE to najszybszy sposób importu dużych ilości danych z plików tekstowych. Plik CSV (Comma Separated Values) jest standardowym formatem wymiany danych między arkuszami kalkulacyjnymi a bazami SQL. Polecenie oferuje wiele opcji formatowania.

-- Przykład pliku czytelnicy.csv:
-- Kowalski;Jan;Główna 15;Warszawa;00-001;123456789;jan@example.com;2025-01-15

LOAD DATA INFILE '/tmp/czytelnicy.csv'
INTO TABLE Czytelnicy
FIELDS TERMINATED BY ';'
ENCLOSED BY '"'
LINES TERMINATED BY '\n'
IGNORE 1 LINES   -- pomija nagłówek CSV
SET Data_zap = CURDATE();  -- funkcja dla kolumny

-- Alternatywnie: mysqlimport (narzędzie konsolowe)
mysqlimport --local --fields-terminated-by=';' \
  --lines-terminated-by='\n' -u root -p biblioteka /tmp/czytelnicy.csv
Dla plików na serwerze użyj LOAD DATA INFILE. Dla plików lokalnych klienta – LOAD DATA LOCAL INFILE.
Plik CSV otwarty w edytorze, strzałki pokazujące mapowanie do kolumn

LOAD DATA INFILE jest znacznie szybszy niż INSERT dla tysięcy wierszy – ładuje dane bezpośrednio do silnika składowania, pomijając warstwę SQL. W przypadku InnoDB można dodatkowo przyspieszyć import, wyłączając tymczasowo autocommit i klucze obce: SET autocommit=0; SET foreign_key_checks=0; przed LOAD DATA.

Opcje FIELDS i LINES definiują format pliku. TERMINATED BY określa separator kolumn (najczęściej przecinek lub średnik). ENCLOSED BY mówi, czy wartości są otoczone cudzysłowem. LINES TERMINATED BY określa znak końca wiersza (\n dla Linux, \r\n dla Windows). IGNORE 1 LINES przydaje się, gdy pierwsza linia zawiera nagłówki kolumn.

Bezpieczeństwo: LOAD DATA INFILE wymaga uprawnienia FILE. Plik źródłowy musi znajdować się na serwerze (lub w katalogu wskazanym przez secure_file_priv). LOAD DATA LOCAL INFILE ładuje plik z komputera klienta, ale wymaga włączonej opcji local-infile po stronie serwera i klienta. Z powodów bezpieczeństwa LOCAL INFILE jest często wyłączony.

28/60SELECT podstawy + aliasy + CONCAT

Pobieranie danych z tabel

SELECT to najważniejsze polecenie SQL – służy do pobierania danych. Aliasy (AS) nadają kolumnom czytelne nazwy. Funkcja CONCAT łączy łańcuchy znaków. Możemy wybierać konkretne kolumny, używać wyrażeń i funkcji.

-- Wybór wszystkich kolumn (używaj ostrożnie!)
SELECT * FROM Czytelnicy;

-- Wybór konkretnych kolumn z aliasem
SELECT
    Nazwisko AS 'Nazwisko czytelnika',
    Imie,
    Data_zap AS Data_rejestracji
FROM Czytelnicy;

-- CONCAT – łączenie kolumn tekstowych
SELECT
    CONCAT(Imie, ' ', Nazwisko) AS Pelne_nazwisko,
    Email
FROM Czytelnicy;

-- Wyrażenia w SELECT
SELECT
    Tytul,
    Rok_wyd,
    (YEAR(NOW()) - Rok_wyd) AS Wiek_ksiazki
FROM Ksiazki;
SELECT * jest wygodne, ale w produkcji wypisuj konkretne kolumny – to szybsze i bezpieczniejsze.
Wynik SELECT z CONCAT – tabela z połączonymi kolumnami

SELECT to najczęściej używane polecenie SQL – według statystyk stanowi około 80% wszystkich zapytań wykonywanych na typowej bazie danych. Jego elastyczność pozwala na wybór kolumn, obliczenia, grupowanie, sortowanie i łączenie danych z wielu tabel. Opcja DISTINCT eliminuje duplikaty z wyniku.

Aliasy z AS są kluczowe dla czytelności zapytań. Gdy używamy funkcji (CONCAT, YEAR) lub wyrażeń, kolumna wynikowa nie ma nazwy – alias nadaje jej sensowną etykietę. Aliasy są też niezbędne przy łączeniu tabel, gdy kolumny o tej samej nazwie występują w wielu tabelach (np. Czytelnicy.Nazwisko vs Pracownicy.Nazwisko).

CONCAT łączy dwa lub więcej łańcuchów w jeden. CONCAT_WS (CONCAT With Separator) dodaje separator między elementami: CONCAT_WS(' ', Imie, Nazwisko). Ważne: CONCAT zwraca NULL, jeśli którykolwiek argument jest NULL. W takich przypadkach użyj CONCAT_WS, który pomija NULL-e, lub IFNULL do zabezpieczenia przed NULL-em.

29/60SELECT DISTINCT, ORDER BY, LIMIT, OFFSET

Eliminacja duplikatów, sortowanie i stronicowanie

DISTINCT usuwa duplikaty z wyniku. ORDER BY sortuje wiersze według podanych kolumn. LIMIT ogranicza liczbę zwracanych wierszy, OFFSET pomija określoną liczbę wierszy – to podstawa stronicowania wyników.

-- Unikalne kategorie książek
SELECT DISTINCT Kategoria
FROM Ksiazki;

-- Sortowanie rosnące (ASC) i malejące (DESC)
SELECT Tytul, Autor, Rok_wyd
FROM Ksiazki
ORDER BY Rok_wyd DESC;

-- Sortowanie wielopoziomowe
SELECT Autor, Tytul, Rok_wyd
FROM Ksiazki
ORDER BY Autor ASC, Rok_wyd DESC;

-- Stronicowanie – pierwsze 5 książek
SELECT Tytul, Autor
FROM Ksiazki
LIMIT 5;

-- Stronicowanie – strony 3 (wiersze 11-15)
SELECT Tytul, Autor
FROM Ksiazki
ORDER BY Tytul
LIMIT 5
OFFSET 10;
OFFSET dotyczy wierszy, które zostały znalezione – nie stron w sensie wizualnym. OFFSET 10 pomija 10 pierwszych wierszy wyniku.
Tabela z wynikami SELECT – DISTINCT, ORDER BY, LIMIT

DISTINCT działa na wszystkich wybranych kolumnach – jeśli SELECT DISTINCT Autor, Kategoria, to duplikaty są usuwane tylko gdy obie kolumny są identyczne. DISTINCT wymaga sortowania wyników, co może spowalniać zapytania na dużych tabelach. Alternatywą jest GROUP BY, które daje większą kontrolę nad agregacją.

ORDER BY domyślnie sortuje rosnąco (ASC). Sortowanie malejące wymaga DESC. Można sortować według wielu kolumn – najpierw według pierwszej, a w przypadku równości według drugiej. Sortowanie według aliasu (ORDER BY Wiek_ksiazki) też działa. Ważne: ORDER BY jest wykonywane po WHERE, ale przed LIMIT.

LIMIT i OFFSET to podstawa paginacji w aplikacjach webowych. LIMIT 5 OFFSET 10 zwraca 5 wierszy, pomijając pierwsze 10. W praktyce zamiast OFFSET często używa się metody "kursora" (WHERE id > ostatni_id LIMIT 5), która jest wydajniejsza dla dużych zbiorów danych, ponieważ nie wymaga przeliczania wszystkich poprzednich wierszy.

30/60WHERE + AND/OR/NOT – filtrowanie danych

Selekcja wierszy spełniających warunki

Klauzula WHERE filtruje wiersze przed zwróceniem wyniku. Operatory logiczne AND, OR, NOT łączą i negują warunki. WHERE to najpotężniejsze narzędzie do precyzyjnego wybierania potrzebnych danych.

-- Pojedynczy warunek
SELECT * FROM Ksiazki
WHERE Kategoria = 'Fantastyka';

-- AND – oba warunki muszą być spełnione
SELECT * FROM Ksiazki
WHERE Autor = 'Stanisław Lem'
  AND Rok_wyd > 1950;

-- OR – jeden z warunków
SELECT Tytul, Autor
FROM Ksiazki
WHERE Kategoria = 'SF'
   OR Kategoria = 'Fantastyka';

-- NOT – negacja warunku
SELECT Tytul, Autor, Rok_wyd
FROM Ksiazki
WHERE NOT Rok_wyd > 2000;

-- Łączenie AND, OR z nawiasami
SELECT * FROM Czytelnicy
WHERE Miasto = 'Warszawa'
  AND (Kara > 0 OR Telefon IS NULL);
Kolejność operatorów: NOT > AND > OR. Używaj nawiasów dla jasności, nawet gdy nie są wymagane.
Diagram Venna ilustrujący AND, OR, NOT w WHERE

WHERE to klauzula selekcji – określa, które wiersze trafią do wyniku zapytania. Wykonywana jest przed GROUP BY i ORDER BY. MariaDB optymalizuje WHERE przy użyciu indeksów – jeśli warunek dotyczy indeksowanej kolumny, baza od razu znajduje pasujące wiersze bez przeszukiwania całej tabeli.

Porównanie z NULL wymaga specjalnej składni: IS NULL lub IS NOT NULL. NULL nie równa się niczemu – nawet NULL = NULL daje NULL, a nie TRUE. To często źródło błędów u początkujących. Jeśli kolumna może zawierać NULL, a chcemy porównać z wartością, musimy uwzględnić oba przypadki: WHERE kolumna = 'wartość' OR kolumna IS NULL.

Łączenie warunków wymaga ostrożności z nawiasami. Warunek WHERE A OR B AND C jest interpretowany jako A OR (B AND C) z powodu priorytetu AND nad OR. Jeśli chcemy (A OR B) AND C, musimy jawnie dodać nawiasy. To częsty błąd prowadzący do nieoczekiwanych wyników – zawsze grupowuj operatory OR w nawiasy dla czytelności.

31/60LIKE, IN, BETWEEN – zaawansowane filtrowanie

Wzorce, listy i zakresy w WHERE

LIKE dopasowuje wzorce tekstowe z użyciem symboli wieloznacznych: % (dowolny ciąg znaków) i _ (pojedynczy znak). IN sprawdza przynależność do listy. BETWEEN określa zakres wartości (włączny). To trzy niezwykle przydatne operatory filtrujące.

-- LIKE – książki zaczynające się na "W"
SELECT Tytul, Autor FROM Ksiazki
WHERE Tytul LIKE 'W%';

-- LIKE – nazwiska zawierające "ski"
SELECT Nazwisko, Imie FROM Czytelnicy
WHERE Nazwisko LIKE '%ski%';

-- LIKE – dokładnie 6 znaków
SELECT Tytul FROM Ksiazki
WHERE Tytul LIKE '______';

-- IN – lista kategorii
SELECT Tytul, Kategoria FROM Ksiazki
WHERE Kategoria IN ('SF', 'Fantastyka', 'Powieść');

-- BETWEEN – zakres lat (włącznie)
SELECT Tytul, Rok_wyd FROM Ksiazki
WHERE Rok_wyd BETWEEN 1950 AND 2000;

-- BETWEEN z datami
SELECT * FROM Wypozyczenia
WHERE Data_wyp BETWEEN '2025-01-01' AND '2025-03-31';
LIKE '%tekst%' nie używa indeksów (chyba że mamy indeks FULLTEXT). LIKE 'tekst%' – tak, używa indeksu.
Tabela z wynikami LIKE, IN, BETWEEN – porównanie

LIKE z % na początku wzorca (%ski) nie może skorzystać z indeksu B-drzewo – wymaga pełnego przeskanowania tabeli (full table scan). LIKE z % na końcu (W%) może użyć indeksu, co jest znacznie szybsze. Dla wyszukiwania pełnotekstowego w dużych zbiorach danych lepiej użyć indeksu FULLTEXT i operatora MATCH ... AGAINST.

Znak _ w LIKE reprezentuje dokładnie jeden dowolny znak. LIKE '_____' (5 podkreśleń) znajdzie 5-znakowe tytuły. Jeśli szukamy dosłownego % lub _, używamy ESCAPE: LIKE '100\%' ESCAPE '\' zwróci tekst "100%". Znak ucieczki można zdefiniować dowolnie – ESCAPE '=' oznacza, że =% to dosłowny znak procenta.

IN jest równoważne kilku warunkom OR, ale czytelniejsze i często szybsze. MariaDB potrafi zoptymalizować IN dla stałych wartości. BETWEEN jest włączny – BETWEEN 1950 AND 2000 obejmuje 1950 i 2000. Dla dat BETWEEN '2025-01-01' AND '2025-03-31' obejmuje cały 31 marca do północy.

32/60ORDER BY szczegółowo

Zaawansowane sortowanie wyników

ORDER BY może sortować według wielu kolumn, wyrażeń, funkcji, a nawet według pozycji kolumny w SELECT. Można też sortować według aliasów i mieszać kierunki (ASC/DESC) dla różnych kolumn. Dla języka polskiego ważna jest odpowiednia collation.

-- Sortowanie według pozycji kolumny (3. kolumna)
SELECT Tytul, Autor, Rok_wyd
FROM Ksiazki
ORDER BY 3 DESC;

-- Sortowanie według wyrażenia
SELECT Tytul, Rok_wyd
FROM Ksiazki
ORDER BY (YEAR(NOW()) - Rok_wyd) DESC;

-- Sortowanie z uwzględnieniem polskich znaków
SELECT Nazwisko, Imie
FROM Czytelnicy
ORDER BY Nazwisko COLLATE utf8mb4_polish_ci;

-- Sortowanie z NULL-ami na końcu
SELECT Tytul, Data_zwrot
FROM Wypozyczenia
ORDER BY Data_zwrot IS NULL, Data_zwrot;

-- Losowa kolejność
SELECT Tytul
FROM Ksiazki
ORDER BY RAND()
LIMIT 3;
ORDER BY RAND() jest kosztowne dla dużych tabel – dla małych zbiorów danych działa dobrze.
Tabela przed i po sortowaniu ORDER BY różnymi kolumnami

ORDER BY według pozycji (ORDER BY 3) to skrót, który sortuje według trzeciej kolumny w SELECT. Jest to wygodne, ale ryzykowne – zmiana kolejności kolumn w SELECT zmieni znaczenie ORDER BY. W kodzie produkcyjnym lepiej jawnie podawać nazwy kolumn.

Sortowanie NULL-i: domyślnie NULL-e w sortowaniu rosnącym pojawiają się na początku (są traktowane jako mniejsze od wszystkich wartości). W sortowaniu malejącym NULL-e są na końcu. Można to zmienić za pomocą ORDER BY kolumna IS NULL, kolumna – to sortuje najpierw nie-NULL-e, potem NULL-e.

Collation ma kluczowe znaczenie dla poprawnego sortowania w języku polskim. utf8mb4_polish_ci rozpoznaje polskie znaki diakrytyzowane: ą, ć, ę, ł, ń, ó, ś, ź, ż i sortuje je zgodnie z polskimi regułami. Bez tej collation polskie znaki mogą być sortowane na końcu lub w losowej kolejności.

33/60GROUP BY – grupowanie danych

Agregacja danych według grup

GROUP BY grupuje wiersze o tych samych wartościach w podanych kolumnach i umożliwia wykonywanie funkcji agregujących na każdej grupie. To podstawowe narzędzie do tworzenia raportów i zestawień.

-- Liczba książek w każdej kategorii
SELECT Kategoria,
       COUNT(*) AS Liczba_ksiazek
FROM Ksiazki
GROUP BY Kategoria;

-- Średnia rok wydania książek według autora
SELECT Autor,
       AVG(Rok_wyd) AS Sredni_rok
FROM Ksiazki
GROUP BY Autor;

-- Grupowanie według wielu kolumn
SELECT Miasto,
       YEAR(Data_zap) AS Rok_rejestracji,
       COUNT(*) AS Liczba
FROM Czytelnicy
GROUP BY Miasto, Rok_rejestracji;

-- Wyłączenie trybu ONLY_FULL_GROUP_BY (jeśli potrzebne)
SET sql_mode = '';
Każda kolumna w SELECT, która nie jest funkcją agregującą, musi być wymieniona w GROUP BY (tryb ONLY_FULL_GROUP_BY).
Schemat – dane przed GROUP BY (10 wierszy) i po (3 grupy)

GROUP BY to jedna z najpotężniejszych klauzul SQL. Dzieli tabelę na grupy wierszy o tych samych wartościach w kolumnie grupującej. Dla każdej grupy możemy wykonać funkcje agregujące: COUNT (liczba wierszy), SUM (suma), AVG (średnia), MIN (minimum), MAX (maksimum). Wynikiem jest jeden wiersz na grupę.

Tryb ONLY_FULL_GROUP_BY (domyślnie włączony w MariaDB 10.2+) wymaga, aby wszystkie kolumny w SELECT (oprócz agregowanych) znalazły się w GROUP BY. To zapobiega niejednoznacznościom – bez tego trybu MariaDB wybiera przypadkową wartość z grupy dla kolumn spoza GROUP BY. Wyłączenie tego trybu może prowadzić do błędów logicznych.

Grupowanie po wyrażeniu (jak YEAR(Data_zap)) jest dozwolone. Aliasu z SELECT nie można użyć w GROUP BY w standardowym SQL, ale MariaDB na to pozwala. W praktyce lepiej powtórzyć wyrażenie w GROUP BY dla przenośności między systemami baz danych.

34/60Funkcje agregujące: COUNT, SUM, AVG, MIN, MAX

Statystyki i podsumowania danych

Funkcje agregujące obliczają wartości na podstawie całych grup wierszy. COUNT zlicza wiersze, SUM sumuje wartości, AVG oblicza średnią, MIN i MAX znajdują minimalną i maksymalną wartość. Funkcje te pomijają NULL-e (oprócz COUNT(*)).

-- COUNT(*) – wszystkie wiersze
SELECT COUNT(*) AS Wszyscy_czytelnicy
FROM Czytelnicy;

-- COUNT(kolumna) – pomija NULL-e
SELECT COUNT(Email) AS Z_emailem
FROM Czytelnicy;

-- COUNT(DISTINCT) – unikalne wartości
SELECT COUNT(DISTINCT Miasto) AS Liczba_miast
FROM Czytelnicy;

-- SUM, AVG, MIN, MAX
SELECT
    SUM(Kara) AS Suma_kary,
    AVG(Kara) AS Srednia_kara,
    MIN(Kara) AS Minimalna_kara,
    MAX(Kara) AS Maksymalna_kara
FROM Czytelnicy;

-- SUM z warunkiem (funkcja warunkowa)
SELECT
    SUM(IF(Kara > 0, 1, 0)) AS Liczba_z_kara
FROM Czytelnicy;
COUNT(*) zlicza wszystkie wiersze. COUNT(kolumna) zlicza tylko wiersze, gdzie kolumna nie jest NULL.
Tabela z wynikami COUNT, SUM, AVG, MIN, MAX

Różnica między COUNT(*) a COUNT(kolumna) jest często mylona. COUNT(*) zlicza wszystkie wiersze w grupie, niezależnie od wartości NULL. COUNT(kolumna) zlicza tylko te wiersze, gdzie kolumna ma wartość nie-NULL. Dla kolumny Kara z 10 wierszami, gdzie 3 mają NULL, COUNT(*) = 10, COUNT(Kara) = 7.

SUM i AVG automatycznie pomijają NULL-e. AVG to SUM / COUNT(kolumna) (nie COUNT(*)). Jeśli chcemy wliczać NULL-e do średniej jako 0, musimy użyć AVG(IFNULL(kolumna, 0)). MIN i MAX działają dla typów liczbowych, tekstowych i dat. Dla tekstów MIN to wartość najwcześniejsza alfabetycznie, MAX – najpóźniejsza.

COUNT(DISTINCT kolumna) zlicza unikalne wartości, pomijając NULL-e. To wydajniejsza alternatywa dla podzapytań z DISTINCT. W MariaDB COUNT(DISTINCT) można łączyć z GROUP BY – wtedy zliczamy unikalne wartości w każdej grupie.

35/60HAVING – filtrowanie po agregacji

Warunki na wynikach grupowania

HAVING działa podobnie do WHERE, ale na zagregowanych wynikach po GROUP BY. WHERE filtruje wiersze przed grupowaniem, HAVING – po grupowaniu. To kluczowa różnica: w HAVING możemy odwoływać się do funkcji agregujących.

-- Kategorie z więcej niż 2 książkami
SELECT Kategoria,
       COUNT(*) AS Liczba
FROM Ksiazki
GROUP BY Kategoria
HAVING Liczba > 2;

-- Autorzy ze średnią rokiem wydania po 1950
SELECT Autor,
       AVG(Rok_wyd) AS Sredni_rok
FROM Ksiazki
GROUP BY Autor
HAVING Sredni_rok > 1950;

-- WHERE przed GROUP BY + HAVING po
SELECT Autor,
       COUNT(*) AS Liczba_ks
FROM Ksiazki
WHERE Rok_wyd > 1900     -- przed grupowaniem
GROUP BY Autor
HAVING Liczba_ks >= 2;    -- po grupowaniu
HAVING działa na wynikach GROUP BY, a nie na pojedynczych wierszach. WHERE i HAVING mogą być używane razem.
WHERE vs HAVING – WHERE przed GROUP BY, HAVING po GROUP BY

Kolejność wykonywania zapytania SQL: FROM -> JOIN -> WHERE -> GROUP BY -> HAVING -> SELECT -> ORDER BY -> LIMIT. WHERE filtruje wiersze przed grupowaniem (oszczędza czas), GROUP BY grupuje, HAVING odrzuca całe grupy, które nie spełniają warunku. To wyjaśnia, dlaczego WHERE nie może używać funkcji agregujących – te istnieją dopiero po GROUP BY.

HAVING może odwoływać się do aliasów z SELECT (jak Liczba > 2). To wygodne, ponieważ nie musimy powtarzać funkcji agregującej. MariaDB jest pod tym względem bardziej elastyczna niż standard SQL. W standardzie SQL aliasy w HAVING nie są dozwolone – trzeba powtórzyć COUNT(*) > 2.

Typowy błąd: użycie WHERE z funkcją agregującą zamiast HAVING. WHERE COUNT(*) > 2 zawsze zwróci błąd, ponieważ COUNT(*) nie jest dostępne przed grupowaniem. Zapamiętaj: WHERE = przed grupowaniem (pojedyncze wiersze), HAVING = po grupowaniu (zagregowane grupy).

36/60UPDATE – modyfikacja danych

Zmiana wartości w istniejących wierszach

UPDATE modyfikuje dane w istniejących wierszach. Bez WHERE zaktualizuje wszystkie wiersze w tabeli – to jedna z najniebezpieczniejszych operacji w SQL. Zawsze sprawdzaj warunek WHERE przed wykonaniem UPDATE.

-- UWAGA! Aktualizacja WSZYSTKICH wierszy
UPDATE Ksiazki
SET Liczba_egz = 1;

-- Bezpieczna aktualizacja z WHERE
UPDATE Ksiazki
SET Liczba_egz = 3
WHERE Tytul = 'Wiedźmin';

-- Zwiększenie wartości o 1
UPDATE Ksiazki
SET Liczba_egz = Liczba_egz + 1
WHERE Autor = 'Andrzej Sapkowski';

-- Aktualizacja wielu kolumn
UPDATE Czytelnicy
SET
    Telefon = '987654321',
    Email = 'nowy@example.com',
    Kara = Kara + 5.00
WHERE Nr_czytel = 1;
Przed UPDATE wykonaj SELECT z tym samym WHERE, aby zobaczyć, które wiersze zostaną zmodyfikowane.
Przed i po UPDATE – zmiana wartości w wybranych wierszach

UPDATE to operacja DML, która zmienia istniejące dane. W MariaDB z InnoDB każde UPDATE jest wykonywane w ramach transakcji (jeśli autocommit jest włączony, każdy UPDATE jest automatycznie zatwierdzany). Dla bezpieczeństwa można wyłączyć autocommit (SET autocommit=0) i wykonać ROLLBACK w razie pomyłki.

WHERE w UPDATE jest kluczowe – pominięcie WHERE powoduje aktualizację wszystkich wierszy w tabeli. Zawsze najpierw wykonaj SELECT z tym samym warunkiem, aby sprawdzić, które wiersze zostaną zmodyfikowane. W MariaDB można włączyć tryb bezpieczny (--safe-updates), który blokuje UPDATE i DELETE bez WHERE z kluczem.

UPDATE z wyrażeniem (Liczba_egz + 1) to częsta konstrukcja – zwiększa wartość istniejącą o 1 bez potrzeby odczytywania jej wcześniej. Działa atomowo: MariaDB odczytuje bieżącą wartość, dodaje 1 i zapisuje wynik. W środowiskach współbieżnych to bezpieczniejsze niż osobny SELECT + UPDATE.

37/60UPDATE z WHERE i wyrażeniami warunkowymi

Zaawansowane aktualizacje z użyciem CASE i podzapytań

UPDATE można łączyć z instrukcją warunkową CASE do aktualizacji różnych wartości w zależności od warunku. Można też używać podzapytań w SET i WHERE, aby odwołać się do danych z innych tabel.

-- CASE WHEN – warunkowa zmiana wartości
UPDATE Ksiazki
SET Kategoria =
    CASE
        WHEN Rok_wyd < 1800 THEN 'Stary druk'
        WHEN Rok_wyd < 1950 THEN 'Klasyka'
        ELSE Kategoria   -- pozostaje bez zmian
    END;

-- Podzapytanie w SET
UPDATE Czytelnicy
SET Kara = (
    SELECT SUM(Kara)
    FROM Czytelnicy
) / (SELECT COUNT(*) FROM Czytelnicy)
WHERE Nr_czytel = 1;

-- UPDATE z JOIN (aktualizacja na podstawie innej tabeli)
UPDATE Czytelnicy c
JOIN Wypozyczenia w ON c.Nr_czytel = w.Nr_czytel
SET c.Kara = c.Kara + 2.00
WHERE w.Data_zwrot IS NULL
  AND w.Data_wyp < DATE_SUB(NOW(), INTERVAL 30 DAY);
UPDATE z JOIN to potężne narzędzie – pozwala aktualizować dane w jednej tabeli na podstawie wartości z innej.
Schemat – UPDATE z JOIN łączący Czytelnicy z Wypozyczenia

CASE WHEN w UPDATE to elastyczny sposób na warunkową modyfikację danych. Każdy wiersz jest sprawdzany pod kątem warunków w kolejności – pierwszy spełniony warunek decyduje o nowej wartości. ELSE jest opcjonalny – jeśli go pominiemy, a żaden warunek nie jest spełniony, wartość pozostaje niezmieniona.

UPDATE z podzapytaniem w SET jest przydatne, gdy chcemy ustawić wartość na podstawie zagregowanych danych. W przykładzie ustawiamy karę na średnią wszystkich kar. Podzapytania w UPDATE muszą zwracać pojedynczą wartość – w przeciwnym razie MariaDB zgłosi błąd.

UPDATE z JOIN to najbardziej zaawansowana forma. W przykładzie nakładamy karę 2 zł na czytelników, którzy mają wypożyczenia przeterminowane o ponad 30 dni (brak daty zwrotu, a data wypożyczenia starsza niż 30 dni). Klauzula JOIN łączy tabele, a WHERE filtruje wiersze do aktualizacji. To typowy scenariusz biznesowy w systemie bibliotecznym.

38/60DELETE – usuwanie wierszy

Usuwanie danych z tabel

DELETE usuwa wiersze z tabeli. Podobnie jak UPDATE, DELETE bez WHERE usuwa wszystkie wiersze – ale tabela pozostaje (w przeciwieństwie do DROP TABLE). Dla tabel z kluczami obcymi ON DELETE CASCADE usunięcie wiersza może kaskadowo usunąć powiązane dane.

-- Usunięcie konkretnego wiersza
DELETE FROM Ksiazki
WHERE Nr_ks = 5;

-- Usunięcie wszystkich książek danej kategorii
DELETE FROM Ksiazki
WHERE Kategoria = 'Stary druk';

-- Usunięcie z użyciem podzapytania
DELETE FROM Wypozyczenia
WHERE Nr_czytel IN (
    SELECT Nr_czytel
    FROM Czytelnicy
    WHERE Miasto = 'Warszawa'
);

-- DELETE z JOIN (MariaDB składnia)
DELETE w
FROM Wypozyczenia w
JOIN Ksiazki k ON w.Nr_ks = k.Nr_ks
WHERE k.Autor = 'Nieznany';

-- Usunięcie wszystkich wierszy (szybkie)
DELETE FROM Wypozyczenia;
DELETE nie resetuje licznika AUTO_INCREMENT. Po DELETE wstawione dane dostaną kolejne wartości AUTO_INCREMENT.
Tabela przed i po DELETE – usunięte wiersze znikają

DELETE z JOIN pozwala usuwać wiersze z jednej tabeli na podstawie warunków dotyczących innych tabel. W przykładzie usuwamy wypożyczenia powiązane z książkami nieznanego autora. Składnia DELETE w FROM Wypozyczenia w JOIN Ksiazki k ... oznacza: usuń z Wypozyczenia (alias w) tylko te wiersze, które spełniają warunek JOIN z Ksiazki.

Bezpieczeństwo: przed DELETE warto wykonać SELECT z tym samym WHERE, aby zobaczyć, które wiersze zostaną usunięte. W MariaDB można użyć START TRANSACTION przed DELETE i ROLLBACK po sprawdzeniu, czy usunięto właściwe dane. W środowiskach produkcyjnych często stosuje się "miękkie usuwanie" – kolumna aktywny TINYINT(1) DEFAULT 1 zamiast fizycznego usuwania.

DELETE w InnoDB nie zwalnia miejsca na dysku – oznacza wiersze jako usunięte, ale przestrzeń pozostaje zarezerwowana dla przyszłych INSERT-ów. Aby fizycznie odzyskać miejsce, użyj OPTIMIZE TABLE lub ALTER TABLE ... ENGINE=InnoDB. Dla tabel MyISAM DELETE natychmiast zwalnia miejsce.

39/60DELETE vs TRUNCATE vs DROP – porównanie

Trzy sposoby usuwania danych – kiedy czego użyć?

DELETE, TRUNCATE i DROP to trzy różne operacje usuwania. DELETE usuwa wiersze (można cofnąć w transakcji). TRUNCATE usuwa wszystkie wiersze i resetuje AUTO_INCREMENT (szybciej niż DELETE). DROP usuwa całą tabelę wraz ze strukturą.

-- DELETE – usuwanie wierszy (wolne, ale z WHERE)
DELETE FROM Wypozyczenia
WHERE Data_zwrot IS NOT NULL;

-- TRUNCATE – szybkie usunięcie wszystkich danych
TRUNCATE TABLE Wypozyczenia;

-- DROP – usunięcie całej tabeli
DROP TABLE IF EXISTS Wypozyczenia;

-- Różnica w AUTO_INCREMENT po DELETE vs TRUNCATE
INSERT INTO Ksiazki (Tytul, Autor)
VALUES ('Nowa książka', 'Autor');
-- DELETE: AUTO_INCREMENT kontynuuje, TRUNCATE: resetuje do 1
TRUNCATE to operacja DDL – nie można jej cofnąć transakcją. DELETE to DML – można cofnąć jeśli jest w transakcji.
Tabela porównawcza DELETE vs TRUNCATE vs DROP

DELETE z warunkiem WHERE to jedyna opcja, gdy chcemy usunąć tylko część wierszy. DELETE usuwa wiersz po wierszu, rejestrując każdy w dzienniku transakcji – to sprawia, że dla dużych tabel DELETE może być bardzo wolny. DELETE można przerwać i cofnąć w ramach transakcji.

TRUNCATE to operacja DDL, która działa inaczej: usuwa wszystkie wiersze przez deallokację stron danych, co jest znacznie szybsze niż DELETE (szczególnie dla dużych tabel). TRUNCATE resetuje licznik AUTO_INCREMENT do 1. Nie można użyć WHERE. TRUNCATE nie wywołuje triggerów DELETE. W InnoDB TRUNCATE jest wykonywane jako DROP TABLE + CREATE TABLE.

DROP TABLE usuwa zarówno strukturę, jak i dane. Po DROP tabela nie istnieje – trzeba ją odtworzyć przez CREATE TABLE. DROP zwalnia całe miejsce zajmowane przez tabelę. W praktyce: używaj DELETE do usuwania wybranych wierszy, TRUNCATE do czyszczenia tabeli z zachowaniem struktury, DROP gdy tabela nie jest już potrzebna.

40/60ALTER TABLE – MODIFY, CHANGE, RENAME

Zmiana definicji kolumn i nazw tabel

ALTER TABLE oferuje trzy dodatkowe operacje: MODIFY zmienia typ i atrybuty kolumny (ale nie nazwę), CHANGE zmienia nazwę i typ kolumny, RENAME zmienia nazwę tabeli. To niezbędne narzędzia do ewolucji schematu bazy.

-- MODIFY – zmiana typu kolumny
ALTER TABLE Ksiazki
MODIFY Liczba_egz SMALLINT DEFAULT 1;

-- MODIFY – zmiana atrybutów (NOT NULL)
ALTER TABLE Czytelnicy
MODIFY Email VARCHAR(100) NOT NULL;

-- CHANGE – zmiana nazwy i typu kolumny
ALTER TABLE Ksiazki
CHANGE Kategoria Gatunek VARCHAR(40);

-- RENAME – zmiana nazwy tabeli
RENAME TABLE Ksiazki TO Ksiazki_archiwum;

-- ALTER TABLE ... RENAME (równoważne)
ALTER TABLE Ksiazki_archiwum
RENAME TO Ksiazki;

-- Zmiana silnika składowania
ALTER TABLE Czytelnicy ENGINE = MyISAM;
CHANGE wymaga podania starej i nowej nazwy – jeśli nie zmieniasz nazwy, użyj MODIFY (który nie wymaga podawania nazwy dwukrotnie).
Schemat – MODIFY vs CHANGE vs RENAME w ALTER TABLE

MODIFY kolumna to najprostsza forma zmiany – pozwala zmienić typ danych, atrybuty (NULL/NOT NULL, DEFAULT) i pozycję kolumny (FIRST, AFTER). Nie można zmienić nazwy kolumny przez MODIFY. Jeśli zmieniasz tylko atrybuty, a typ pozostaje ten sam, musisz powtórzyć istniejący typ – w przeciwnym razie MariaDB może zmienić typ domyślnie.

CHANGE to rozszerzona wersja MODIFY – pozwala zmienić zarówno nazwę, jak i typ kolumny. Składnia: CHANGE stara_nazwa nowa_nazwa typ [atrybuty]. Jeśli chcesz zmienić tylko nazwę, musisz podać nową nazwę i powtórzyć istniejący typ. CHANGE jest szczególnie przydatne przy refaktoryzacji schematu.

RENAME TABLE (lub ALTER TABLE ... RENAME TO) zmienia nazwę tabeli. Operacja jest bezpieczna – nie wpływa na dane, indeksy ani klucze obce. Klucze obce wskazujące na tę tabelę muszą być zaktualizowane osobno. Zmiana silnika ENGINE (np. z InnoDB na MyISAM) powoduje przebudowę całej tabeli – dla dużych tabel może to trwać długo.

41/60Wprowadzenie do JOIN

Łączenie danych z wielu tabel

JOIN pozwala pobierać dane z wielu tabel jednocześnie, łącząc wiersze na podstawie powiązanych kolumn. Bez JOIN dane z różnych tabel trzeba by pobierać osobnymi zapytaniami i łączyć w aplikacji. JOIN robi to w jednym zapytaniu – wydajniej i czyściej.

-- Klasyczne łączenie w WHERE (stara składnia)
SELECT c.Nazwisko, c.Imie, w.Data_wyp
FROM Czytelnicy c, Wypozyczenia w
WHERE c.Nr_czytel = w.Nr_czytel;

-- JOIN – nowoczesna składnia (zalecana)
SELECT c.Nazwisko, c.Imie, w.Data_wyp
FROM Czytelnicy c
JOIN Wypozyczenia w ON c.Nr_czytel = w.Nr_czytel;

-- Rodzaje JOIN (poglądowo)
-- INNER JOIN – tylko pasujące wiersze
-- LEFT JOIN – wszystkie z lewej + pasujące z prawej
-- RIGHT JOIN – wszystkie z prawej + pasujące z lewej
Zawsze używaj jawnej składni JOIN zamiast przecinka w WHERE – jest czytelniejsza i trudniej o pomyłkę.
Diagram Venna – INNER JOIN, LEFT JOIN, RIGHT JOIN

JOIN to fundamentalna koncepcja relacyjnych baz danych. Łączy wiersze z dwóch (lub więcej) tabel na podstawie wspólnej kolumny – zazwyczaj klucza głównego z jednej tabeli i klucza obcego z drugiej. W naszej bibliotece: Czytelnicy.Nr_czytel to klucz główny, Wypozyczenia.Nr_czytel to klucz obcy.

Stara składnia z przecinkiem i WHERE (zwana implicit join) jest równoważna INNER JOIN, ale trudniejsza do czytania i bardziej podatna na błędy. W przypadku LEFT JOIN stara składnia nie działa poprawnie bez rozszerzeń specyficznych dla MariaDB. Nowoczesna składnia z jawnym JOIN jest standardem ANSI SQL i jest zalecana.

Aliasy tabel (c, w) skracają zapis i poprawiają czytelność. c.Nazwisko oznacza kolumnę Nazwisko z tabeli Czytelnicy (alias c). Aliasy są szczególnie ważne przy łączeniu wielu tabel, gdy kolumny mogą mieć te same nazwy w różnych tabelach. Bez aliasu MariaDB zgłosi błąd ambiguous column.

42/60INNER JOIN – łączenie wewnętrzne

Tylko pasujące wiersze z obu tabel

INNER JOIN zwraca tylko wiersze, które mają dopasowanie w obu tabelach. Jeśli czytelnik nie ma żadnych wypożyczeń – nie pojawi się w wyniku. To najczęściej używany rodzaj JOIN.

-- Kto wypożyczył książki i jakie?
SELECT c.Nazwisko, c.Imie,
       k.Tytul, w.Data_wyp
FROM Czytelnicy c
INNER JOIN Wypozyczenia w
    ON c.Nr_czytel = w.Nr_czytel
INNER JOIN Ksiazki k
    ON w.Nr_ks = k.Nr_ks;

-- INNER JOIN z dodatkowym warunkiem
SELECT c.Nazwisko, k.Tytul, w.Data_wyp
FROM Czytelnicy c
INNER JOIN Wypozyczenia w ON c.Nr_czytel = w.Nr_czytel
INNER JOIN Ksiazki k ON w.Nr_ks = k.Nr_ks
WHERE k.Kategoria = 'Fantastyka'
      AND w.Data_zwrot IS NULL;

-- Liczba wypożyczeń na czytelnika
SELECT c.Nazwisko,
       COUNT(w.Nr_wyp) AS Liczba_wypozyczen
FROM Czytelnicy c
INNER JOIN Wypozyczenia w ON c.Nr_czytel = w.Nr_czytel
GROUP BY c.Nr_czytel;
INNER JOIN i JOIN to to samo – JOIN domyślnie oznacza INNER JOIN.
Diagram Venna – INNER JOIN, przecięcie zbiorów A i B

INNER JOIN łączy trzy tabele w jednym zapytaniu: Czytelnicy -> Wypozyczenia -> Ksiazki. Każdy JOIN łączy się z poprzednim wynikiem. Kolejność JOIN-ów ma znaczenie dla wydajności – MariaDB stara się optymalizować kolejność, ale warto łączyć od najmniejszej tabeli. W przykładzie zaczynamy od Czytelnicy, przez Wypozyczenia do Ksiazki.

Warunek w WHERE po JOIN-ach filtruje końcowy wynik. W przykładzie szukamy aktualnie wypożyczonych książek z kategorii Fantastyka (Data_zwrot IS NULL oznacza, że książka nie została zwrócona). WHERE jest wykonywane po wszystkich JOIN-ach, co pozwala filtrować połączone dane z wielu tabel.

COUNT(w.Nr_wyp) w INNER JOIN pokazuje liczbę wypożyczeń na czytelnika. Ponieważ INNER JOIN pomija czytelników bez wypożyczeń, w wyniku nie zobaczymy czytelników z zerową liczbą wypożyczeń. Jeśli chcemy ich uwzględnić, użylibyśmy LEFT JOIN.

43/60LEFT JOIN – łączenie zewnętrzne lewostronne

Wszystkie wiersze z lewej tabeli + dopasowania z prawej

LEFT JOIN zwraca wszystkie wiersze z lewej tabeli (pierwszej w FROM) i pasujące wiersze z prawej. Jeśli nie ma dopasowania, kolumny z prawej tabeli są NULL. To najczęściej używany JOIN zewnętrzny – przydaje się do znajdowania "osieroconych" rekordów.

-- Wszyscy czytelnicy i ich wypożyczenia (jeśli istnieją)
SELECT c.Nazwisko, c.Imie,
       w.Data_wyp, w.Data_zwrot
FROM Czytelnicy c
LEFT JOIN Wypozyczenia w
    ON c.Nr_czytel = w.Nr_czytel;

-- Czytelnicy, którzy NIGDY nie wypożyczyli książki
SELECT c.Nazwisko, c.Imie
FROM Czytelnicy c
LEFT JOIN Wypozyczenia w
    ON c.Nr_czytel = w.Nr_czytel
WHERE w.Nr_wyp IS NULL;

-- Wszystkie książki i ich wypożyczenia
SELECT k.Tytul, k.Autor,
       w.Data_wyp
FROM Ksiazki k
LEFT JOIN Wypozyczenia w
    ON k.Nr_ks = w.Nr_ks
ORDER BY k.Tytul;
LEFT JOIN jest asymetryczne – zamiana tabel zmienia wynik. LEFT JOIN A ON ... to nie to samo co LEFT JOIN B ON ...
Diagram Venna – LEFT JOIN, cały zbiór A + przecięcie z B

LEFT JOIN jest kluczowe do znajdowania wierszy bez powiązań. WHERE w.Nr_wyp IS NULL znajduje czytelników, którzy nigdy nie wypożyczyli książki. Działa to, ponieważ dla czytelników bez wypożyczeń wszystkie kolumny z Wypozyczenia są NULL. To jeden z najczęstszych wzorców w SQL – znajdź rekordy bez dopasowania w drugiej tabeli.

LEFT JOIN jest też przydatne do raportów, gdzie potrzebujemy wszystkich elementów z listy głównej, nawet jeśli nie mają powiązanych danych. Na przykład lista wszystkich książek z informacją, czy są aktualnie wypożyczone. Jeśli książka nie jest wypożyczona, Data_wyp będzie NULL.

Wynik LEFT JOIN może zawierać wiele wierszy z lewej tabeli, jeśli w prawej jest wiele dopasowań. Jeśli czytelnik ma 5 wypożyczeń, pojawi się 5 wierszy z jego danymi (powtórzonymi) i różnymi danymi wypożyczeń. Do grupowania takich wyników używa się DISTINCT lub GROUP BY.

44/60RIGHT JOIN – łączenie zewnętrzne prawostronne

Wszystkie wiersze z prawej tabeli + dopasowania z lewej

RIGHT JOIN działa odwrotnie do LEFT JOIN – zwraca wszystkie wiersze z prawej tabeli i pasujące z lewej. W praktyce RIGHT JOIN jest rzadziej używane, ponieważ można je zastąpić LEFT JOIN przez zamianę tabel miejscami. Niemniej warto znać tę konstrukcję.

-- RIGHT JOIN – wszystkie wypożyczenia i dane książek
SELECT w.Nr_wyp, w.Data_wyp,
       k.Tytul, k.Autor
FROM Wypozyczenia w
RIGHT JOIN Ksiazki k
    ON w.Nr_ks = k.Nr_ks;

-- To samo co LEFT JOIN z zamianą tabel
SELECT w.Nr_wyp, w.Data_wyp,
       k.Tytul, k.Autor
FROM Ksiazki k
LEFT JOIN Wypozyczenia w
    ON k.Nr_ks = w.Nr_ks;

-- Książki, które nigdy nie były wypożyczone
SELECT k.Tytul, k.Autor
FROM Wypozyczenia w
RIGHT JOIN Ksiazki k
    ON w.Nr_ks = k.Nr_ks
WHERE w.Nr_wyp IS NULL;
RIGHT JOIN jest rzadko używane w praktyce – większość programistów woli LEFT JOIN i zamianę tabel dla czytelności.
Diagram Venna – RIGHT JOIN, cały zbiór B + przecięcie z A

RIGHT JOIN jest symetryczną odwrotnością LEFT JOIN. Jeśli LEFT JOIN przydaje się do znajdowania "osieroconych" rekordów w tabeli nadrzędnej (czytelnicy bez wypożyczeń), to RIGHT JOIN znajduje osierocone rekordy w tabeli podrzędnej w stosunku do głównej. W praktyce jednak łatwiej utrzymywać kod, konsekwentnie używając LEFT JOIN.

RIGHT JOIN może być mylące, ponieważ kierunek "prawo-lewo" zależy od kolejności tabel w FROM, a nie od logicznej zależności między tabelami. LEFT JOIN z zamienionymi tabelami daje identyczny wynik i jest bardziej intuicyjne – czytamy od lewej do prawej, tak jak piszemy.

W MariaDB RIGHT JOIN jest zaimplementowane przez wewnętrzną zamianę na LEFT JOIN w optymalizatorze zapytań. Nie ma więc różnicy wydajnościowej. Wybór między LEFT a RIGHT to kwestia preferencji i czytelności. W projektach zespołowych warto ustalić konwencję – zwykle LEFT JOIN jest standardem.

45/60FULL OUTER JOIN – symulacja przez UNION

Wszystkie wiersze z obu tabel – nawet bez dopasowania

MariaDB nie obsługuje bezpośrednio FULL OUTER JOIN (standardowy SQL łączy LEFT i RIGHT). Można go zasymulować przez UNION LEFT JOIN i RIGHT JOIN. FULL OUTER JOIN zwraca wszystkie wiersze z obu tabel, łącząc je gdzie to możliwe i uzupełniając NULL-ami brakujące dopasowania.

-- FULL OUTER JOIN symulacja przez UNION
SELECT c.Nazwisko, w.Nr_wyp
FROM Czytelnicy c
LEFT JOIN Wypozyczenia w
    ON c.Nr_czytel = w.Nr_czytel
UNION
SELECT c.Nazwisko, w.Nr_wyp
FROM Czytelnicy c
RIGHT JOIN Wypozyczenia w
    ON c.Nr_czytel = w.Nr_czytel
WHERE c.Nr_czytel IS NULL;

-- Prostsza symulacja (MariaDB 10.4+)
SELECT c.Nazwisko, w.Nr_wyp
FROM Czytelnicy c
LEFT JOIN Wypozyczenia w ON c.Nr_czytel = w.Nr_czytel
UNION ALL
SELECT c.Nazwisko, w.Nr_wyp
FROM Wypozyczenia w
LEFT JOIN Czytelnicy c ON w.Nr_czytel = c.Nr_czytel
WHERE c.Nr_czytel IS NULL;
UNION domyślnie usuwa duplikaty. UNION ALL zachowuje wszystkie wiersze – jest szybszy.
Diagram Venna – FULL OUTER JOIN, suma zbiorów A i B

FULL OUTER JOIN zwraca wszystkie wiersze z obu tabel – zarówno te z dopasowaniem, jak i te bez. Wiersze bez dopasowania mają NULL w kolumnach drugiej tabeli. To przydatne, gdy chcemy zobaczyć pełny obraz: wszystkich czytelników i wszystkie wypożyczenia w jednym wyniku.

Symulacja przez UNION działa przez połączenie LEFT JOIN (wszyscy czytelnicy + ich wypożyczenia) z RIGHT JOIN (wszystkie wypożyczenia + ich czytelnicy), przy czym w drugiej części dodajemy WHERE c.Nr_czytel IS NULL, aby wyeliminować duplikaty już zwrócone przez LEFT JOIN. UNION (bez ALL) też usuwa duplikaty, ale kosztem dodatkowego sortowania.

W MariaDB 10.4+ istnieje możliwość użycia FULL JOIN w stylu Oracle poprzez włączenie trybu kompatybilności: SET sql_mode='ORACLE';. Daje to dostęp do FULL OUTER JOIN, ale zmienia też inne zachowania (składnia anonimowych bloków PL/SQL itp.). W praktyce symulacja przez UNION jest bezpieczniejsza i przenośna między systemami.

46/60Łączenie wielu tabel – zaawansowane JOIN

Praktyczne przykłady łączenia 3+ tabel

W rzeczywistych bazach danych często potrzebujemy połączyć trzy, cztery lub więcej tabel w jednym zapytaniu. Każdy kolejny JOIN rozszerza wynik o dane z następnej tabeli. Ważna jest kolejność JOIN-ów i poprawne warunki łączenia.

-- Pełny raport wypożyczeń: czytelnik + książka + wypożyczenie
SELECT
    c.Nazwisko AS Czytelnik,
    k.Tytul AS Ksiazka,
    w.Data_wyp,
    w.Data_zwrot
FROM Czytelnicy c
INNER JOIN Wypozyczenia w ON c.Nr_czytel = w.Nr_czytel
INNER JOIN Ksiazki k ON w.Nr_ks = k.Nr_ks
ORDER BY c.Nazwisko, w.Data_wyp;

-- Czytelnicy i ich wypożyczenia (z LEFT JOIN dla wszystkich)
SELECT
    c.Nazwisko,
    COUNT(k.Tytul) AS Liczba_ksiazek,
    GROUP_CONCAT(k.Tytul ORDER BY k.Tytul SEPARATOR ', ') AS Tytuly
FROM Czytelnicy c
LEFT JOIN Wypozyczenia w ON c.Nr_czytel = w.Nr_czytel
LEFT JOIN Ksiazki k ON w.Nr_ks = k.Nr_ks
GROUP BY c.Nr_czytel
ORDER BY Liczba_ksiazek DESC;
GROUP_CONCAT to funkcja agregująca, która łączy wartości z wielu wierszy w jeden łańcuch – idealna do raportów.
Schemat – łączenie 3 tabel: Czytelnicy -> Wypozyczenia -> Ksiazki

Łączenie trzech tabel to standard w praktyce programistycznej. W przykładzie łączymy Czytelnicy z Wypozyczenia (po Nr_czytel) i Wypozyczenia z Ksiazki (po Nr_ks). Efektem jest pełna historia wypożyczeń z nazwiskami czytelników i tytułami książek. To typowy raport dla systemu bibliotecznego.

GROUP_CONCAT to funkcja agregująca specyficzna dla MySQL/MariaDB. Łączy wartości z grupy w jeden łańcuch, oddzielony separatorem (domyślnie przecinek). W przykładzie tworzy listę tytułów książek wypożyczonych przez każdego czytelnika. ORDER BY wewnątrz GROUP_CONCAT sortuje listę, SEPARATOR pozwala zmienić separator na dowolny.

Przy łączeniu wielu tabel kluczowe jest używanie LEFT JOIN zamiast INNER JOIN, gdy chcemy zachować wszystkie wiersze z głównej tabeli. W drugim przykładzie używamy LEFT JOIN dla obu połączeń, aby czytelnicy bez wypożyczeń również pojawili się w raporcie (z liczbą 0). Przy INNER JOIN zostaliby pominięci.

47/60Aliasy tabel – skracanie i unikanie konfliktów

Dlaczego aliasy są ważne?

Aliasy tabel (nazywane też korelacjami) to skrócone nazwy nadawane tabelom w zapytaniu. Są niezbędne, gdy ta sama tabela występuje wielokrotnie w jednym zapytaniu (self-join) lub gdy kolumny w różnych tabelach mają te same nazwy. Aliasy deklaruje się po nazwie tabeli z opcjonalnym AS.

-- Self-join: aliasy niezbędne przy łączeniu tabeli z samą sobą
-- Załóżmy, że mamy tabele Pracownicy z kolumną Szef (ID szefa)
SELECT
    p.Imie AS Pracownik,
    s.Imie AS Szef
FROM Pracownicy p
LEFT JOIN Pracownicy s ON p.Szef = s.ID;

-- Aliasy dla podzapytań
SELECT
    stat.Autor,
    stat.Liczba
FROM (
    SELECT Autor, COUNT(*) AS Liczba
    FROM Ksiazki
    GROUP BY Autor
) AS stat
WHERE stat.Liczba > 1;

-- Alias bez AS (skrócona składnia)
SELECT c.Nazwisko, k.Tytul
FROM Czytelnicy c
JOIN Wypozyczenia w ON c.Nr_czytel = w.Nr_czytel
JOIN Ksiazki k ON w.Nr_ks = k.Nr_ks;
Aliasy tabel są obowiązkowe przy self-join (łączeniu tabeli z samą sobą). Bez aliasów MariaDB nie rozróżni kopii tabel.
Self-join – ta sama tabela występuje dwukrotnie z różnymi aliasami

Self-join (łączenie tabeli z samą sobą) to klasyczny przypadek użycia aliasów. W przykładzie tabela Pracownicy zawiera kolumnę Szef przechowującą ID przełożonego. Aby w jednym wyniku zobaczyć zarówno pracownika, jak i jego szefa, musimy odwołać się do tej samej tabeli dwukrotnie – raz jako p (pracownik), raz jako s (szef).

Aliasy są też niezbędne w podzapytaniach w FROM (inline views). Podzapytanie tworzy tymczasową tabelę, która musi mieć nazwę – stąd alias stat. Bez aliasu MariaDB zwróci błąd "Every derived table must have its own alias". To częsty błąd początkujących – zawsze nadawaj alias podzapytaniu.

Składnia z aliasem może być z AS (Czytelnicy AS c) lub bez (Czytelnicy c). Obie formy są równoważne. W praktyce częściej spotyka się wersję bez AS, ponieważ jest krótsza. Jednak w kodzie dla początkujących warto używać AS dla jasności, że nadajemy alias.

48/60UNION – łączenie wyników zapytań

Pionowe łączenie zbiorów wynikowych

UNION łączy wyniki dwóch lub więcej zapytań SELECT w jeden zbiór wynikowy. Zapytania muszą mieć tę samą liczbę kolumn i zgodne typy danych. UNION domyślnie usuwa duplikaty, UNION ALL zachowuje wszystkie wiersze. To przydatne do łączenia danych z podobnych tabel.

-- UNION – lista wszystkich unikalnych autorów i kategorii
SELECT Autor AS Nazwa
FROM Ksiazki
UNION
SELECT Kategoria
FROM Ksiazki;

-- UNION ALL – szybszy, zachowuje duplikaty
SELECT Miasto FROM Czytelnicy
UNION ALL
SELECT Miasto FROM Czytelnicy
WHERE Miasto = 'Warszawa';

-- UNION z ORDER BY na końcu
SELECT Tytul, Rok_wyd FROM Ksiazki
WHERE Rok_wyd < 1900
UNION ALL
SELECT Tytul, Rok_wyd FROM Ksiazki
WHERE Rok_wyd >= 1900
ORDER BY Rok_wyd;
UNION sortuje wynik (usuwa duplikaty). UNION ALL nie sortuje – jest szybszy. Używaj UNION ALL gdy nie potrzebujesz unikalności.
UNION – dwa SELECT łączą się w jeden wynik (pionowo)

UNION przydaje się, gdy mamy dane rozproszone w kilku podobnych tabelach (np. zamowienia_2024, zamowienia_2025) i chcemy je połączyć w jeden raport. W nowoczesnych bazach takie partycjonowanie rzadko występuje na poziomie osobnych tabel, ale w starszych systemach bywa częste.

Wymagania UNION: każdy SELECT musi zwracać tę samą liczbę kolumn. Typy danych powinny być zgodne (MariaDB stara się konwertować automatycznie). Nazwy kolumn w wyniku pochodzą z pierwszego SELECT. ORDER BY na końcu sortuje cały wynik – nie można sortować poszczególnych części UNION osobno.

UNION ALL jest zazwyczaj szybszy od UNION, ponieważ nie wymaga sortowania i usuwania duplikatów. Jeśli dane z poszczególnych SELECT-ów na pewno się nie pokrywają (np. dane z różnych lat), używaj UNION ALL. Jeśli mogą wystąpić duplikaty, a chcesz je usunąć, użyj UNION.

49/60Podzapytania w WHERE

Zapytania zagnieżdżone w klauzuli WHERE

Podzapytanie (subquery) to zapytanie SELECT umieszczone wewnątrz innego zapytania. W WHERE podzapytania są najczęściej używane z operatorami IN, =, >, <, EXISTS. Podzapytanie jest wykonywane przed zapytaniem głównym (lub dla każdego wiersza – zależy od optymalizatora).

-- Podzapytanie z IN: czytelnicy, którzy wypożyczyli książki
SELECT Nazwisko, Imie
FROM Czytelnicy
WHERE Nr_czytel IN (
    SELECT DISTINCT Nr_czytel
    FROM Wypozyczenia
);

-- Podzapytanie z =: książka z najwcześniejszym rokiem
SELECT Tytul, Autor, Rok_wyd
FROM Ksiazki
WHERE Rok_wyd = (
    SELECT MIN(Rok_wyd)
    FROM Ksiazki
);

-- Podzapytanie skorelowane: czytelnicy z karą powyżej średniej
SELECT Nazwisko, Imie, Kara
FROM Czytelnicy c
WHERE Kara > (
    SELECT AVG(Kara)
    FROM Czytelnicy
);

-- Podzapytanie z NOT IN: czytelnicy bez wypożyczeń
SELECT Nazwisko, Imie
FROM Czytelnicy
WHERE Nr_czytel NOT IN (
    SELECT Nr_czytel
    FROM Wypozyczenia
);
NOT IN z podzapytaniem może zwracać puste wyniki, gdy podzapytanie zawiera NULL – w takich przypadkach użyj NOT EXISTS.
Schemat – podzapytanie w WHERE, wynik wewnętrznego SELECT trafia do zewnętrznego

Podzapytania w WHERE dzielą się na trzy typy. 1) Z IN – zwraca listę wartości (używane najczęściej). 2) Z operatorem skalarnym (=, >, <) – podzapytanie musi zwrócić dokładnie jedną wartość. 3) Skorelowane – podzapytanie odwołuje się do kolumn z zapytania głównego (jak c.Kara) i jest wykonywane dla każdego wiersza zewnętrznego.

Podzapytanie skorelowane z > (Kara > AVG) porównuje karę każdego czytelnika ze średnią wszystkich kar. Ponieważ AVG(Kara) nie zależy od wiersza zewnętrznego, jest to podzapytanie nieskorelowane – MariaDB oblicza średnią raz. Gdybyśmy użyli aliasu c w podzapytaniu (SELECT AVG(c2.Kara) FROM Czytelnicy c2), to byłoby skorelowane.

NOT IN z podzapytaniem ma pułapkę: jeśli podzapytanie zwróci chociaż jeden NULL, NOT IN zwróci pusty wynik (bo NULL = NULL daje NULL, co jest traktowane jako false). Bezpieczniejszą alternatywą jest NOT EXISTS, które poprawnie obsługuje NULL-e: WHERE NOT EXISTS (SELECT 1 FROM Wypozyczenia w WHERE w.Nr_czytel = c.Nr_czytel).

50/60EXISTS i NOT EXISTS

Sprawdzanie istnienia powiązanych rekordów

EXISTS sprawdza, czy podzapytanie zwraca jakikolwiek wiersz. Jest prawdziwe, jeśli podzapytanie ma co najmniej jeden wiersz wyniku. EXISTS jest zazwyczaj wydajniejsze niż IN dla dużych zbiorów danych, ponieważ MariaDB może przerwać sprawdzanie po znalezieniu pierwszego dopasowania.

-- EXISTS: czytelnicy, którzy wypożyczyli jakąś książkę
SELECT Nazwisko, Imie
FROM Czytelnicy c
WHERE EXISTS (
    SELECT 1
    FROM Wypozyczenia w
    WHERE w.Nr_czytel = c.Nr_czytel
);

-- NOT EXISTS: czytelnicy bez wypożyczeń (bezpieczniejsza wersja)
SELECT Nazwisko, Imie
FROM Czytelnicy c
WHERE NOT EXISTS (
    SELECT 1
    FROM Wypozyczenia w
    WHERE w.Nr_czytel = c.Nr_czytel
);

-- EXISTS z JOIN-em – książki, które ktoś wypożyczył
SELECT k.Tytul, k.Autor
FROM Ksiazki k
WHERE EXISTS (
    SELECT 1
    FROM Wypozyczenia w
    WHERE w.Nr_ks = k.Nr_ks
);
W EXISTS podzapytanie często używa SELECT 1 lub SELECT * – nie ma to znaczenia, bo EXISTS sprawdza tylko ISTNIENIE wierszy.
EXISTS – strzałka z podzapytania do zewnętrznego zapytania przez korelację

EXISTS to predykat logiczny – zwraca TRUE, jeśli podzapytanie zwróci co najmniej jeden wiersz. W podzapytaniu SELECT 1 jest konwencją – wybór kolumny nie ma znaczenia, ponieważ EXISTS sprawdza tylko obecność wierszy. MariaDB może zoptymalizować EXISTS do first match – przerywa wykonywanie podzapytania po znalezieniu pierwszego pasującego wiersza.

NOT EXISTS jest bezpieczniejszą alternatywą dla NOT IN. Gdy podzapytanie NOT IN zawiera NULL, wynik jest zawsze pusty (bo NULL nie jest IN ani NOT IN w stosunku do niczego). NOT EXISTS poprawnie traktuje NULL-e: EXISTS sprawdza korelację, a NULL-e w kolumnie łączącej są po prostu pomijane w porównaniu.

Wydajność: EXISTS vs IN. Dla podzapytań skorelowanych EXISTS jest zazwyczaj szybszy. Dla podzapytań nieskorelowanych IN może być optymalizowane przez MariaDB do połączenia z JOIN. W praktyce różnica jest często pomijalna, a wybór zależy od czytelności. EXISTS lepiej oddaje intencję "sprawdź, czy istnieje".

51/60Eksport do .sql – mysqldump

Backup i przenoszenie baz danych

mysqldump to podstawowe narzędzie do eksportu bazy danych do pliku SQL. Tworzy kompletny skrypt z CREATE TABLE i INSERT-ami, który można odtworzyć na innym serwerze. To standardowy sposób robienia backupów i migracji danych.

# Eksport całej bazy do pliku
mysqldump -u root -p biblioteka > biblioteka_backup.sql

# Eksport bez danych (tylko struktura)
mysqldump -u root -p --no-data biblioteka > struktura.sql

# Eksport tylko danych (bez CREATE TABLE)
mysqldump -u root -p --no-create-info biblioteka > dane.sql

# Eksport wielu baz
mysqldump -u root -p --databases biblioteka sklep > dwie_bazy.sql

# Eksport wszystkich baz
mysqldump -u root -p --all-databases > wszystkie_bazy.sql

# Kompresja eksportu
mysqldump -u root -p biblioteka | gzip > biblioteka.sql.gz
mysqldump z opcją --routines eksportuje procedury składowane, --triggers triggery, --events zdarzenia.
Zrzut ekranu – mysqldump w terminalu, postęp eksportu

mysqldump to narzędzie wiersza poleceń, które generuje skrypt SQL składający się z poleceń DDL (CREATE TABLE, CREATE INDEX) i DML (INSERT). Wynikowy plik można odtworzyć przez mysql < plik.sql. To najprostszy i najbardziej niezawodny sposób backupu – plik jest czytelny i można go edytować.

Opcje warte poznania: --single-transaction (dla InnoDB – tworzy spójny snapshot bez blokowania tabel), --lock-tables (domyślnie – blokuje tabele na czas eksportu), --routines, --triggers, --events (eksport dodatkowych obiektów), --where (eksport tylko wierszy spełniających warunek). Dla dużych baz --single-transaction jest zalecany, by nie blokować dostępu.

Eksport z kompresją (gzip) zmniejsza rozmiar pliku 5-10 razy. Przywracanie: gunzip -c biblioteka.sql.gz | mysql -u root -p. Do automatyzacji backupów warto użyć cron-a z mysqldump i opcjami --all-databases --single-transaction --routines --triggers. Przechowuj backupy na innym dysku lub w chmurze.

52/60Eksport do CSV – SELECT INTO OUTFILE

Wyniki zapytań w formacie CSV/tekstowym

SELECT INTO OUTFILE zapisuje wynik zapytania SQL bezpośrednio do pliku na serwerze. Plik można potem otworzyć w Excelu, LibreOffice Calc lub zaimportować do innej bazy. To najszybszy sposób eksportu danych w formacie tekstowym.

-- Eksport wszystkich czytelników do CSV
SELECT Nazwisko, Imie, Miasto, Email
INTO OUTFILE '/tmp/czytelnicy.csv'
FIELDS TERMINATED BY ';'
ENCLOSED BY '"'
LINES TERMINATED BY '\n'
FROM Czytelnicy;

-- Eksport z nagłówkami kolumn
SELECT 'Nazwisko', 'Imie', 'Miasto'
UNION ALL
SELECT Nazwisko, Imie, Miasto
FROM Czytelnicy
INTO OUTFILE '/tmp/czytelnicy_z_naglowkiem.csv'
FIELDS TERMINATED BY ';'
ENCLOSED BY '"'
LINES TERMINATED BY '\n';

-- Alternatywa: mysql -e z przekierowaniem
mysql -u root -p -e "SELECT * FROM biblioteka.Czytelnicy" \
  -B --silent > czytelnicy.txt
SELECT INTO OUTFILE wymaga uprawnienia FILE. Plik zapisywany jest na serwerze, nie na kliencie – ścieżka musi być zapisywalna dla serwera.
Plik CSV otwarty w arkuszu kalkulacyjnym – dane z bazy

SELECT INTO OUTFILE generuje plik tekstowy na serwerze. Domyślny katalog docelowy to ten wskazany przez zmienną secure_file_priv (zazwyczaj /var/lib/mysql-files/). Jeśli secure_file_priv jest ustawiony, plik musi być w tym katalogu – próba zapisu gdzie indziej zakończy się błędem. To zabezpieczenie przed nieautoryzowanym zapisem plików.

Opcje formatowania: FIELDS TERMINATED BY (separator kolumn – domyślnie tabulator), ENCLOSED BY (znak otaczający wartości tekstowe), ESCAPED BY (znak ucieczki – domyślnie backslash), LINES TERMINATED BY (znak końca linii). Dla zgodności z Excelem w Windows używa się \r\n zamiast \n.

Alternatywa dla klientów: mysql -e "SELECT ..." -B --silent > plik.txt. Opcja -B (batch mode) używa tabulatorów jako separatorów, --silent pomija ramki i nagłówki. Ta metoda zapisuje plik na komputerze klienta, nie na serwerze – wygodniejsza w codziennej pracy.

53/60Indeksy – przyspieszanie zapytań

Jak indeksy wpływają na wydajność?

Indeksy to struktury danych (B-drzewa), które przyspieszają wyszukiwanie w tabelach. Działają jak indeks w książce – zamiast przeglądać wszystkie strony, od razu znajdujemy potrzebne hasło. Indeksy spowalniają INSERT/UPDATE/DELETE, ale drastycznie przyspieszają SELECT.

-- Utworzenie indeksu na kolumnie
CREATE INDEX idx_nazwisko
ON Czytelnicy(Nazwisko);

-- Indeks złożony (na wielu kolumnach)
CREATE INDEX idx_nazwisko_imie
ON Czytelnicy(Nazwisko, Imie);

-- Unikalny indeks (alternatywa dla UNIQUE CONSTRAINT)
CREATE UNIQUE INDEX idx_email
ON Czytelnicy(Email);

-- Indeks częściowy (na części kolumny)
CREATE INDEX idx_tytul
ON Ksiazki(Tytul(20));

-- Usunięcie indeksu
DROP INDEX idx_nazwisko
ON Czytelnicy;

-- Wyświetlenie indeksów tabeli
SHOW INDEX FROM Czytelnicy;
Indeksy są automatycznie tworzone dla PRIMARY KEY i UNIQUE. Dla pozostałych kolumn musisz je stworzyć jawnie.
Schemat B-drzewa – jak indeks znajduje wartość w 3 krokach zamiast 100

Indeksy oparte na B-drzewie (Balanced Tree) to najpopularniejszy typ indeksu w MariaDB. Pozwalają znaleźć wartość w logarytmicznym czasie O(log n) zamiast liniowego O(n). Dla tabeli z 1 milionem wierszy indeks może znaleźć wartość w ~20 krokach zamiast 500 000. Różnica jest ogromna.

Indeks złożony (na kolumnach Nazwisko, Imie) działa dla zapytań filtrujących po Nazwisko lub po Nazwisko i Imię razem. Nie zadziała dla zapytań tylko po Imię (bez Nazwiska) – to zasada "leftmost prefix". Kolejność kolumn w indeksie złożonym ma znaczenie: najpierw umieszczamy kolumnę z największą selektywnością.

Indeksy cząstkowe (Tytul(20)) indeksują tylko pierwsze 20 znaków kolumny. Oszczędzają miejsce i są szybsze dla bardzo długich kolumn tekstowych. Mają sens, gdy pierwsze 20 znaków jest wystarczająco unikalne. Indeksy mają też wady: spowalniają zapisy (INSERT, UPDATE, DELETE) i zajmują dodatkowe miejsce na dysku.

54/60Transakcje – COMMIT i ROLLBACK

Zapewnianie spójności danych

Transakcja to grupa operacji SQL wykonywanych jako jedna niepodzielna całość. COMMIT zatwierdza zmiany, ROLLBACK je cofa. Transakcje realizują zasadę ACID (Atomicity, Consistency, Isolation, Durability). Silnik InnoDB w pełni wspiera transakcje.

-- Rozpoczęcie transakcji
START TRANSACTION;

-- Operacje w ramach transakcji
INSERT INTO Wypozyczenia (Nr_czytel, Nr_ks, Data_wyp)
VALUES (1, 2, CURDATE());

UPDATE Ksiazki
SET Liczba_egz = Liczba_egz - 1
WHERE Nr_ks = 2;

-- Jeśli wszystko OK – zatwierdź
COMMIT;

-- Jeśli błąd – cofnij
ROLLBACK;

-- Sprawdzenie zmiennej autocommit
SELECT @@autocommit;

-- Wyłączenie autocommit dla bieżącej sesji
SET autocommit = 0;
W MariaDB z InnoDB domyślnie każdy pojedynczy INSERT/UPDATE/DELETE jest automatycznie zatwierdzany (autocommit=1).
Diagram – transakcja: START -> operacje -> COMMIT lub ROLLBACK

Przykład transakcji wypożyczenia: dodajemy wpis w Wypozyczenia i zmniejszamy liczbę egzemplarzy w Ksiazki. Jeśli jedna z tych operacji się nie powiedzie (np. nie ma już egzemplarzy), ROLLBACK cofa obie – dane pozostają spójne. Bez transakcji moglibyśmy zmniejszyć liczbę egzemplarzy, a wpis w Wypozyczenia nie zostałby utworzony.

Poziomy izolacji transakcji w MariaDB: READ UNCOMMITTED, READ COMMITTED (domyślny w MariaDB), REPEATABLE READ, SERIALIZABLE. READ COMMITTED oznacza, że transakcja widzi tylko zatwierdzone zmiany innych transakcji. Wyższy poziom izolacji = więcej blokad = mniejsza wydajność. Wybór zależy od wymagań aplikacji.

ROLLBACK jest szczególnie przydatne podczas testowania i debugowania. Po wykonaniu serii operacji można sprawdzić ich efekt: jeśli wszystko OK – COMMIT, jeśli nie – ROLLBACK i popraw kod. W skryptach automatyzujących warto używać SAVEPOINT do tworzenia punktów przywracania wewnątrz transakcji: SAVEPOINT punkt1; ... ; ROLLBACK TO SAVEPOINT punkt1.

55/60Widoki – CREATE VIEW

Zapamiętane zapytania jako wirtualne tabele

Widok (VIEW) to zapamiętane zapytanie SQL, które można traktować jak wirtualną tabelę. Widoki upraszczają złożone zapytania, dodają warstwę abstrakcji i poprawiają bezpieczeństwo (można dać dostęp do widoku bez dostępu do bazowych tabel).

-- Utworzenie widoku – aktywne wypożyczenia
CREATE VIEW Aktywne_wypozyczenia AS
SELECT
    c.Nazwisko,
    c.Imie,
    k.Tytul,
    w.Data_wyp,
    DATEDIFF(NOW(), w.Data_wyp) AS Dni_od_wypozyczenia
FROM Czytelnicy c
JOIN Wypozyczenia w ON c.Nr_czytel = w.Nr_czytel
JOIN Ksiazki k ON w.Nr_ks = k.Nr_ks
WHERE w.Data_zwrot IS NULL;

-- Użycie widoku (jak zwykłej tabeli)
SELECT * FROM Aktywne_wypozyczenia;

-- Wyświetlenie definicji widoku
SHOW CREATE VIEW Aktywne_wypozyczenia;

-- Modyfikacja widoku
ALTER VIEW Aktywne_wypozyczenia AS
-- ... nowe zapytanie ...

-- Usunięcie widoku
DROP VIEW IF EXISTS Aktywne_wypozyczenia;
Widok nie przechowuje danych – przechowuje tylko zapytanie. Dane są pobierane z bazowych tabel przy każdym SELECT z widoku.
Schemat – widok jako nakładka na bazowe tabele Czytelnicy, Ksiazki, Wypozyczenia

Widoki to jeden z mechanizmów abstrakcji w SQL. Zamiast pisać za każdym razem złożone zapytanie z JOIN-ami i WHERE, zapisujemy je jako widok i odwołujemy się do niego jak do tabeli. Widok Aktywne_wypozyczenia ukrywa złożoność łączenia trzech tabel i filtrowania – użytkownik widzi tylko czytelną wirtualną tabelę.

Widoki mogą być aktualizowalne – jeśli spełniają określone warunki (nie zawierają GROUP BY, UNION, funkcji agregujących itp.), można przez nie wykonywać INSERT, UPDATE, DELETE. W praktyce widoki aktualizowalne są rzadko używane – częściej stosuje się widoki tylko do odczytu, a modyfikacje wykonuje się bezpośrednio na tabelach.

Zalety widoków: 1) Bezpieczeństwo – można dać użytkownikowi dostęp do widoku bez dostępu do bazowych tabel (ukrywamy wrażliwe kolumny). 2) Prostota – użytkownicy widzą tylko potrzebne dane. 3) Spójność – zmiana zapytania w widoku automatycznie zmienia działanie wszystkich aplikacji korzystających z widoku. Wady: widoki mogą ukrywać problemy wydajnościowe.

56/60Funkcje daty i czasu

Operacje na danych datowo-czasowych

MariaDB oferuje bogaty zestaw funkcji do manipulacji datami i czasem: pobieranie bieżącej daty, formatowanie, obliczanie różnic, dodawanie/odejmowanie interwałów. To niezbędne narzędzia w każdej aplikacji – od prostych filtrów po złożone raporty okresowe.

-- Bieżąca data i czas
SELECT NOW(), CURDATE(), CURTIME();

-- Formatowanie daty
SELECT
    DATE_FORMAT(Data_wyp, '%d-%m-%Y') AS Data_polska,
    DATE_FORMAT(Data_wyp, '%W, %d %M %Y') AS Data_slownie
FROM Wypozyczenia;

-- Różnica dni między datami
SELECT
    Tytul,
    Data_wyp,
    Data_zwrot,
    DATEDIFF(Data_zwrot, Data_wyp) AS Dni_wypozyczenia
FROM Wypozyczenia;

-- Dodawanie interwałów
SELECT
    Data_wyp,
    DATE_ADD(Data_wyp, INTERVAL 30 DAY) AS Termin_zwrotu,
    DATE_ADD(Data_wyp, INTERVAL 1 MONTH) AS Za_miesiac
FROM Wypozyczenia;

-- Wyciąganie części daty
SELECT
    YEAR(Data_zap) AS Rok,
    MONTH(Data_zap) AS Miesiac,
    DAY(Data_zap) AS Dzien
FROM Czytelnicy;
DATE_FORMAT używa specyfikatorów % – to ważne: %Y (wielkie) = rok 4-cyfrowy, %y (małe) = rok 2-cyfrowy.
Kalendarz z zaznaczonymi datami – funkcje daty w akcji

DATEDIFF(Data_zwrot, Data_wyp) oblicza różnicę w dniach między dwiema datami. Wynik jest dodatni, jeśli pierwsza data jest późniejsza. W przykładzie zwraca liczbę dni wypożyczenia. Jeśli książka nie została zwrócona (Data_zwrot IS NULL), DATEDIFF zwróci NULL – w takich przypadkach można użyć IFNULL lub COALESCE.

TIMESTAMPDIFF to bardziej elastyczna funkcja – pozwala obliczać różnicę w różnych jednostkach: TIMESTAMPDIFF(MONTH, Data_wyp, NOW()) zwraca liczbę pełnych miesięcy od wypożyczenia. LAST_DAY(data) zwraca ostatni dzień miesiąca dla podanej daty. WEEKDAY(data) zwraca dzień tygodnia (0 = poniedziałek, 6 = niedziela).

INTERVAL w DATE_ADD/DATE_SUB może być: DAY, MONTH, YEAR, HOUR, MINUTE, SECOND, WEEK, QUARTER. Kombinacje jak '1 2:30' (1 dzień 2 godziny 30 minut) są obsługiwane jako INTERVAL '1 2:30' DAY_MINUTE. Funkcje daty są niezbędne w raportach okresowych: zestawienia miesięczne, kwartalne, roczne.

57/60Funkcje tekstowe

Manipulacja łańcuchami znaków

Funkcje tekstowe pozwalają przekształcać, formatować i analizować dane tekstowe. MariaDB oferuje dziesiątki funkcji: zmiana wielkości liter, wycinanie fragmentów, wyszukiwanie, zamiana, usuwanie białych znaków. To potężne narzędzia do czyszczenia i formatowania danych.

-- Zmiana wielkości liter
SELECT
    UPPER(Nazwisko) AS WIELKIE,
    LOWER(Imie) AS male,
    CONCAT(UPPER(LEFT(Imie, 1)), LOWER(SUBSTRING(Imie, 2))) AS Poprawione
FROM Czytelnicy;

-- Długość stringa
SELECT
    Tytul,
    LENGTH(Tytul) AS Bajty,
    CHAR_LENGTH(Tytul) AS Znaki
FROM Ksiazki;

-- Wycinanie fragmentów
SELECT
    LEFT(Tytul, 10) AS Pierwsze_10,
    RIGHT(Tytul, 10) AS Ostatnie_10,
    SUBSTRING(Tytul, 3, 5) AS Od_3_do_8
FROM Ksiazki
LIMIT 3;

-- Zastępowanie tekstu
SELECT
    REPLACE(Adres, 'Główna', 'Mickiewicza') AS Nowy_adres
FROM Czytelnicy;

-- Usuwanie białych znaków
SELECT
    TRIM('  tekst  '),
    LTRIM('  tekst'),
    RTRIM('tekst  ');
LENGTH podaje długość w bajtach. CHAR_LENGTH podaje długość w znakach. Dla UTF-8 polskie znaki to 2 bajty.
Tabela z oryginalnym tekstem i wynikami funkcji tekstowych

Różnica między LENGTH a CHAR_LENGTH jest krytyczna dla języków z diakrytykami. Dla łańcucha "Łąka" LENGTH zwróci 5 (bo Ł i ą to po 2 bajty w UTF-8), a CHAR_LENGTH zwróci 4 (liczbę znaków). Przy sprawdzaniu długości dla walidacji (np. PESEL ma 11 znaków) zawsze używaj CHAR_LENGTH.

LOCATE(szukanetekst, tekst, pozycja) znajduje pozycję szukanego tekstu – zwraca 0 jeśli nie znaleziono. INSTR(tekst, szukany) to synonim. CONCAT_WS(separator, elem1, elem2, ...) łączy z separatorem, pomijając NULL-e. To bezpieczniejsza alternatywa dla CONCAT. GROUP_CONCAT łączy wartości z grupy w jeden łańcuch.

Funkcje tekstowe są często używane do czyszczenia danych przed analizą: TRIM usuwa nadmiarowe spacje, UPPER/LOWER ujednolica format, REPLACE poprawia błędy w danych. W połączeniu z UPDATE pozwalają masowo korygować dane: UPDATE Czytelnicy SET Nazwisko = TRIM(UPPER(Nazwisko)).

58/60CASE WHEN – logika warunkowa w SQL

Wyrażenie warunkowe – odpowiednik IF-ELSE w SQL

CASE WHEN to potężne wyrażenie, które pozwala na logikę warunkową bezpośrednio w zapytaniach SQL. Może zastąpić wiele skomplikowanych IF-ów i jest czytelniejsze. Występuje w dwóch formach: prostej (CASE wartość WHEN) i przeszukiwanej (CASE WHEN warunek).

-- CASE proste: mapowanie kategorii
SELECT
    Tytul,
    Kategoria,
    CASE Kategoria
        WHEN 'SF' THEN 'Science Fiction'
        WHEN 'Fantastyka' THEN 'Fantasy'
        ELSE 'Inna'
    END AS Kategoria_eng
FROM Ksiazki;

-- CASE przeszukiwane: przedziały wiekowe książek
SELECT
    Tytul,
    Rok_wyd,
    CASE
        WHEN Rok_wyd < 1800 THEN 'Stary druk'
        WHEN Rok_wyd < 1950 THEN 'Klasyka'
        WHEN Rok_wyd < 2000 THEN 'Współczesna'
        ELSE 'Nowość'
    END AS Okres
FROM Ksiazki;

-- CASE z funkcjami agregującymi
SELECT
    SUM(CASE WHEN Kategoria = 'SF' THEN 1 ELSE 0 END) AS SF,
    SUM(CASE WHEN Kategoria = 'Fantastyka' THEN 1 ELSE 0 END) AS Fantastyka
FROM Ksiazki;
ELSE w CASE jest opcjonalne. Bez ELSE, gdy żaden WHEN nie pasuje, CASE zwraca NULL.
Schemat – CASE WHEN dzieli dane na kategorie według warunków

CASE WHEN to standardowe wyrażenie SQL (nie funkcja, choć bywa tak nazywane). W formie prostej (CASE Kategoria WHEN 'SF' THEN ...) porównuje wartość z każdym WHEN. W formie przeszukiwanej (CASE WHEN Rok_wyd < 1800 THEN ...) każdy WHEN to osobne wyrażenie logiczne. Forma przeszukiwana jest bardziej elastyczna.

CASE z SUM to technika zwana "conditional aggregation" lub "pivot table". Zamiast jednego wiersza na kategorię (GROUP BY Kategoria), tworzymy osobne kolumny dla każdej kategorii. Wynik to jeden wiersz z liczbami SF, Fantastyka, itd. To przydatne w raportach krzyżowych i dashboardach.

IF() to prostsza alternatywa dla CASE z dwoma wynikami: IF(warunek, wartość_prawda, wartość_fałsz). Dla prostych warunków IF jest zwięźlejsze: IF(Kara > 0, 'Ma dług', 'Bez długu'). Dla wielu warunków CASE jest czytelniejsze: CASE WHEN Kara = 0 THEN 'Brak' WHEN Kara < 10 THEN 'Mały' ELSE 'Duży' END.

59/60Dobre praktyki i bezpieczeństwo

Jak bezpiecznie i efektywnie pracować z MariaDB?

Dobre praktyki to fundament bezpiecznej i wydajnej pracy z bazami danych. Obejmują zarządzanie dostępem, backup, optymalizację zapytań, unikanie SQL Injection i regularne monitorowanie. Nawet najlepszy schemat nie pomoże, jeśli zaniedbamy bezpieczeństwo.

-- 1. Zasada najmniejszych uprawnień
GRANT SELECT ON biblioteka.* TO 'aplikacja'@'%';
-- Nie dajemy więcej niż potrzeba – DELETE tylko dla administratora

-- 2. Zabezpieczenie przed SQL Injection (przykład w kodzie aplikacji)
-- ZŁE: mysqli_query("SELECT * FROM user WHERE login='" . $_POST['login'] . "'");
-- DOBRE: $stmt = $pdo->prepare("SELECT * FROM user WHERE login = ?");

-- 3. Backup regularny (cron)
-- 0 2 * * * mysqldump --all-databases --single-transaction > /backup/db_$(date +\%F).sql

-- 4. Monitorowanie wolnych zapytań
SET GLOBAL slow_query_log = 1;
SET GLOBAL long_query_time = 2;  -- loguj zapytania > 2 sekund

-- 5. Sprawdzanie stanu serwera
SHOW GLOBAL STATUS LIKE 'Threads_connected';
SHOW GLOBAL STATUS LIKE 'Questions';
SQL Injection to najczęstsze zagrożenie dla baz danych. Zawsze używaj prepared statements – NIGDY nie łącz stringów w zapytaniach.
Lista kontrolna – 10 zasad bezpiecznej pracy z bazą danych

Zasada najmniejszych uprawnień (Least Privilege Principle) mówi: każdy użytkownik i aplikacja powinny mieć tylko te uprawnienia, które są niezbędne do działania. Aplikacja webowa potrzebuje tylko SELECT, INSERT, UPDATE na określonych tabelach – nie potrzebuje DROP TABLE ani globalnych uprawnień. Rozdzielenie kont na admin, aplikacja, raportowanie to podstawa.

SQL Injection to wstrzykiwanie złośliwego kodu SQL przez parametry wejściowe aplikacji. Jeśli aplikacja łączy stringi: "SELECT * FROM user WHERE login='" + login + "'", atakujący może podać login: ' OR '1'='1 i uzyskać dostęp do wszystkich danych. Prepared statements (PDO, MySQLi) całkowicie eliminują to zagrożenie, oddzielając kod SQL od danych.

Regularne backupy to podstawa – nie ma systemu, który nie ulegnie awarii. Strategia backupu: pełny (codziennie), przyrostowy (co godzinę). Przechowuj backupy w dwóch lokalizacjach (lokalnie i w chmurze). Testuj odtwarzanie z backupu – nie chcesz odkryć, że backup jest uszkodzony w momencie awarii. Używaj GTID (Global Transaction ID) w MariaDB do łatwiejszego odtwarzania.

Monitorowanie: włącz slow query log, aby znaleźć wolne zapytania. Używaj EXPLAIN do analizy planu zapytania. Monitoruj połączenia (Threads_connected), zapytania na sekundę (Questions), rozmiar baz danych i miejsce na dysku. Narzędzia takie jak phpMyAdmin, DBeaver, MySQL Workbench oferują graficzne monitory, ale konsola z SHOW STATUS jest często szybsza.

60/60Podsumowanie – co dalej?

Gratulacje! Opanowałeś podstawy MariaDB i SQL

Przeszedłeś przez 60 slajdów – od instalacji serwera przez tworzenie tabel, operacje DML, złączenia JOIN, aż po zaawansowane tematy jak transakcje, widoki i indeksy. Jesteś gotowy do samodzielnej pracy z MariaDB.

Następne kroki:

  • Przećwicz każdy przykład na własnej bazie – praktyka to klucz
  • Poznaj phpMyAdmin lub DBeaver jako graficzne narzędzia
  • Zapoznaj się z procedurami składowanymi i triggerami
  • Dowiedz się o replikacji (master-slave, Galera Cluster)
  • Optymalizacja wydajności – indeksy, partycjonowanie, EXPLAIN
-- Twoje ostatnie zapytanie w tym kursie
SELECT 'Dziękujemy za udział w kursie!' AS Podsumowanie;
-- Wynik: Dziękujemy za udział w kursie!
Dokumentacja MariaDB: mariadb.com/kb – najlepsze źródło wiedzy. Społeczność: Stack Overflow, forum MariaDB.
Dyplom ukończenia kursu SQL – logo MariaDB i napis

Ten kurs dał Ci solidne fundamenty, ale SQL to język, którego uczy się całe życie. Każdy nowy projekt, każda nowa baza danych przynosi wyzwania i możliwości nauki. Kluczem jest praktyka – nie wystarczy przeczytać, trzeba samodzielnie pisać zapytania, popełniać błędy i wyciągać wnioski. Zachęcamy do tworzenia własnych projektów: system biblioteczny to dobry początek, ale spróbuj też sklepu internetowego, systemu zamówień czy bazy przepisów.

MariaDB rozwija się bardzo dynamicznie. Wersja 11.x (aktualna na 2025 rok) przyniosła wiele ulepszeń: JSON – funkcje do pracy z danymi JSON, nowe funkcje okienne (WINDOW FUNCTIONS), ulepszenia wydajnościowe InnoDB i lepszą obsługę standardu SQL:2016. Warto śledzić changelog i testować nowe funkcje w środowisku deweloperskim. MariaDB Foundation regularnie publikuje webinary i artykuły o nowościach.

Ścieżka dalszego rozwoju: 1) Administracja – backup, replikacja, monitorowanie, tuning. 2) Programowanie – procedury składowane, triggery, zdarzenia w MariaDB. 3) NoSQL – MariaDB oferuje silnik Connect do łączenia z innymi źródłami danych. 4) Big Data – połączenie z platformami jak Hadoop, Spark, Kafka przez łączniki MariaDB. 5) Cloud – MariaDB na AWS RDS, Google Cloud SQL, Azure Database. Możliwości są nieograniczone.

Pamiętaj o certyfikacji: MariaDB Foundation oferuje egzamin MariaDB Certified Professional. Znajomość SQL to jedna z najbardziej uniwersalnych umiejętności w IT – znajdziesz ją w każdej ofercie pracy dla programisty backendowego, administratora baz danych, analityka danych czy data scientista. SQL to inwestycja, która zawsze się zwraca. Powodzenia!