• Bazy danych
  • CREATE TABLE w SQL - kolumny, klucze i ograniczenia

CREATE TABLE w SQL - kolumny, klucze i ograniczenia

Tymoteusz Maciejewski • 15 października 2026
SQL CREATE TABLE dla tabeli pracownicy z kluczem głównym id. Klucz główny jest unikalny, niepusty i jeden na tabelę.

Spis treści

Masz utworzyć tabelę na zamówienia i po chwili okazuje się, że sama lista kolumn nie wystarczy: trzeba jeszcze ustalić, które pola mogą być puste i jak powiązać zamówienie z klientem. Instrukcja CREATE TABLE definiuje strukturę tabeli, ale szczegóły składni zależą od silnika bazy danych.

Dobra definicja tabeli zapobiega błędom, zanim trafią do danych

  • CREATE TABLE tworzy tabelę z kolumnami, typami danych i ograniczeniami.
  • PRIMARY KEY identyfikuje wiersz, a FOREIGN KEY pilnuje powiązania z inną tabelą.
  • NOT NULL blokuje brak wartości, a CHECK może ograniczyć dozwolone wartości.
  • Dialekt ma znaczenie: typy danych i mechanizm generowania identyfikatorów różnią się między PostgreSQL, MySQL, SQLite i SQL Server.
  • IF NOT EXISTS nie sprawdza, czy istniejąca tabela ma oczekiwaną strukturę.

CREATE TABLE opisuje kolumny i reguły dla nowych danych

Najprostsza instrukcja składa się z nazwy tabeli oraz definicji jej kolumn. Przy każdej kolumnie podajesz typ danych, a w razie potrzeby także reguły, które baza ma egzekwować.

CREATE TABLE tasks (
    task_id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    title VARCHAR(200) NOT NULL,
    status VARCHAR(20) NOT NULL DEFAULT 'new',
    created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    CONSTRAINT tasks_status_allowed
        CHECK (status IN ('new', 'in_progress', 'done'))
);

Ten przykład używa składni PostgreSQL. Identyfikator jest generowany przez bazę, tytuł nie może być pusty, a status dostaje domyślną wartość. Ograniczenie nazwane tasks_status_allowed odrzuca wartości spoza listy, więc literówka nie przejdzie po cichu do tabeli.

PRIMARY KEY oznacza klucz podstawowy, czyli unikatowy identyfikator wiersza. DEFAULT uzupełnia wartość, gdy instrukcja INSERT pomija kolumnę, ale nie zastępuje NOT NULL: te dwa zapisy odpowiadają na różne pytania.

Typ kolumny i generator identyfikatora zależą od silnika

Ten sam zamysł można zapisać inaczej w różnych bazach. Szczególnie łatwo pomylić automatyczny identyfikator z typem kolumny, bo PostgreSQL, MySQL, SQLite i SQL Server mają tu odmienne rozwiązania.

SilnikPrzykładowy zapis identyfikatoraNa co uważać
PostgreSQLINTEGER GENERATED ALWAYS AS IDENTITYMożna użyć BY DEFAULT, jeśli ręczne podanie wartości ma być dopuszczone w zwykłym INSERT.
MySQLINT AUTO_INCREMENTSkładnię trzeba dopasować do pozostałych definicji kolumn i klucza.
SQLiteINTEGER PRIMARY KEYTen zapis ma specjalne znaczenie dla identyfikatora wiersza; nie jest prostym odpowiednikiem każdej konstrukcji z innych baz.
SQL ServerINT IDENTITY(1,1)IDENTITY określa generowanie kolejnych wartości.

Różnice nie kończą się na identyfikatorze. Dokumentacja PostgreSQL, MySQL, SQLite i SQL Server opisuje składnię właściwą dla poszczególnych silników. Jeśli kod ma działać na więcej niż jednym, sprawdź każdy dialekt osobno, zamiast zakładać, że zamiana nazwy typu danych wystarczy.

Uważam, że przy pierwszej tabeli lepiej jawnie zapisać ograniczenia, niż liczyć na domyślne zachowanie silnika. Taka definicja od razu pokazuje, które dane są wymagane i co baza odrzuci.

Klucz obcy chroni powiązanie, a nie tylko opisuje je w kodzie

Jeśli zadanie ma należeć do projektu, sama kolumna project_id nie gwarantuje, że wskazuje istniejący projekt. Klucz obcy zapisuje tę relację w bazie i pozwala silnikowi kontrolować odwołania.

CREATE TABLE tasks (
    task_id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    project_id INTEGER NOT NULL,
    title VARCHAR(200) NOT NULL,
    CONSTRAINT tasks_project_fk
        FOREIGN KEY (project_id) REFERENCES projects(project_id)
);

Przed wykonaniem tej instrukcji tabela projects musi już istnieć, a wskazana kolumna powinna być kluczem lub mieć unikatowe wartości zgodnie z wymaganiami danego silnika. Przy usuwaniu projektu możesz też określić regułę, na przykład zablokować usunięcie, gdy istnieją zadania, albo skasować powiązane wiersze przez ON DELETE CASCADE. Drugi wariant jest wygodny, ale przy błędnym usunięciu może objąć cały zestaw danych.

Gdy pole może być nieznane, dopuszczenie NULL i ustawienie DEFAULT na NULL nie oznaczają tego samego co wymagana wartość domyślna. Z kolei CHECK (status IN (...)) sam nie zawsze zabrania braku statusu: na przykład SQLite nie uznaje wyniku NULL wyrażenia CHECK za naruszenie. Jeśli status jest obowiązkowy, dodaj również NOT NULL.

