Najpierw wybierz, czy chcesz skopiować dane, schemat czy oba elementy naraz
-
INSERT INTO ... SELECTkopiuje dane do już istniejącej tabeli. -
CREATE TABLE AStworzy nową tabelę z wyniku zapytania i nadaje się do szybkich kopii danych. -
CREATE TABLE ... LIKEjest najlepsze, gdy chcesz sklonować strukturę, a nie tylko wynik zapytania. -
COPYsłuży do pracy z plikami, nie do bezpośredniego kopiowania między tabelami. - Przy dużych wolumenach danych najczęściej warto dodać indeksy i uruchomić
ANALYZEdopiero po załadowaniu danych.
Która metoda naprawdę pasuje do zadania
W PostgreSQL nie ma jednego polecenia, które „kopiuje tabelę” w każdym sensie. Ja rozbijam ten problem na cel biznesowy, bo od tego zależy składnia, wydajność i to, czy skopiuje się tylko zawartość, czy również ograniczenia, indeksy i domyślne wartości. Dokumentacja PostgreSQL jasno pokazuje, że te operacje mają różne zastosowania, więc warto wybrać je świadomie, a nie z rozpędu.
| Cel | Najlepsze polecenie | Co naprawdę kopiuje | Na co uważać |
|---|---|---|---|
| Nowa tabela z danymi | CREATE TABLE AS |
Wynik zapytania, czyli dane i kolumny wynikowe | Nie przenosi wiernie indeksów i ograniczeń |
| Dopisanie danych do istniejącej tabeli | INSERT INTO ... SELECT |
Tylko wiersze | Trzeba dopasować kolumny i typy danych |
| Skopiowanie samej struktury | CREATE TABLE ... LIKE |
Kolumny, typy i opcjonalnie więcej elementów schematu | Nie każde INCLUDING ma sens w każdej tabeli |
| Import albo eksport pliku |
COPY lub \copy
|
Dane między tabelą a plikiem | To nie jest bezpośredni transfer tabela-tabela |
| Starszy styl tworzenia tabeli z wyniku | SELECT INTO |
Nową tabelę z wyniku zapytania | Jest historyczne i zwykle ustępuje CREATE TABLE AS
|
Jeśli masz w głowie tylko jedną rzecz, zapamiętaj to: najpierw ustal, czy potrzebujesz nowej tabeli, czy tylko dopisania rekordów do istniejącej. Od tego zależy cała reszta. Dalej pokazuję każdy wariant osobno, żeby nie mieszać tych scenariuszy ze sobą.
Jak skopiować dane do nowej tabeli
Gdy chcesz szybko stworzyć nową tabelę na podstawie istniejącej i przenieść do niej dane, najprostszy wariant to CREATE TABLE AS. To dobry wybór do snapshotów, tabel roboczych i analitycznych kopii, bo działa prosto i czytelnie. Dokumentacja PostgreSQL podaje też krótszy zapis z TABLE, który kopiuje całą zawartość źródła bez przepisywania listy kolumn.
CREATE TABLE sales_copy AS
TABLE sales;
Jeśli potrzebujesz tylko wycinka danych, lepiej od razu zbudować kopię z warunkiem lub transformacją. To szczególnie przydatne w analizie danych, gdzie zwykle nie chcesz całej tabeli, tylko fragment zamknięty w konkretnym przedziale czasu albo zestaw rekordów spełniających warunek biznesowy.
CREATE TABLE sales_2026_q1 AS
SELECT id, customer_id, amount, created_at
FROM sales
WHERE created_at >= DATE '2026-01-01'
AND created_at < DATE '2026-04-01';
Warto pamiętać, że taka kopia jest jednorazowym wynikiem zapytania. Nowa tabela nie śledzi potem zmian w źródle. To zaleta, jeśli budujesz punkt w czasie do raportu, ale wada, jeśli oczekujesz automatycznej synchronizacji. Właśnie dlatego ten wariant tak dobrze pasuje do zadań analitycznych, a gorzej do pełnej repliki operacyjnej. Jeśli potrzebujesz pustej tabeli o tej samej budowie, przechodzę do następnego wariantu.
Jak odtworzyć samą strukturę bez danych
Jeżeli chcesz sklonować tabelę, ale bez rekordów, najbardziej precyzyjnym narzędziem jest CREATE TABLE ... LIKE. To rozwiązanie daje większą kontrolę niż tworzenie tabeli z wyniku zapytania, bo możesz zdecydować, które elementy schematu mają zostać przeniesione. W praktyce to mój pierwszy wybór dla tabel stagingowych, tymczasowych i pomocniczych.
CREATE TABLE sales_archive (
LIKE sales INCLUDING ALL
);
Opcja INCLUDING ALL jest wygodna, ale nie zawsze optymalna. Czasem lepiej świadomie wybrać tylko część elementów:
-
INCLUDING DEFAULTSprzydaje się, gdy chcesz zachować domyślne wartości kolumn. -
INCLUDING IDENTITYkopiuje definicję kolumn autoinkrementowanych, ale tworzy nową sekwencję dla nowej tabeli. -
INCLUDING INDEXESma sens wtedy, gdy naprawdę chcesz wierny klon, a nie lekką tabelę roboczą. -
INCLUDING CONSTRAINTSprzenosi ograniczeniaCHECK, aNOT NULLi tak jest kopiowane.
Jest tu jeden detal, który łatwo przeoczyć: jeśli domyślna wartość korzysta z funkcji typu nextval, to kopiowanie defaultów może tworzyć niezamierzone powiązanie między starą i nową tabelą. To nie jest błąd samego mechanizmu, tylko sygnał, że przy klonowaniu schematu trzeba patrzeć nie tylko na kolumny, ale też na logikę ich wartości. Gdy struktura jest już gotowa, naturalnym kolejnym krokiem staje się wypełnienie jej danymi.
Jak przepisać dane do istniejącej tabeli
Jeśli tabela docelowa już istnieje, najczęściej używam INSERT INTO ... SELECT. To najbardziej bezpośredni sposób kopiowania rekordów między tabelami, a jednocześnie najbardziej elastyczny. Możesz wskazać tylko część kolumn, zmienić ich kolejność, zrobić rzutowanie typów albo od razu przefiltrować dane.
INSERT INTO sales_archive (id, customer_id, amount, created_at)
SELECT id, customer_id, amount, created_at
FROM sales
WHERE status = 'paid';
Ten wariant jest bezpieczniejszy niż SELECT *, bo nie zakłada identycznego układu kolumn po obu stronach. W praktyce to ważne nawet wtedy, gdy dziś schematy są takie same, bo w kolejnej iteracji projektu ktoś doda kolumnę, zmieni kolejność albo wprowadzi kolumnę generowaną. Ja wolę dopisać listę pól raz, a potem mieć spokój.
INSERT INTO sales_archive (customer_id, amount_pln, copied_at)
SELECT customer_id, amount * 4.05, NOW()
FROM sales
WHERE created_at >= CURRENT_DATE - INTERVAL '30 days';
To właśnie tutaj najlepiej widać przewagę INSERT ... SELECT: nie tylko kopiujesz, ale od razu przekształcasz dane pod konkretny model analityczny. Jeśli chcesz uniknąć duplikatów, możesz też połączyć ten wzorzec z ON CONFLICT, ale sens takiego kroku zależy już od kluczy i reguł biznesowych w tabeli docelowej. Następny temat warto wyjaśnić osobno, bo bywa mylony z kopiowaniem tabel częściej niż wszystkie pozostałe.
Dlaczego COPY działa inaczej niż wiele osób zakłada
Tu najczęściej pojawia się nieporozumienie: COPY nie kopiuje tabeli do tabeli. Według dokumentacji PostgreSQL ten mechanizm przenosi dane między tabelą a plikiem w systemie plików. COPY TO eksportuje dane, a COPY FROM je importuje i dokleja do istniejącej tabeli.
COPY sales TO '/tmp/sales.csv' WITH (FORMAT csv, HEADER true);
COPY sales_import FROM '/tmp/sales.csv' WITH (FORMAT csv, HEADER true);
To rozwiązanie ma sens, gdy pracujesz z CSV, hurtowym importem albo eksportem na potrzeby integracji. Jest też bardzo szybkie przy dużych wolumenach, ale działa najlepiej w swoim właściwym środowisku: najpierw tworzysz tabelę, potem ładujesz dane, a dopiero na końcu budujesz indeksy. Gdy dane mają trafić z jednego wyniku SQL do drugiej tabeli w tej samej bazie, ja sięgam po INSERT INTO ... SELECT albo CREATE TABLE AS, nie po COPY. To oszczędza czas i usuwa pokusę, żeby klikać dookoła problemu zamiast rozwiązać go wprost.
Najczęstsze błędy przy duplikowaniu tabel
Przy kopiowaniu tabel problemy zwykle nie wynikają z samej składni, tylko z tego, że ktoś oczekuje od polecenia czegoś, czego ono nie robi. W praktyce widzę kilka powtarzalnych błędów:
-
Zakładanie, że
CREATE TABLE ASskopiuje indeksy i ograniczenia. Nie skopiuje ich w pełnym sensie schematu; dostajesz nową tabelę opartą o wynik zapytania. -
Używanie
SELECT *między tabelami o innym schemacie. To działa tylko wtedy, gdy układ i typy kolumn naprawdę się zgadzają. - Zapominanie o kolumnach generowanych i identity. W takich tabelach warto jawnie wskazać listę kolumn zamiast liczyć na automatyzm.
-
Próba użycia
COPYdo kopiowania między dwiema tabelami. To narzędzie od plików, nie od bezpośredniej replikacji SQL. - Załadowanie wielkiej ilości danych z aktywnymi indeksami i kluczami obcymi. Przy dużych wsadach to spowalnia operację bardziej, niż większość osób zakłada.
-
Brak
ANALYZEpo dużym załadunku. Bez aktualnych statystyk planner może wybrać gorszy plan zapytania.
Ten ostatni punkt jest szczególnie ważny w analizie danych. Tabela może być poprawnie skopiowana, a mimo to raporty po uruchomieniu działają wolniej, bo optymalizator nie zna jeszcze rozkładu wartości. Właśnie dlatego samo skopiowanie danych to dopiero połowa zadania.
Jak ja wybieram metodę przy analizie danych i migracjach
Jeżeli mam zbudować tabelę roboczą do analizy, zwykle idę najkrótszą drogą: CREATE TABLE AS, potem ewentualnie indeksy i na końcu ANALYZE. Jeśli mam odtworzyć strukturę do ładowania danych, wybieram CREATE TABLE ... LIKE, bo wtedy kontroluję schemat od początku. Gdy dokładam rekordy do tabeli już używanej przez aplikację lub raport, zostaję przy INSERT INTO ... SELECT. To prosta reguła, ale w praktyce oszczędza sporo błędów.
Dokumentacja PostgreSQL podpowiada też, że przy dużych importach najszybciej działa układ: utworzyć tabelę, załadować dane masowo, a dopiero później zbudować indeksy i odświeżyć statystyki. W projektach analitycznych to zwykle lepsze niż próba utrzymania wszystkiego w pełnej gotowości od pierwszej sekundy, bo zmniejsza narzut i przyspiesza cały proces.
Jeśli mam zostawić jedną praktyczną wskazówkę, to tę: nie kopiuj „tabeli” jako idei, tylko konkretny element, którego naprawdę potrzebujesz. Czasem chodzi o dane, czasem o schemat, a czasem o jednorazowy eksport do pliku. Gdy to rozdzielisz, SQL w PostgreSQL staje się prostszy, czytelniejszy i zwyczajnie bezpieczniejszy.