Blog JSystems - uwalniamy wiedzę!
Blog JSystems - uwalniamy wiedzę!
Z tego artykułu dowiesz się:
upsert, żeby uniknąć duplikatówPrawie każda firma z historią ma dwie bazy, które muszą się „widzieć". Stary system ERP siedzi na Oracle, a nowa aplikacja albo hurtownia raportowa stoi już na PostgreSQL - albo dowolna inna kombinacja baz, w tym Microsoft SQL Server, MySQL lub MariaDB, MongoDB czy Snowflake (n8n ma dla nich gotowe węzły). Dane trzeba przenieść raz przy starcie projektu, a potem regularnie dosynchronizowywać, żeby po obu stronach było to samo. Klasyczne odpowiedzi na ten problem to napisanie własnego skryptu (który potem ktoś musi utrzymywać) albo wdrożenie ciężkiego narzędzia ETL (drogiego i często przerośniętego dla prostej integracji). Pełną, klasyczną migrację całego schematu dedykowanym narzędziem opisaliśmy w osobnym przewodniku migracja Oracle do PostgreSQL - to inna metoda niż ta, którą pokażemy w tym artykule.
Jest trzecia droga: n8n. To narzędzie do automatyzacji typu low-code (mało kodu), w którym przepływ danych układasz z gotowych klocków zwanych węzłami. Ma natywne węzły do PostgreSQL, MySQL i Microsoft SQL Server, a Oracle dokładasz jednym kliknięciem. W tym artykule zbudujemy prawdziwą integrację Oracle do PostgreSQL i pokażemy ją na zrzutach z działającego workflow. Pokażemy też wariant z zapisem do MS SQL Server, bo ten sam wzorzec obsługuje dowolną bazę docelową. Termin ETL (Extract, Transform, Load, czyli pobierz, przekształć, załaduj) będzie się przewijał, ale całą tę robotę wyklikamy, zamiast ją programować.
Wartość n8n w tym zadaniu bierze się z trzech rzeczy. Po pierwsze, gotowe węzły baz danych: łączysz się przez formularz (host, port, użytkownik, hasło), a n8n zajmuje się sterownikiem i połączeniem. Po drugie, podgląd danych po każdym kroku: widzisz dokładnie, co wyszło z bazy źródłowej, co zrobiła transformacja i co wleciało do bazy docelowej. Po trzecie, wbudowany harmonogram, ponowienia i logi, więc synchronizacja cykliczna to kwestia jednego węzła, a nie osobnego zadania w systemie.
Poniżej efekt, do którego dojdziemy. To realny workflow uruchomiony w n8n: cztery węzły, wszystkie zielone, a nad połączeniami widać, ile elementów przepłynęło (jeden wynik z Oracle rozłożony na dwanaście wierszy zapisanych w PostgreSQL).
Start -> Oracle -> Mapuj typy i kolumny -> PostgreSQL. Nad połączeniami liczba przetworzonych elementówWeźmy typową sytuację. W bazie Oracle (stary ERP) mamy tabelę KONTRAHENCI z klientami firmy: identyfikator, nazwa, NIP, miasto, segment, data rejestracji, limit kredytowy i flaga aktywności. Chcemy przenieść tych kontrahentów do PostgreSQL, gdzie stoi nowa aplikacja i raportowanie. Tabela docelowa kontrahenci ma te same dane, ale w konwencji PostgreSQL: nazwy kolumn małymi literami, typ logiczny boolean zamiast liczbowej flagi, kolumna ze znacznikiem czasu ostatniej synchronizacji.
Ta pozorna drobnostka (wielkie kontra małe litery, NUMBER(1) kontra boolean, format daty) to sedno każdej integracji baz. Właśnie po to w środku przepływu stanie węzeł, który mapuje jedno na drugie. W naszym zbiorze jest dwunastu kontrahentów o łącznym limicie kredytowym 2 290 000 zł. Po migracji dokładnie te liczby powinny pojawić się po stronie PostgreSQL, co na koniec sprawdzimy.
PostgreSQL, MySQL i Microsoft SQL Server mają w n8n węzły od razu. Oracle dokładasz jako węzeł społecznościowy (community node), czyli rozszerzenie napisane przez społeczność. Wchodzisz w Ustawienia -> Community nodes, klikasz „Install", wpisujesz nazwę pakietu n8n-nodes-oracle-database i potwierdzasz. Po chwili w wyszukiwarce węzłów pojawia się „Oracle Database". Nowsze wydania n8n mają też natywny węzeł Oracle, ale wariant społecznościowy ma jedną wygodną cechę: łączy się w trybie thin, czyli czysto w JavaScripcie, bez instalowania klienta Oracle na serwerze.
Cała instalacja to kilka prostych kroków w panelu n8n:
Ustawienia -> Community nodes. Na początku lista jest pusta - klikasz pomarańczowy przycisk Install a community node
n8n-nodes-oracle-database, zaznaczasz zgodę na instalację i klikasz Install
n8n-nodes-oracle-database pojawia się na liście (jeden węzeł: Oracle Database) i jest już dostępny w wyszukiwarce węzłówZanim dodamy Oracle na kanwę, tworzymy sam przepływ. Każdy workflow w n8n zaczyna się od wyzwalacza (trigger) - węzła, który go uruchamia. W nowym, pustym workflow n8n pokazuje pole Add first step i pyta „What triggers this workflow?". Wybierasz Trigger manually (uruchomienie ręczne, przyciskiem Execute), bo migrację chcemy odpalać na żądanie, i nazywasz ten pierwszy węzeł Start migracji. To on będzie punktem startowym całego przepływu.
Z gotowym wyzwalaczem Start migracji na kanwie dodajesz węzeł źródła: klikasz +, wpisujesz Oracle w wyszukiwarce węzłów i wybierasz Oracle Database (dostępny właśnie dzięki zainstalowanemu pakietowi społecznościowemu).
Oracle w wyszukiwarce węzłów i wybierasz Oracle Database - to nasze źródło danychZanim jednak węzeł cokolwiek pobierze, trzeba podłączyć do niego poświadczenie bazy. Po dodaniu węzła Oracle Database na kanwę otwierasz go, a na górze ustawień masz pole Credential. Ponieważ nie utworzyliśmy jeszcze żadnego poświadczenia Oracle, widnieje tu napis No credentials yet i przycisk Set up credential. Pole SQL Statement zostawiasz na razie puste (szara podpowiedź to tylko przykład) - zapytanie wpiszemy dopiero po podłączeniu bazy.
Po kliknięciu Set up credential otwiera się formularz nowego połączenia (Oracle Credentials). Wypełniasz w nim trzy pola: użytkownika, hasło i łańcuch połączenia w formacie host:port/nazwa_uslugi (u nas serwis FREEPDB1), a na dole włączasz przełącznik Use Thin mode.
host:port/nazwa_uslugi, plus włączony przełącznik Use Thin modeDopiero z podłączonym poświadczeniem wracasz do węzła i wypełniasz jego jedyne pole - SQL Statement. Wpisujemy zwykły SELECT. Datę od razu formatujemy funkcją TO_CHAR na łańcuch YYYY-MM-DD, żeby przeszła przez sterownik w czystej, przewidywalnej postaci.
-- Zapytanie w węźle Oracle (źródło)
SELECT ID_KONTRAHENTA, NAZWA, NIP, MIASTO, SEGMENT,
TO_CHAR(DATA_REJESTRACJI, 'YYYY-MM-DD') AS DATA_REJESTRACJI,
LIMIT_KREDYTOWY, AKTYWNY
FROM KONTRAHENCI
ORDER BY ID_KONTRAHENTA
Po wpisaniu zapytania zostaje uruchomienie. Klikasz Execute step u góry węzła, a n8n łączy się z Oracle, wykonuje SELECT i w panelu OUTPUT po prawej pokazuje pobrane wiersze.
rows w środkuTu pojawia się pierwsza rzecz, którą trzeba wiedzieć o tym węźle: całe zapytanie zwraca jeden element, a wszystkie wiersze siedzą w polu rows tego elementu. To nie problem, tylko cecha, którą obsłużymy w następnym kroku. Widać to zresztą na zrzucie: po prawej stronie jest metadata (opis kolumn) i rows z listą kontrahentów.
Między źródłem a celem wstawiamy węzeł Code (kawałek JavaScriptu). Dodajemy go tak samo jak każdy inny węzeł: klikamy + (na kanwie albo na łączniku między dwoma węzłami), wpisujemy w wyszukiwarce Code i wybieramy z listy pozycję Code z podpisem „Run custom JavaScript or Python code". Jeśli klikniesz plus dokładnie na połączeniu Oracle z celem, n8n sam wepnie nowy węzeł w środek przepływu.
Po dodaniu węzeł robi dwie rzeczy naraz. Najpierw rozkłada pojedynczy element z Oracle na dwanaście osobnych wierszy (iterujemy po rows). Potem mapuje każdy wiersz na konwencję PostgreSQL: nazwy kolumn z wielkich liter na małe, liczbową flagę AKTYWNY (1 lub 0) na wartość logiczną true lub false. Datę zostawiamy jako łańcuch YYYY-MM-DD, bo PostgreSQL sam rzutuje ją na typ date.
// Węzeł Code: rozłóż wynik Oracle (pole rows) i zmapuj typy
// Oracle (WIELKIE litery, DATE, 1/0) -> PostgreSQL (małe litery, date, boolean)
const wynik = [];
for (const item of $input.all()) {
for (const r of item.json.rows) {
wynik.push({ json: {
id_kontrahenta: r.ID_KONTRAHENTA,
nazwa: r.NAZWA,
nip: r.NIP,
miasto: r.MIASTO,
segment: r.SEGMENT,
data_rejestracji: r.DATA_REJESTRACJI,
limit_kredytowy: r.LIMIT_KREDYTOWY,
aktywny: r.AKTYWNY === 1
}});
}
}
return wynik;
Efekt widać najlepiej właśnie w tym węźle. Po lewej stronie mamy surowe wejście z Oracle, w środku kod, a po prawej czystą tabelę dwunastu wierszy gotowych do zapisu: małe nazwy kolumn, kolumna aktywny jako true lub false, poprawne daty i liczby. To jest ta „litera T" z ETL, tylko wyklikana i widoczna gołym okiem.
Wskazówka: gdy mapowanie jest proste (tylko zmiana nazw kolumn), zamiast węzła Code możesz użyć wizualnego węzła Edit Fields i przeciągać pola myszką. Węzeł Code wybieramy, gdy potrzebujemy logiki, takiej jak konwersja 1/0 na wartość logiczną albo rozłożenie zagnieżdżonego wyniku na osobne wiersze.
Ostatni klocek to natywny węzeł PostgreSQL. Dokładasz go tak samo jak poprzednie: klikasz + na końcu przepływu, wpisujesz w wyszukiwarce węzłów Postgres i wybierasz z listy węzeł Postgres.
Najpierw podłączasz poświadczenie. W polu Credential wybierasz Create New Credential i wypełniasz zwykłe dane połączenia: host, bazę, użytkownika, hasło i port. Po zapisaniu n8n od razu testuje połączenie - zielony komunikat „Connection tested successfully" potwierdza, że baza odpowiada.
Z podłączonym poświadczeniem konfigurujesz sam zapis. Operację ustawiamy na Insert, wskazujemy schemat public, tabelę kontrahenci i listę kolumn do zapisu. n8n dopasowuje pola wejściowe do kolumn po nazwie, dlatego wcześniejsze mapowanie na małe litery było ważne: nazwy z węzła Code muszą zgadzać się z nazwami kolumn w PostgreSQL. Po kliknięciu Execute step w panelu OUTPUT po prawej pojawia się dwanaście faktycznie zapisanych wierszy.
A jeśli nazwy się nie zgadzają? n8n dopasowuje pola po nazwie, więc rozjazd kończy się na jeden z dwóch sposobów. Kolumna, dla której nie ma pasującego pola (klasyczny przypadek: węzeł Code zwraca NAZWA wielkimi literami, a kolumna w PostgreSQL to nazwa), dostaje wartość NULL - dane z niej po cichu przepadają. A jeśli taka pominięta kolumna ma ograniczenie NOT NULL (na przykład klucz główny id_kontrahenta), cały zapis kończy się błędem null value in column "id_kontrahenta" violates not-null constraint, a węzeł zatrzymuje przepływ na czerwono. Dlatego mapowanie w węźle Code na dokładnie te same, małe nazwy kolumn jest tak ważne - inaczej albo tracisz dane bez ostrzeżenia, albo dostajesz błąd zapisu. To samo dotyczy węzła Microsoft SQL i każdej innej bazy docelowej.
Na koniec sprawdzamy bazę docelową bezpośrednio zapytaniem SQL - w węźle PostgreSQL przełączamy operację na Execute Query i zliczamy wiersze. W PostgreSQL wylądowało dwanaście wierszy o łącznym limicie kredytowym 2 290 000 zł (dokładnie tyle, ile w Oracle) i jedenastu aktywnych kontrahentach, bo jeden miał flagę wyłączoną. Migracja przeszła w całości i z poprawnym mapowaniem typów.
Celem nie musi być PostgreSQL. Ta sama układanka działa dla Microsoft SQL Server - zmienia się tylko węzeł zapisu, a reszta przepływu (źródło Oracle i węzeł Code z mapowaniem) zostaje bez zmian. Węzeł MS SQL dodajesz identycznie: klikasz +, w wyszukiwarce wpisujesz Microsoft SQL i wybierasz węzeł z listy.
Poświadczenie wygląda niemal identycznie jak dla PostgreSQL, z drobną różnicą nazewnictwa: pole nazywa się Server zamiast Host, a domyślny port to 1433. Reszta to ta sama baza, użytkownik i hasło. Tu również n8n potwierdza połączenie od razu po zapisaniu.
Konfiguracja zapisu też jest bliźniacza: operacja Insert, tabela kontrahenci i ta sama lista kolumn. Węzeł MS SQL nie pyta osobno o schemat (domyślnie dbo). Po uruchomieniu w panelu OUTPUT znów pojawia się dwanaście zapisanych wierszy.
Weryfikacja jest analogiczna - Execute Query z zapytaniem zliczającym. Jedyna różnica składniowa to CAST(aktywny AS INT) zamiast CASE WHEN, bo w MS SQL typ logiczny to BIT. Wynik ten sam: 12 wierszy, 2 290 000 zł limitów i 11 aktywnych kontrahentów.
Jednorazowa migracja to dopiero połowa historii. W praktyce dane w Oracle się zmieniają, więc PostgreSQL (lub inną bazę docelową) trzeba regularnie dosynchronizowywać. Zmieniamy w tym celu dwie rzeczy. Pierwszą jest wyzwalacz: zamiast ręcznego startu wstawiamy węzeł Schedule Trigger, który sam uruchamia przepływ w zadanym rytmie, na przykład co godzinę.
Kolejność jest tu ważna, bo najpierw pozbywamy się starego wyzwalacza. Klikasz na kanwie węzeł Start migracji i naciskasz Delete (albo klikasz go prawym przyciskiem myszy i wybierasz Delete). Na kanwie zostaje sam potok - Oracle: pobierz kontrahentów, Mapuj typy i kolumny, PostgreSQL: zapisz - bez żadnego węzła startowego. Ponieważ każdy przepływ musi mieć wyzwalacz, n8n od razu pokazuje ten sam panel, co przy zakładaniu pustego workflow: What triggers this workflow? (gdyby nie pojawił się sam, otwierasz go przyciskiem + / Add first step na początku potoku). Tym razem w jego wyszukiwarce wpisujesz Schedule i wybierasz Schedule Trigger (na liście wyzwalaczy to pozycja „On a schedule" - uruchamianie według harmonogramu). Nowy wyzwalacz podłącza się przed węzłem Oracle i przejmuje rolę dawnego Start migracji.
Po wybraniu węzeł otwiera się na zakładce Parameters - i to tu ustawiasz rytm uruchomień. W sekcji Trigger Rules masz listę Trigger Interval, w której wybierasz jednostkę: Seconds, Minutes, Hours, Days, Weeks, Months albo Custom (Cron). W zależności od wyboru pojawiają się dodatkowe pola. Dla naszego „co godzinę" wybieramy Hours, ustawiamy Hours Between Triggers na 1 (co ile godzin, zakres 1-23), a w Trigger at Minute wpisujemy 0 - dzięki temu przepływ rusza na początku każdej godziny (o pełnej minucie). Gdybyś potrzebował innego rytmu, zmieniasz jednostkę: Days dodaje pola „o której godzinie" i „w której minucie", Weeks pozwala wskazać dni tygodnia, a Custom (Cron) udostępnia pole na wyrażenie cron, na przykład 0 6 * * 1-5 (w dni robocze o 6:00). Przyciskiem Add Rule możesz dołożyć kolejne reguły, jeśli chcesz kilka różnych harmonogramów w jednym wyzwalaczu (na przykład częściej w godzinach pracy, rzadziej nocą).
Drugą zmianą jest sposób zapisu. Przy powtarzalnym uruchamianiu zwykły insert próbowałby dodać te same wiersze po raz drugi i wywalił się na kluczu głównym. Zamiast tego używamy upsert (od update plus insert): jeśli kontrahent o danym identyfikatorze jeszcze nie istnieje, wiersz zostaje wstawiony; jeśli już jest, zostaje zaktualizowany. W PostgreSQL robi to konstrukcja INSERT ... ON CONFLICT ... DO UPDATE. W węźle PostgreSQL przełączamy operację na Execute Query i wpisujemy zapytanie z tą konstrukcją.
-- Węzeł PostgreSQL w trybie Execute Query: upsert (wstaw lub zaktualizuj)
INSERT INTO kontrahenci
(id_kontrahenta, nazwa, nip, miasto, segment, data_rejestracji, limit_kredytowy, aktywny)
VALUES
({{$json.id_kontrahenta}}, '{{$json.nazwa}}', '{{$json.nip}}', '{{$json.miasto}}',
'{{$json.segment}}', '{{$json.data_rejestracji}}', {{$json.limit_kredytowy}}, {{$json.aktywny}})
ON CONFLICT (id_kontrahenta) DO UPDATE SET
nazwa = EXCLUDED.nazwa, nip = EXCLUDED.nip, miasto = EXCLUDED.miasto,
segment = EXCLUDED.segment, data_rejestracji = EXCLUDED.data_rejestracji,
limit_kredytowy = EXCLUDED.limit_kredytowy, aktywny = EXCLUDED.aktywny,
zsynchronizowano = now();
Jeśli celem jest MS SQL Server, upsert robimy inaczej, bo ta baza nie zna ON CONFLICT. Odpowiednikiem jest polecenie MERGE, które w jednym kroku dopasowuje wiersze po kluczu i albo je aktualizuje (gałąź WHEN MATCHED), albo wstawia (gałąź WHEN NOT MATCHED). To samo zadanie, inna składnia:
-- Węzeł Microsoft SQL w trybie Execute Query: upsert (MERGE)
MERGE kontrahenci AS cel
USING (SELECT
{{$json.id_kontrahenta}} AS id_kontrahenta, '{{$json.nazwa}}' AS nazwa, '{{$json.nip}}' AS nip,
'{{$json.miasto}}' AS miasto, '{{$json.segment}}' AS segment,
'{{$json.data_rejestracji}}' AS data_rejestracji, {{$json.limit_kredytowy}} AS limit_kredytowy,
{{$json.aktywny}} AS aktywny
) AS zrodlo
ON cel.id_kontrahenta = zrodlo.id_kontrahenta
WHEN MATCHED THEN UPDATE SET
nazwa = zrodlo.nazwa, nip = zrodlo.nip, miasto = zrodlo.miasto,
segment = zrodlo.segment, data_rejestracji = zrodlo.data_rejestracji,
limit_kredytowy = zrodlo.limit_kredytowy, aktywny = zrodlo.aktywny,
zsynchronizowano = GETDATE()
WHEN NOT MATCHED THEN INSERT
(id_kontrahenta, nazwa, nip, miasto, segment, data_rejestracji, limit_kredytowy, aktywny)
VALUES
(zrodlo.id_kontrahenta, zrodlo.nazwa, zrodlo.nip, zrodlo.miasto,
zrodlo.segment, zrodlo.data_rejestracji, zrodlo.limit_kredytowy, zrodlo.aktywny);
Dwie różnice warte zapamiętania: w MS SQL polecenie MERGE musi kończyć się średnikiem (inaczej dostaniesz błąd składni), a bieżący czas bierzemy z funkcji GETDATE() zamiast now(). Reszta działa tak samo - jeden identyfikator dopasowuje wiersz, brak dopasowania oznacza wstawienie nowego.
Warto też ograniczyć, ile danych pobieramy ze źródła przy każdym przebiegu. Jeśli tabela źródłowa ma kolumnę ze znacznikiem modyfikacji (na przykład DATA_MODYFIKACJI albo updated_at), dokładamy do zapytania źródłowego warunek WHERE DATA_MODYFIKACJI >= SYSDATE - 1 i bierzemy tylko rekordy zmienione w ostatniej dobie. Takiej kolumny jednak nie zawsze mamy - wtedy zostają inne podejścia:
Niezależnie od wybranej strategii sam upsert gwarantuje, że powtórne przebiegi aktualizują istniejące wiersze zamiast tworzyć duplikaty.
Całość drugiego przepływu wygląda niemal tak samo jak migracja, tylko z innym wyzwalaczem i innym trybem zapisu. Uruchomiliśmy go i po stronie PostgreSQL dalej było dwanaście wierszy (żadnych duplikatów), za to ze świeżym znacznikiem synchronizacji, co potwierdza, że upsert zadziałał.
Najważniejsze w tym podejściu jest to, że wzorzec się nie zmienia. „Źródło -> mapowanie -> cel" wygląda identycznie niezależnie od tego, jakie bazy łączysz. Chcesz przenieść dane z Microsoft SQL Server do PostgreSQL? Podmieniasz pierwszy węzeł na natywny Microsoft SQL. Synchronizujesz MySQL z Oracle? Zmieniasz węzły na końcach, a środek (mapowanie typów) zostaje. Dzięki temu jeden raz nauczony schemat obsługuje dowolną parę baz, także w tę i z powrotem.
Jeśli pary baz nie musisz łączyć przez zewnętrzne narzędzie, tylko chcesz odpytywać jedną bazę z drugiej, to osobny mechanizm; opisaliśmy go w artykule o linkach bazodanowych w PostgreSQL. n8n przydaje się tam, gdzie dane trzeba realnie przenieść, przekształcić po drodze albo połączyć z innym systemem czy API.
n8n nie zastępuje wszystkiego. Przy jednorazowym przeniesieniu bardzo dużej bazy z pełnym, wiernym odwzorowaniem schematu, indeksów i ograniczeń lepiej sięgnąć po dedykowane narzędzie migracyjne. Rozłożyliśmy taki proces na czynniki pierwsze w osobnym tekście: migracja Oracle do PostgreSQL, kompletny przewodnik. n8n błyszczy tam, gdzie liczy się szybkość budowy, cykliczna synchronizacja i czytelny podgląd danych.
Do produkcyjnego uruchomienia takiego przepływu przyda się jeszcze jedno: własna, stabilna instancja n8n. Jak ją postawić, opisaliśmy w artykule jak postawić n8n na własnym serwerze. Wtedy integracje działają u Ciebie, a dane bazy nie opuszczają Twojej infrastruktury.
Cała ta układanka sprowadza się do jednego: integracja baz danych nie musi oznaczać pisania i utrzymywania ETL-a. Trzy węzły, jedno mapowanie i harmonogram wystarczą, żeby Oracle i PostgreSQL trzymały te same dane, a Ty widzisz każdy krok jak na dłoni zamiast zgadywać, co poszło nie tak w skrypcie.
Integracje z bazami i API, agenci AI, automatyzacja procesów w firmie i budowa własnych workflow na żywo podczas praktycznego szkolenia. Prowadzi Sebastian Koziatek, a szkolenie ma termin gwarantowany, więc odbędzie się niezależnie od liczby zgłoszeń.
n8n + AI: automatyzacja firmy
Komentarze (0)
Brak komentarzy...