Bazy danych — SQL vs NoSQL dla początkujących

Zrozum fundamenty baz danych bez wojny "SQL kontra NoSQL". Poznaj różnice, CRUD, relacje, indeksy i praktyczne kryteria wyboru dla aplikacji webowych.

Opublikowano

2 lipca 2025 13:30

Czytanie

5 min czytania

Aktualizacja

15 kwietnia 2026 11:52

Jako frontend developer wysyłasz requesty do API i dostajesz JSON. Ale skąd te dane pochodzą? Z bazy danych. Zrozumienie jak działają bazy to klucz do lepszej współpracy z backendowcami i pisania fullstack aplikacji.

Krótka odpowiedź: SQL to język zapytań używany do pracy z relacyjnymi bazami danych. (np. PostgreSQL) dla danych z relacjami, stałą strukturą i wymogami spójności (e-commerce, CRM, finanse). NoSQL to szeroka grupa baz danych nierelacyjnych, zwykle lepiej dopasowanych do elastycznych modeli danych. (np. MongoDB) dla elastycznej struktury dokumentów i dużej zmienności danych. Redis jako uzupełnienie do cache i sesji — nie jako alternatywa.

W tym artykule poznasz fundamenty baz danych, różnicę między SQL i NoSQL, i nauczysz się podstawowych operacji.

Czym jest baza danych?

Baza danych to zorganizowany zbiór danych z mechanizmami do:

  • Przechowywania (storage)
  • Wyszukiwania (queries)
  • Aktualizacji (updates)
  • Zarządzania dostępem (permissions)

Bez bazy danych:

Code
// Dane w pamięci — giną po restarcie
let users = [
  { id: 1, name: 'Jan' },
  { id: 2, name: 'Anna' }
]

Z bazą danych:

Code
// Dane trwałe, skalowalne, bezpieczne
const users = await db.query('SELECT * FROM users')

SQL vs NoSQL — podstawowa różnica

SQL (relacyjne)

Dane w tabelach z wierszami i kolumnami:

Code
Tabela: users
┌────┬──────────┬─────────────────────┬─────────┐
│ id │ name     │ email               │ role    │
├────┼──────────┼─────────────────────┼─────────┤
│ 1  │ Jan      │ jan@example.com     │ admin   │
│ 2  │ Anna     │ anna@example.com    │ user    │
│ 3  │ Piotr    │ piotr@example.com   │ user    │
└────┴──────────┴─────────────────────┴─────────┘

Tabela: posts
┌────┬─────────┬──────────────────┐
│ id │ user_id │ title            │
├────┼─────────┼──────────────────┤
│ 1  │ 1       │ Pierwszy post    │
│ 2  │ 1       │ Drugi post       │
│ 3  │ 2       │ Aktualności      │
└────┴─────────┴──────────────────┘

Relacje łączą tabele (posts.user_id → users.id).

Popularne SQL:

  • PostgreSQL (częsty pierwszy wybór)
  • MySQL / MariaDB
  • SQLite (lokalny, bez serwera)
  • Microsoft SQL Server

NoSQL (nierelacyjne)

Dane w dokumentach (JSON-like):

Code
// Kolekcja: users
{
  "_id": "abc123",
  "name": "Jan",
  "email": "jan@example.com",
  "role": {
    "name": "admin",
    "permissions": ["read", "write", "delete"]
  },
  "posts": [
    { "title": "Pierwszy post", "date": "2025-01-15" },
    { "title": "Drugi post", "date": "2025-01-20" }
  ]
}

Dane mogą być zagnieżdżone, a schemat bywa bardziej elastyczny. To nie znaczy jednak, że schematu nie ma wcale — zwykle istnieje w kodzie, walidacji albo regułach aplikacji.

Popularne NoSQL:

  • MongoDB (dokumenty)
  • Redis (key-value, cache)
  • Firebase/Firestore
  • DynamoDB (AWS)

Kiedy SQL, kiedy NoSQL?

