Witaj ponownie, @name! Darwin tutaj z kluczową lekcją o przechowywaniu danych.
Do tej pory pracowałeś/aś z danymi tymczasowo - w zmiennych, plikach JSON, scrapowanych stronach. Ale co gdy masz miliony obserwacji gatunków? Co gdy potrzebujesz szybko wyszukać wszystkie zagrożone gatunki z Serengeti?
Wtedy potrzebujesz bazy danych - wyspecjalizowanego systemu do przechowywania i zarządzania dużymi zbiorami danych! 🗄️💾
Analogia Safari: Pliki JSON to jak notatnik biologa - działa dla 10 obserwacji. Baza danych to wielka biblioteka katalogowa - zorganizowane szuflady z kartotekami, indeksami, szybkim wyszukiwaniem dla milionów rekordów! 📚🔍
Baza danych to uporządkowany zbiór danych + system zarządzania (DBMS - Database Management System).
Po co bazy danych?
Struktura: Tabele z wierszami i kolumnami (jak arkusz Excel)
Przykłady: PostgreSQL, MySQL, SQLite, Oracle, SQL Server
Cechy:
1TABELA: species
2+----+-----------------+------------+----------+
3| id | scientific_name | population | habitat |
4+----+-----------------+------------+----------+
5| 1 | Panthera leo | 120 | sawanna |
6| 2 | Gorilla gorilla | 230 | las |
7+----+-----------------+------------+----------+Struktura: Dokumenty, pary klucz-wartość, grafy, kolumny
Przykłady: MongoDB, Redis, Cassandra, Neo4j
Cechy:
W tej lekcji: Skupiamy się na SQL (relacyjne) - fundament większości aplikacji!
SQLite to najpopularniejsza baza SQL na świecie:
sqlite3Użycie: Aplikacje mobilne, przeglądarki, urządzenia IoT, prototypy
SQL (Structured Query Language) to uniwersalny język do komunikacji z bazami relacyjnymi.
1-- Tabela gatunków
2CREATE TABLE species (
3 id INTEGER PRIMARY KEY AUTOINCREMENT,
4 scientific_name TEXT NOT NULL UNIQUE,
5 common_name TEXT NOT NULL,
6 population INTEGER DEFAULT 0,
7 habitat TEXT,
8 endangered BOOLEAN DEFAULT 0,
9 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
10);
11
12-- Tabela obserwacji
13CREATE TABLE observations (
14 id INTEGER PRIMARY KEY AUTOINCREMENT,
15 species_id INTEGER NOT NULL,
16 observation_date DATE NOT NULL,
17 location TEXT NOT NULL,
18 count INTEGER DEFAULT 0,
19 notes TEXT,
20 FOREIGN KEY (species_id) REFERENCES species(id)
21);Typy danych SQLite:
INTEGER - liczba całkowitaREAL - liczba zmiennoprzecinkowaTEXT - tekstBLOB - dane binarneNULL - brak wartościOgraniczenia (constraints):
PRIMARY KEY - unikalny identyfikator wierszaAUTOINCREMENT - automatyczny wzrostNOT NULL - pole wymaganeUNIQUE - wartość unikalnaDEFAULT - wartość domyślnaFOREIGN KEY - relacja do innej tabeli1-- Dodaj jeden rekord
2INSERT INTO species (scientific_name, common_name, population, habitat, endangered)
3VALUES ('Panthera leo', 'Lew', 120, 'sawanna', 1);
4
5-- Dodaj wiele rekordów
6INSERT INTO species (scientific_name, common_name, population, habitat, endangered)
7VALUES
8 ('Loxodonta africana', 'Słoń afrykański', 450, 'sawanna', 1),
9 ('Gorilla gorilla', 'Goryl', 230, 'las tropikalny', 1),
10 ('Python regius', 'Pyton królewski', 85, 'dżungla', 0);
11
12-- Dodaj obserwację
13INSERT INTO observations (species_id, observation_date, location, count, notes)
14VALUES (1, '2024-01-15', 'Serengeti North', 12, 'Pride with 2 cubs');1-- Wszystkie gatunki
2SELECT * FROM species;
3
4-- Wybrane kolumny
5SELECT common_name, population FROM species;
6
7-- WHERE - filtrowanie
8SELECT * FROM species WHERE endangered = 1;
9SELECT * FROM species WHERE population > 100;
10SELECT * FROM species WHERE habitat = 'sawanna' AND endangered = 1;
11
12-- ORDER BY - sortowanie
13SELECT * FROM species ORDER BY population DESC; -- Malejąco
14SELECT * FROM species ORDER BY common_name ASC; -- Rosnąco
15
16-- LIMIT - ograniczenie wyników
17SELECT * FROM species LIMIT 10;
18SELECT * FROM species LIMIT 10 OFFSET 20; -- Strona 3 (20-30)
19
20-- LIKE - wyszukiwanie wzorca
21SELECT * FROM species WHERE scientific_name LIKE 'Panthera%'; -- Zaczyna się od Panthera
22SELECT * FROM species WHERE habitat LIKE '%las%'; -- Zawiera "las"
23
24-- COUNT, SUM, AVG, MIN, MAX - agregacje
25SELECT COUNT(*) FROM species;
26SELECT AVG(population) FROM species;
27SELECT SUM(population) FROM species WHERE endangered = 1;
28SELECT MIN(population), MAX(population) FROM species;
29
30-- GROUP BY - grupowanie
31SELECT habitat, COUNT(*) as species_count
32FROM species
33GROUP BY habitat;
34
35SELECT habitat, AVG(population) as avg_population
36FROM species
37GROUP BY habitat
38HAVING AVG(population) > 100; -- HAVING = WHERE dla grup1-- Zaktualizuj jeden rekord
2UPDATE species
3SET population = 125
4WHERE id = 1;
5
6-- Zaktualizuj wiele rekordów
7UPDATE species
8SET endangered = 1
9WHERE population < 50;
10
11-- Zaktualizuj wiele kolumn
12UPDATE species
13SET population = 130, habitat = 'sawanna i step'
14WHERE scientific_name = 'Panthera leo';1-- Usuń jeden rekord
2DELETE FROM species WHERE id = 10;
3
4-- Usuń wiele rekordów
5DELETE FROM species WHERE population = 0;
6
7-- UWAGA: Usuń wszystko (bez WHERE!)
8DELETE FROM species; -- Usuwa WSZYSTKIE rekordy!1-- INNER JOIN - tylko dopasowane rekordy
2SELECT
3 species.common_name,
4 observations.observation_date,
5 observations.location,
6 observations.count
7FROM observations
8INNER JOIN species ON observations.species_id = species.id;
9
10-- LEFT JOIN - wszystkie z lewej + dopasowane z prawej
11SELECT
12 species.common_name,
13 COUNT(observations.id) as observation_count
14FROM species
15LEFT JOIN observations ON species.id = observations.species_id
16GROUP BY species.id, species.common_name;
17
18-- Aliasy tabel (s, o)
19SELECT
20 s.common_name,
21 o.location,
22 o.count
23FROM observations o
24INNER JOIN species s ON o.species_id = s.id
25WHERE s.endangered = 1;Python ma wbudowany moduł
sqlite3 do pracy z SQLite.1import sqlite3
2
3# 1. Połącz się z bazą (tworzy plik jeśli nie istnieje)
4conn = sqlite3.connect("safari.db")
5
6# 2. Utwórz kursor (obiekt do wykonywania zapytań)
7cursor = conn.cursor()
8
9# 3. Wykonaj zapytanie SQL
10cursor.execute("""
11 CREATE TABLE IF NOT EXISTS species (
12 id INTEGER PRIMARY KEY AUTOINCREMENT,
13 scientific_name TEXT NOT NULL UNIQUE,
14 common_name TEXT NOT NULL,
15 population INTEGER DEFAULT 0,
16 endangered BOOLEAN DEFAULT 0
17 )
18""")
19
20# 4. Zatwierdź zmiany (WAŻNE!)
21conn.commit()
22
23# 5. Zamknij połączenie
24conn.close()1import sqlite3
2
3conn = sqlite3.connect("safari.db")
4cursor = conn.cursor()
5
6# Metoda 1: Zapytanie z wartościami (⚠️ NIEBEZPIECZNE - SQL injection!)
7# NIE RÓB TAK:
8# name = "Lew'; DROP TABLE species; --" # SQL injection!
9# cursor.execute(f"INSERT INTO species (common_name) VALUES ('{name}')")
10
11# Metoda 2: Parameteryzowane zapytania (✅ BEZPIECZNE)
12cursor.execute("""
13 INSERT INTO species (scientific_name, common_name, population, endangered)
14 VALUES (?, ?, ?, ?)
15""", ("Panthera leo", "Lew", 120, 1))
16
17# Wiele rekordów naraz
18species_data = [
19 ("Loxodonta africana", "Słoń afrykański", 450, 1),
20 ("Gorilla gorilla", "Goryl", 230, 1),
21 ("Python regius", "Pyton królewski", 85, 0)
22]
23
24cursor.executemany("""
25 INSERT INTO species (scientific_name, common_name, population, endangered)
26 VALUES (?, ?, ?, ?)
27""", species_data)
28
29conn.commit()
30
31# Pobierz ID ostatnio dodanego rekordu
32print(f"Dodano gatunek ID: {cursor.lastrowid}")
33
34conn.close()1import sqlite3
2
3conn = sqlite3.connect("safari.db")
4cursor = conn.cursor()
5
6# fetchall() - wszystkie wyniki jako lista tupli
7cursor.execute("SELECT * FROM species")
8all_species = cursor.fetchall()
9
10for species in all_species:
11 print(species) # (1, 'Panthera leo', 'Lew', 120, 1)
12
13# fetchone() - jeden wynik
14cursor.execute("SELECT * FROM species WHERE id = ?", (1,))
15lion = cursor.fetchone()
16print(lion) # (1, 'Panthera leo', 'Lew', 120, 1)
17
18# fetchmany(n) - n wyników
19cursor.execute("SELECT * FROM species")
20first_five = cursor.fetchmany(5)
21
22# Row factory - wyniki jako słowniki
23conn.row_factory = sqlite3.Row
24cursor = conn.cursor()
25
26cursor.execute("SELECT * FROM species")
27for row in cursor.fetchall():
28 print(row["common_name"], row["population"])
29
30conn.close()1import sqlite3
2
3conn = sqlite3.connect("safari.db")
4cursor = conn.cursor()
5
6# UPDATE
7cursor.execute("""
8 UPDATE species
9 SET population = ?
10 WHERE id = ?
11""", (125, 1))
12
13print(f"Zaktualizowano {cursor.rowcount} rekordów")
14
15# DELETE
16cursor.execute("DELETE FROM species WHERE population < ?", (10,))
17print(f"Usunięto {cursor.rowcount} rekordów")
18
19conn.commit()
20conn.close()1import sqlite3
2
3# Automatyczne commit i close
4with sqlite3.connect("safari.db") as conn:
5 cursor = conn.cursor()
6
7 cursor.execute("""
8 INSERT INTO species (scientific_name, common_name, population)
9 VALUES (?, ?, ?)
10 """, ("Acinonyx jubatus", "Gepard", 7100))
11
12 # commit() automatycznie przy wyjściu z bloku with
13# close() automatycznie1import sqlite3
2from typing import List, Dict, Optional
3from datetime import datetime
4
5class SafariDatabase:
6 """Kompletny system zarządzania danymi Safari"""
7
8 def __init__(self, db_path: str = "safari.db"):
9 self.db_path = db_path
10 self.init_database()
11
12 def get_connection(self):
13 """Utwórz połączenie z bazą"""
14 conn = sqlite3.connect(self.db_path)
15 conn.row_factory = sqlite3.Row # Wyniki jako słowniki
16 return conn
17
18 def init_database(self):
19 """Inicjalizuj schemat bazy danych"""
20 with self.get_connection() as conn:
21 cursor = conn.cursor()
22
23 # Tabela gatunków
24 cursor.execute("""
25 CREATE TABLE IF NOT EXISTS species (
26 id INTEGER PRIMARY KEY AUTOINCREMENT,
27 scientific_name TEXT NOT NULL UNIQUE,
28 common_name TEXT NOT NULL,
29 population INTEGER DEFAULT 0,
30 habitat TEXT,
31 endangered BOOLEAN DEFAULT 0,
32 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
33 updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
34 )
35 """)
36
37 # Tabela obserwacji
38 cursor.execute("""
39 CREATE TABLE IF NOT EXISTS observations (
40 id INTEGER PRIMARY KEY AUTOINCREMENT,
41 species_id INTEGER NOT NULL,
42 observation_date DATE NOT NULL,
43 location TEXT NOT NULL,
44 count INTEGER DEFAULT 0,
45 notes TEXT,
46 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
47 FOREIGN KEY (species_id) REFERENCES species(id) ON DELETE CASCADE
48 )
49 """)
50
51 # Indeksy dla szybszego wyszukiwania
52 cursor.execute("""
53 CREATE INDEX IF NOT EXISTS idx_species_endangered
54 ON species(endangered)
55 """)
56
57 cursor.execute("""
58 CREATE INDEX IF NOT EXISTS idx_observations_species
59 ON observations(species_id)
60 """)
61
62 conn.commit()
63
64 # === SPECIES CRUD ===
65
66 def create_species(self, scientific_name: str, common_name: str,
67 population: int = 0, habitat: str = "",
68 endangered: bool = False) -> int:
69 """Dodaj nowy gatunek"""
70 with self.get_connection() as conn:
71 cursor = conn.cursor()
72 cursor.execute("""
73 INSERT INTO species (scientific_name, common_name, population, habitat, endangered)
74 VALUES (?, ?, ?, ?, ?)
75 """, (scientific_name, common_name, population, habitat, int(endangered)))
76
77 conn.commit()
78 return cursor.lastrowid
79
80 def get_species(self, species_id: int) -> Optional[Dict]:
81 """Pobierz gatunek po ID"""
82 with self.get_connection() as conn:
83 cursor = conn.cursor()
84 cursor.execute("SELECT * FROM species WHERE id = ?", (species_id,))
85 row = cursor.fetchone()
86 return dict(row) if row else None
87
88 def list_species(self, endangered: Optional[bool] = None,
89 habitat: Optional[str] = None,
90 min_population: int = 0) -> List[Dict]:
91 """Lista gatunków z filtrami"""
92 with self.get_connection() as conn:
93 cursor = conn.cursor()
94
95 query = "SELECT * FROM species WHERE population >= ?"
96 params = [min_population]
97
98 if endangered is not None:
99 query += " AND endangered = ?"
100 params.append(int(endangered))
101
102 if habitat:
103 query += " AND habitat = ?"
104 params.append(habitat)
105
106 query += " ORDER BY common_name"
107
108 cursor.execute(query, params)
109 return [dict(row) for row in cursor.fetchall()]
110
111 def update_species(self, species_id: int, **kwargs) -> bool:
112 """Zaktualizuj gatunek"""
113 if not kwargs:
114 return False
115
116 # Dynamiczne budowanie UPDATE
117 fields = ", ".join([f"{key} = ?" for key in kwargs.keys()])
118 values = list(kwargs.values())
119 values.append(species_id)
120
121 with self.get_connection() as conn:
122 cursor = conn.cursor()
123 cursor.execute(f"""
124 UPDATE species
125 SET {fields}, updated_at = CURRENT_TIMESTAMP
126 WHERE id = ?
127 """, values)
128
129 conn.commit()
130 return cursor.rowcount > 0
131
132 def delete_species(self, species_id: int) -> bool:
133 """Usuń gatunek"""
134 with self.get_connection() as conn:
135 cursor = conn.cursor()
136 cursor.execute("DELETE FROM species WHERE id = ?", (species_id,))
137 conn.commit()
138 return cursor.rowcount > 0
139
140 # === OBSERVATIONS ===
141
142 def create_observation(self, species_id: int, observation_date: str,
143 location: str, count: int, notes: str = "") -> int:
144 """Dodaj obserwację"""
145 with self.get_connection() as conn:
146 cursor = conn.cursor()
147 cursor.execute("""
148 INSERT INTO observations (species_id, observation_date, location, count, notes)
149 VALUES (?, ?, ?, ?, ?)
150 """, (species_id, observation_date, location, count, notes))
151
152 conn.commit()
153 return cursor.lastrowid
154
155 def get_observations_for_species(self, species_id: int) -> List[Dict]:
156 """Pobierz obserwacje dla gatunku"""
157 with self.get_connection() as conn:
158 cursor = conn.cursor()
159 cursor.execute("""
160 SELECT
161 o.*,
162 s.common_name as species_name
163 FROM observations o
164 JOIN species s ON o.species_id = s.id
165 WHERE o.species_id = ?
166 ORDER BY o.observation_date DESC
167 """, (species_id,))
168
169 return [dict(row) for row in cursor.fetchall()]
170
171 # === STATISTICS ===
172
173 def get_statistics(self) -> Dict:
174 """Pobierz statystyki bazy"""
175 with self.get_connection() as conn:
176 cursor = conn.cursor()
177
178 # Łączna liczba gatunków
179 cursor.execute("SELECT COUNT(*) as count FROM species")
180 total_species = cursor.fetchone()["count"]
181
182 # Zagrożone gatunki
183 cursor.execute("SELECT COUNT(*) as count FROM species WHERE endangered = 1")
184 endangered_count = cursor.fetchone()["count"]
185
186 # Łączna populacja
187 cursor.execute("SELECT SUM(population) as total FROM species")
188 total_population = cursor.fetchone()["total"] or 0
189
190 # Gatunki po siedlisku
191 cursor.execute("""
192 SELECT habitat, COUNT(*) as count
193 FROM species
194 GROUP BY habitat
195 ORDER BY count DESC
196 """)
197 by_habitat = [dict(row) for row in cursor.fetchall()]
198
199 return {
200 "total_species": total_species,
201 "endangered_count": endangered_count,
202 "total_population": total_population,
203 "by_habitat": by_habitat
204 }
205
206
207# === DEMONSTRACJA ===
208
209print("=== SAFARI DATABASE SYSTEM ===\n")
210
211db = SafariDatabase("safari_demo.db")
212
213# 1. Dodaj gatunki
214print("1. Dodawanie gatunków...")
215lion_id = db.create_species("Panthera leo", "Lew", 120, "sawanna", True)
216elephant_id = db.create_species("Loxodonta africana", "Słoń afrykański", 450, "sawanna", True)
217gorilla_id = db.create_species("Gorilla gorilla", "Goryl", 230, "las tropikalny", True)
218python_id = db.create_species("Python regius", "Pyton królewski", 85, "dżungla", False)
219
220print(f" Dodano {4} gatunki")
221
222# 2. Pobierz gatunek
223print("\n2. Pobieranie gatunku...")
224lion = db.get_species(lion_id)
225print(f" {lion['common_name']} ({lion['scientific_name']})")
226print(f" Populacja: {lion['population']}, Zagrożony: {'Tak' if lion['endangered'] else 'Nie'}")
227
228# 3. Lista gatunków z filtrem
229print("\n3. Lista zagrożonych gatunków...")
230endangered = db.list_species(endangered=True)
231for species in endangered:
232 print(f" - {species['common_name']}: {species['population']} osobników")
233
234# 4. Aktualizacja
235print("\n4. Aktualizacja populacji lwa...")
236db.update_species(lion_id, population=125)
237lion = db.get_species(lion_id)
238print(f" Nowa populacja: {lion['population']}")
239
240# 5. Dodaj obserwacje
241print("\n5. Dodawanie obserwacji...")
242db.create_observation(lion_id, "2024-01-15", "Serengeti North", 12, "Pride with 2 cubs")
243db.create_observation(lion_id, "2024-01-20", "Masai Mara", 8, "Male coalition")
244db.create_observation(elephant_id, "2024-01-16", "Amboseli", 35, "Large herd")
245
246print(" Dodano 3 obserwacje")
247
248# 6. Pobierz obserwacje
249print("\n6. Obserwacje lwa...")
250lion_obs = db.get_observations_for_species(lion_id)
251for obs in lion_obs:
252 print(f" - {obs['observation_date']}: {obs['count']}x w {obs['location']}")
253
254# 7. Statystyki
255print("\n7. Statystyki bazy danych...")
256stats = db.get_statistics()
257print(f" Łącznie gatunków: {stats['total_species']}")
258print(f" Zagrożone: {stats['endangered_count']}")
259print(f" Łączna populacja: {stats['total_population']}")
260print(" Według siedliska:")
261for habitat_stat in stats['by_habitat']:
262 print(f" - {habitat_stat['habitat']}: {habitat_stat['count']} gatunków")
263
264print("\n✓ Demonstracja zakończona")1import sqlite3
2
3conn = sqlite3.connect("safari.db")
4
5try:
6 cursor = conn.cursor()
7
8 # Rozpocznij transakcję (domyślnie)
9 cursor.execute("INSERT INTO species (...) VALUES (...)")
10 cursor.execute("UPDATE observations SET ...")
11
12 # Zatwierdź transakcję
13 conn.commit()
14 print("✓ Transakcja zatwierdzona")
15
16except Exception as e:
17 # Wycofaj transakcję przy błędzie
18 conn.rollback()
19 print(f"❌ Transakcja wycofana: {e}")
20
21finally:
22 conn.close()W tej lekcji nauczyłeś/aś się:
Przed przejściem dalej:
Analogia Safari: Baza danych SQL to wielka biblioteka katalogowa z milionami kart gatunków - szybkie wyszukiwanie, relacje, integralność danych. JSON to notatnik - świetny na 10 wpisów, niewystarczający na miliony! 📚🗄️
W następnej lekcji Darwin pokaże Ci SQLAlchemy ORM - jak pracować z bazami danych używając pythonowych klas zamiast surowego SQL! 🐍🔗