IF NOT EXISTS może ukryć niezgodny schemat

Skrypt uruchamia się ponownie i kończy bez błędu. Brzmi dobrze, dopóki istniejąca tabela nie okaże się starszą wersją bez nowej kolumny. IF NOT EXISTS pomija błąd przy istniejącej nazwie, ale nie porównuje definicji tabeli z tą w skrypcie.

To zachowanie jest opisane w dokumentacji PostgreSQL i MySQL. Dlatego taki zapis przydaje się do ograniczenia błędu „tabela już istnieje”, lecz nie zastępuje migracji schematu. Jeśli zmieniasz istniejącą strukturę, przygotuj osobną zmianę, na przykład przez ALTER TABLE, i sprawdź wynik w docelowej bazie.

SytuacjaMożliwy skutekCo zrobić
Tabela o tej nazwie już istnieje, a skrypt zawiera IF NOT EXISTSSkrypt nie zgłasza błędu, choć kolumny mogą się różnić.Sprawdź schemat tabeli i zastosuj migrację, jeśli definicja jest nieaktualna.
Klucz obcy wskazuje na tabelę, której jeszcze nie utworzonoUtworzenie ograniczenia może się nie udać.Twórz najpierw tabelę nadrzędną, a później zależną.
Wartość ma być wymagana, ale kolumna nie ma NOT NULLBaza może przyjąć NULL, nawet gdy zastosowano CHECK.Dodaj NOT NULL do kolumny wymaganej.
Składnia identyfikatora pochodzi z innego silnikaInstrukcja może zostać odrzucona albo działać inaczej, niż zakłada kod.Dopasuj definicję do silnika używanego w projekcie.

Przed uruchomieniem sprawdź trzy decyzje projektowe

Zanim zapiszesz definicję w repozytorium, ustal który silnik ją wykona, które kolumny muszą mieć wartość i jak tabela łączy się z resztą schematu. Te odpowiedzi prowadzą do właściwych typów, ograniczeń i kluczy.

  • Jeśli aplikacja używa jednego silnika, pisz składnię dla niego i zweryfikuj ją w jego dokumentacji.
  • Jeśli tabela ma być przenośna, ogranicz zależność od specyficznych typów i generatorów, a kod sprawdź w każdym docelowym silniku.
  • Jeśli zmieniasz istniejącą tabelę, nie polegaj na IF NOT EXISTS; zaplanuj migrację i sprawdź rzeczywistą strukturę.

Najlepsza definicja tabeli nie zgaduje, co autor miał na myśli. Zapisuje reguły w bazie, więc błędny status, pusty tytuł czy osierocone powiązanie zatrzymują się tam, gdzie powstały.

FAQ - Najczęstsze pytania

Przy każdej kolumnie określ typ danych, a dla wymaganych wartości dodaj NOT NULL. DEFAULT uzupełni pominiętą wartość podczas INSERT, ale nie zastępuje NOT NULL. Do ograniczenia listy dopuszczalnych wartości możesz użyć CHECK, na przykład CHECK (status IN ('new', 'in_progress', 'done')).

W PostgreSQL można użyć INTEGER GENERATED ALWAYS AS IDENTITY, w MySQL INT AUTO_INCREMENT, w SQLite INTEGER PRIMARY KEY, a w SQL Server INT IDENTITY(1,1). Te zapisy nie są wzajemnie wymienne, dlatego definicję trzeba dopasować do konkretnego silnika.

Jeśli kolumna project_id odwołuje się do projects(project_id), tabela projects musi już istnieć, a wskazana kolumna powinna być kluczem lub mieć unikatowe wartości zgodnie z wymaganiami silnika. Przy usuwaniu rekordu nadrzędnego można zablokować operację albo użyć ON DELETE CASCADE, które usuwa także powiązane wiersze.

Nie. IF NOT EXISTS pomija błąd, gdy tabela o tej nazwie już istnieje, ale nie porównuje jej kolumn ani ograniczeń z definicją w skrypcie. Przy zmianie istniejącej struktury trzeba sprawdzić schemat i przygotować migrację, na przykład z użyciem ALTER TABLE.

Nie zawsze. W artykule wskazano, że SQLite nie traktuje wyniku NULL wyrażenia CHECK jako naruszenia. Jeśli kolumna, na przykład status, ma być obowiązkowa, dodaj do niej także NOT NULL.

Oceń artykuł

Ocena: 4.45 Liczba głosów: 11

Tagi

klucze obce
typy danych
migracje
create table
ograniczenia
Autor Tymoteusz Maciejewski
Tymoteusz Maciejewski
Moje dni wypełnia zgłębianie kodu i analiza nowych technologii, zawsze z myślą o tym, jak najlepiej przekazać tę wiedzę dalej. Nazywam się Tymoteusz Maciejewski i od 12 lat aktywnie działam w świecie programowania, technologii i kariery w IT. Interesuje mnie nie tylko samo tworzenie oprogramowania, ale także proces, który prowadzi do zrozumienia złożonych zagadnień i uporządkowania ich w przystępny sposób. Na lukasz-zielinski.pl staram się dzielić się tym, co sam odkrywam, sprawdzając informacje u podstaw i dbając o to, by treści były aktualne i zrozumiałe dla każdego, kto chce rozwijać się w tej dynamicznej branży.

Udostępnij artykuł

Napisz komentarz