Najważniejsze uproszczenie do obalenia: to nie jest wojna religijna. Wiele systemów używa SQL jako głównej bazy, a obok Redis, Elastic albo dokumentowy store do konkretnych zadań.

Wybierz SQL gdy:

  • Dane mają stałą strukturę (users, orders, products)
  • Potrzebujesz relacji między danymi
  • Ważna jest spójność danych (transakcje)
  • Złożone zapytania i raportowanie

Przykłady: E-commerce, systemy bankowe, CRM, ERP

Wybierz NoSQL gdy:

  • Struktura danych się zmienia
  • Dane są hierarchiczne/zagnieżdżone
  • Potrzebujesz wysokiej skalowalności
  • Szybki prototyping

Przykłady: Social media, real-time apps, IoT, cache

Porównanie

CechaSQLNoSQL
StrukturaJawny schematElastyczny lub jawnie walidowany schemat
RelacjeTak (JOIN)Modelowane inaczej, często przez embedding lub referencje
SkalowalnośćPionowa (typowo)Pozioma (typowo)
TransakcjeACIDZależy od silnika i konkretnej operacji
ZapytaniaSQL (standaryzowany)Różne API, czyli Application Programming Interface, definiuje sposób komunikacji między aplikacjami lub modułami.
Use caseDane transakcyjne, raportowanieDane dokumentowe, cache, eventy, duża zmienność struktury

SQL — podstawy

Tworzenie tabeli

Code
CREATE TABLE users (
  id SERIAL PRIMARY KEY,
  name VARCHAR(100) NOT NULL,
  email VARCHAR(255) UNIQUE NOT NULL,
  role VARCHAR(50) NOT NULL DEFAULT 'user',
  last_login TIMESTAMP,
  created_at TIMESTAMP DEFAULT NOW()
);
 
CREATE TABLE posts (
  id SERIAL PRIMARY KEY,
  user_id INTEGER REFERENCES users(id),
  title VARCHAR(255) NOT NULL,
  content TEXT,
  created_at TIMESTAMP DEFAULT NOW()
);

CRUD — Create, Read, Update, Delete

CREATE (INSERT):

Code
INSERT INTO users (name, email) 
VALUES ('Jan Kowalski', 'jan@example.com');
 
INSERT INTO users (name, email) VALUES
  ('Anna Nowak', 'anna@example.com'),
  ('Piotr Wiśniewski', 'piotr@example.com');

READ (SELECT):

Code
-- Wszystkie kolumny
SELECT * FROM users;
 
-- Wybrane kolumny
SELECT name, email FROM users;
 
-- Z warunkiem
SELECT * FROM users WHERE name = 'Jan Kowalski';
 
-- Wiele warunków
SELECT * FROM users 
WHERE role = 'admin' AND created_at > '2025-01-01';
 
-- Sortowanie
SELECT * FROM users ORDER BY created_at DESC;
 
-- Limit
SELECT * FROM users LIMIT 10 OFFSET 20;
 
-- Zliczanie
SELECT COUNT(*) FROM users WHERE role = 'admin';

UPDATE:

Code
UPDATE users 
SET email = 'nowy@example.com' 
WHERE id = 1;
 
UPDATE users 
SET role = 'premium' 
WHERE created_at < '2024-01-01';

DELETE:

Code
DELETE FROM users WHERE id = 1;
 
DELETE FROM users WHERE last_login < '2024-01-01';

JOIN — łączenie tabel

Code
-- INNER JOIN — tylko pasujące
SELECT users.name, posts.title
FROM users
INNER JOIN posts ON users.id = posts.user_id;
 
-- LEFT JOIN — wszystkie z lewej + pasujące z prawej
SELECT users.name, posts.title
FROM users
LEFT JOIN posts ON users.id = posts.user_id;
-- Pokaże też userów bez postów (posts.title = NULL)
Code
users:           posts:
id | name        id | user_id | title
1  | Jan         1  | 1       | Post A
2  | Anna        2  | 1       | Post B
3  | Piotr       3  | 2       | Post C

INNER JOIN:
Jan  | Post A
Jan  | Post B
Anna | Post C

LEFT JOIN:
Jan   | Post A
Jan   | Post B
Anna  | Post C
Piotr | NULL

Agregacje

Code
-- Grupowanie
SELECT role, COUNT(*) as count
FROM users
GROUP BY role;
 
-- Z filtrem na grupy
SELECT role, COUNT(*) as count
FROM users
GROUP BY role
HAVING COUNT(*) > 5;
 
-- Średnia, suma, min, max
SELECT 
  AVG(price) as avg_price,
  SUM(quantity) as total_quantity,
  MIN(price) as cheapest,
  MAX(price) as most_expensive
FROM products;

NoSQL (MongoDB) — podstawy

Tworzenie dokumentu

Code
// Insert one
db.users.insertOne({
  name: "Jan Kowalski",
  email: "jan@example.com",
  role: "admin",
  posts: []
})
 
// Insert many
db.users.insertMany([
  { name: "Anna", email: "anna@example.com" },
  { name: "Piotr", email: "piotr@example.com" }
])

CRUD w MongoDB

READ (find):

Code
// Wszystkie dokumenty
db.users.find()
 
// Z filtrem
db.users.find({ role: "admin" })
 
// Wiele warunków
db.users.find({ 
  role: "admin", 
  createdAt: { $gt: new Date("2025-01-01") }
})
 
// Projekcja (wybrane pola)
db.users.find({}, { name: 1, email: 1 })
 
// Sortowanie i limit
db.users.find().sort({ createdAt: -1 }).limit(10)
 
// Jeden dokument
db.users.findOne({ email: "jan@example.com" })

UPDATE:

Code
// Update one
db.users.updateOne(
  { email: "jan@example.com" },
  { $set: { role: "superadmin" } }
)
 
// Update many
db.users.updateMany(
  { role: "user" },
  { $set: { verified: true } }
)
 
// Dodaj do tablicy
db.users.updateOne(
  { _id: ObjectId("...") },
  { $push: { posts: { title: "Nowy post" } } }
)

DELETE:

Code
db.users.deleteOne({ email: "jan@example.com" })
db.users.deleteMany({ lastLogin: { $lt: new Date("2024-01-01") } })

Operatory MongoDB

Code
// Porównanie
{ age: { $gt: 18 } }   // greater than
{ age: { $gte: 18 } }  // greater or equal
{ age: { $lt: 65 } }   // less than
{ age: { $lte: 65 } }  // less or equal
{ age: { $ne: 30 } }   // not equal
 
// Logiczne
{ $and: [{ age: { $gt: 18 } }, { role: "user" }] }
{ $or: [{ role: "admin" }, { role: "moderator" }] }
 
// Istnienie pola
{ avatar: { $exists: true } }
 
// W tablicy
{ tags: { $in: ["javascript", "react"] } }
{ tags: { $all: ["javascript", "react"] } }  // wszystkie
 
// Regex
{ name: { $regex: /^Jan/i } }

ORM/ODM — abstrakcja nad bazą

Zamiast pisać surowe SQL/queries, używaj ORM:

Prisma (SQL) — popularne z Next.js

Code
// schema.prisma
model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  name      String?
  posts     Post[]
}
 
model Post {
  id        Int      @id @default(autoincrement())
  title     String
  author    User     @relation(fields: [authorId], references: [id])
  authorId  Int
}
Code
// Queries
const users = await prisma.user.findMany()
 
const user = await prisma.user.create({
  data: {
    email: 'jan@example.com',
    name: 'Jan',
  }
})
 
const userWithPosts = await prisma.user.findUnique({
  where: { id: 1 },
  include: { posts: true }
})

Mongoose (MongoDB)

Code
// Schema
const userSchema = new Schema({
  name: { type: String, required: true },
  email: { type: String, required: true, unique: true },
  posts: [{ type: Schema.Types.ObjectId, ref: 'Post' }]
})
 
const User = mongoose.model('User', userSchema)
 
// Queries
const users = await User.find()
const user = await User.create({ name: 'Jan', email: 'jan@example.com' })
const userWithPosts = await User.findById(id).populate('posts')

Indeksy — przyspieszanie zapytań

Bez indeksu baza skanuje wszystkie wiersze. Z indeksem — szybkie wyszukiwanie.

Code
-- SQL
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_posts_user_id ON posts(user_id);
 
-- Sprawdź plan zapytania
EXPLAIN SELECT * FROM users WHERE email = 'jan@example.com';
Code
// MongoDB
db.users.createIndex({ email: 1 })
db.posts.createIndex({ userId: 1, createdAt: -1 })

Zasady:

  • Indeksuj kolumny używane w WHERE, JOIN, ORDER BY
  • Nie indeksuj wszystkiego — indeksy spowalniają INSERT/UPDATE
  • Compound indexes dla złożonych zapytań

Transakcje — spójność danych

Transakcja to grupa operacji, które wykonują się wszystkie albo żadna:

Code
BEGIN;
  UPDATE accounts SET balance = balance - 100 WHERE id = 1;
  UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
 
-- Jeśli coś pójdzie nie tak:
ROLLBACK;

ACID:

  • Atomicity — wszystko albo nic
  • Consistency — dane zawsze spójne
  • Isolation — transakcje nie przeszkadzają sobie
  • Durability — zatwierdzone dane są trwałe

FAQ

SQL czy NoSQL — co wybrać dla aplikacji webowej?

Dla większości aplikacji webowych SQL (PostgreSQL) to bezpieczniejszy i bardziej wszechstronny wybór. Daje transakcje ACID, relacje między tabelami, standaryzowany język zapytań i jest wspierany przez niemal każde środowisko hostingowe. NoSQL (MongoDB, Firestore) ma sens gdy struktura danych jest bardzo zmienna, dane są hierarchiczne (zagnieżdżone dokumenty) lub potrzebujesz poziomej skalowalności od pierwszego dnia.

Jaka jest różnica między SQL a NoSQL?

SQL przechowuje dane w tabelach z wierszami i kolumnami oraz jawnym schematem. Relacje między tabelami realizuje przez JOIN. NoSQL przechowuje dane jako dokumenty JSON, pary klucz-wartość, grafy lub kolumny — schemat jest elastyczny lub definiowany w kodzie. Kluczowa różnica praktyczna: SQL wymaga zdefiniowania struktury z góry, NoSQL pozwala na ewolucję struktury.

Kiedy używać PostgreSQL, a kiedy MongoDB?

PostgreSQL — gdy dane mają relacje (użytkownik ma zamówienia, zamówienie ma produkty), potrzebujesz ACID transakcji, piszesz złożone raporty lub agregacje, albo budujesz system, gdzie spójność danych jest krytyczna (e-commerce, finanse, auth). MongoDB — gdy dane to dokumenty o zmiennej strukturze, budujesz prototyp i schemat jeszcze się kształtuje, lub pracujesz z danymi hierarchicznymi gdzie embedding jest naturalny.

Co to jest ORM i kiedy go używać?

ORM (Object-Relational Mapper) to biblioteka, która tłumaczy operacje na obiektach w kodzie na zapytania SQL. Zamiast pisać SELECT * FROM users WHERE id = 1 piszesz prisma.user.findUnique({ where: { id: 1 } }). Warto używać ORM (Prisma, Drizzle, Sequelize) gdy zależy Ci na type-safety, automatycznych migracjach i czystym API. Nie rezygnuj jednak z rozumienia SQL — ORM nie zastąpi wiedzy o tym, jak działają zapytania i indeksy.

Czy mogę używać SQL i NoSQL w tym samym projekcie?

Tak i to jest normalna architektura. Typowy wzorzec: PostgreSQL jako główna baza transakcyjna, Redis do cache i sesji, Elasticsearch do wyszukiwania pełnotekstowego, S3 do plików. Nie musisz wybierać jednego rozwiązania — dobieraj narzędzia do konkretnych zadań.

Co to jest Redis i kiedy jest potrzebny?

Redis to baza danych in-memory operująca na parach klucz-wartość. Nie służy do przechowywania głównych danych aplikacji, lecz jako cache (przyspieszenie kosztownych zapytań), store sesji użytkowników i kolejka zadań. Dane w Redis są dostępne w mikrosekundach — stąd wartość przy często odczytywanych, niezmiennych (lub rzadko zmienianych) danych.

Jaką bazę danych wybrać dla projektu Next.js?

Dla nowego projektu Next.js popularnym stackiem jest PostgreSQL + Prisma jako ORM — Prisma ma świetną integrację z TypeScript i Next.js, a Supabase lub Neon oferują PostgreSQL hostowany z darmowym tieram. Jeśli potrzebujesz szybkiego startu bez konfiguracji bazy — Firebase lub PlanetScale. Dla cache i sesji — Upstash Redis (bezserwerowy Redis kompatybilny z Edge Runtime Next.js).

Podsumowanie

PotrzebujeszWybierz
Relacje między danymiSQL (PostgreSQL)
Elastyczna struktura lub dokumentyNoSQL (MongoDB)
Cache / sesjeRedis
Prototyp / MVPFirebase, Supabase
Enterprise / finansePostgreSQL, MySQL

Rekomendacja dla początkujących:

  1. Naucz się SQL — to fundament, nawet jeśli później użyjesz też NoSQL
  2. PostgreSQL — dla większości aplikacji webowych to bardzo dobry start
  3. Prisma — dobry ORM, ale najpierw rozumiej model danych, nie tylko API biblioteki

Bazy danych to ogromny temat. Ten artykuł to fundament — wystarczy, żebyś mógł rozmawiać z backendowcami i budować proste fullstack aplikacje.


Chcesz zobaczyć bazę danych w praktyce? Sprawdź tutorial Prisma + Next.js — fullstack projekt od zera.

Pracuję z tym zawodowo.

Jeśli chcesz uporządkować backendowe fundamenty aplikacji i uniknąć typowych błędów architektonicznych już na starcie, skontaktuj się ze mną. Pomagam przekładać wiedzę techniczną na rozwiązania, które da się sensownie utrzymać i rozwijać.

O autorze

Maciej Sala

Maciej Sala — project manager i frontendowiec z doświadczeniem w marketingu internetowym. Na co dzień pracuję z Reactem, Next.js i TypeScriptem, łącząc perspektywę produktową z praktycznym podejściem do kodu. Przez kilka lat związany z branżą gier wideo jako project manager i game designer.

Absolwent historii na Uniwersytecie Jagiellońskim i studiów podyplomowych z marketingu internetowego na Akademii Górniczo-Hutniczej w Krakowie. Poza pracą trenuje na siłowni, maluje figurki i realizuje własne projekty.

Biblioteka wiedzy

Czytaj dalej

Zobacz więcej wpisów
Anthropic uderza w Figmę i Adobe — oto Claude Design

Anthropic uderza w Figmę i Adobe — oto Claude Design

Anthropic wypuścił właśnie narzędzie AI do tworzenia stron, landing page'ów i prezentacji z promptu. Oto co wiemy o Claude Design i Opus 4.7 — i co to oznacza dla developerów.

Maciej Sala

Maciej Sala

Founder Strivelab

Astro.js vs Next.js — które narzędzie wybrać w 2026 roku?

Astro.js vs Next.js — które narzędzie wybrać w 2026 roku?

Fachowe porównanie Astro.js i Next.js z perspektywy developera pracującego na co dzień w Next.js. Architektura, wydajność, SEO, DX, koszty i konkretne use case — z benchmarkami i przykładami kodu.

Maciej Sala

Maciej Sala

Founder Strivelab