{"id":74953,"date":"2020-03-22T08:42:22","date_gmt":"2020-03-22T05:42:22","guid":{"rendered":"https:\/\/prohoster.info\/blog\/administrirovanie\/dba-gramotno-organizovyvaem-sinhronizaczii-i-importy"},"modified":"2020-03-22T08:42:22","modified_gmt":"2020-03-22T05:42:22","slug":"dba-gramotno-organizovyvaem-sinhronizaczii-i-importy","status":"publish","type":"post","link":"https:\/\/prohoster.info\/da\/blog\/administrirovanie\/dba-gramotno-organizovyvaem-sinhronizaczii-i-importy","title":{"rendered":"DBA: organiserer synkroniseringer og importer korrekt","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>Ved kompleks behandling af store datas\u00e6t (forskellige <noindex><a rel=\"nofollow\" href=\"https:\/\/ru.wikipedia.org\/wiki\/ETL\">ETL-processer<\/a><\/noindex>: importer, konverteringer og synkronisering med eksterne kilder) opst\u00e5r der ofte behov for <b>at midlertidigt \"huske\", og hurtigt behandle<\/b> noget volumin\u00f8st.<\/p>\n<p>En typisk opgave af denne art lyder som regel nogenlunde s\u00e5dan her: <i>\"Her har <noindex><a rel=\"nofollow\" href=\"https:\/\/sbis.ru\/accounting\">regnskabsafdelingen hentet fra klientbank<\/a><\/noindex> de senest modtagne betalinger, de skal hurtigt overf\u00f8res til hjemmesiden og knyttes til regnskaberne\"<\/i><\/p>\n<p>Men n\u00e5r m\u00e6ngden af dette 'noget' begynder at m\u00e5les i hundrede megabyte, og tjenesten stadig skal fungere 24\/7 med databasen, opst\u00e5r der mange bivirkninger, der vil g\u00f8re dit liv besv\u00e6rligt.<br \/>\n<img decoding=\"async\" alt=\"DBA: organiserer synkroniseringer og importer korrekt\" src=\"\/wp-content\/uploads\/2020\/03\/f74afb2cd6f5f8de26a0932166933c95.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nFor at h\u00e5ndtere dem i PostgreSQL (og ikke kun der) kan man anvende nogle optimeringsmuligheder, som muligg\u00f8r hurtigere behandling med f\u00e6rre ressourceforbrug.<br \/>\n<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<h2>1. Hvor skal vi l\u00e6gge det?<\/h2>\n<p>\nLad os f\u00f8rst bestemme, hvor vi kan indl\u00e6se de data, vi \u00f8nsker at \"behandle\".<\/p>\n<h3>1.1. Midlertidige tabeller (TEMPORARY TABLE)<\/h3>\n<p>\nI princippet er midlertidige tabeller i PostgreSQL de samme som alle andre tabeller. Derfor er overtroen om, <i><b>\"der gemmes kun i hukommelsen, og den kan l\u00f8be t\u00f8r\"<\/b><\/i>forkert. Men der er ogs\u00e5 nogle v\u00e6sentlige forskelle.<\/p>\n<h4>En \"navnerum\" for hver forbindelse til DB<\/h4>\n<p>\nHvis to forbindelser fors\u00f8ger at udf\u00f8re <code>CREATE TABLE x<\/code>, vil nogen uundg\u00e5eligt f\u00e5 <b>fejl p\u00e5 grund af ikke-unikhed<\/b> af DB-objekter.<\/p>\n<p>Men hvis begge fors\u00f8ger at udf\u00f8re <code>CREATE <b>MIDLTID<\/b> TABEL x<\/code>, s\u00e5 vil begge g\u00f8re det uden problemer, og hver vil f\u00e5 <b>sin egen instans<\/b> af tabellen. Og der vil ikke v\u00e6re noget f\u00e6lles mellem dem.<\/p>\n<h4>\"Selvdestruktion\" ved frakobling<\/h4>\n<p>\nVed lukning af forbindelsen slettes alle midlertidige tabeller automatisk, s\u00e5 der er ingen mening i at udf\u00f8re <code>DROP TABLE x<\/code> udover\u2026<\/p>\n<p>Hvis du arbejder via <b>pgbouncer i transaktionsmode<\/b>, s\u00e5 tror databasen stadig, at denne forbindelse stadig er aktiv, og at denne midlertidige tabel stadig eksisterer i den.<\/p>\n<p>Derfor vil et fors\u00f8g p\u00e5 at oprette den igen, allerede fra en anden forbindelse til pgbouncer, f\u00f8re til en fejl. Men dette kan omg\u00e5s ved at bruge <code>OPRET MIDLERTIDIG TABEL <b>HVIS IKKE DER FINDES<\/b> x<\/code>.<\/p>\n<p>Men det er bedst ikke at g\u00f8re det, fordi man s\u00e5 pludselig kan opdage data tilbage fra \"den tidligere ejer\". I stedet er det meget bedre at l\u00e6se manualen n\u00f8je og se, at der ved oprettelse af tabellen er mulighed for at tilf\u00f8je <code>P\u00c5 COMMIT <b>DROP<\/b><\/code> \u2014 det vil sige, at n\u00e5r transaktionen er f\u00e6rdig, vil tabellen automatisk blive slettet.<\/p>\n<h4>Ikke-replikation<\/h4>\n<p>\nP\u00e5 grund af tilknytning kun til en bestemt forbindelse, replikeres midlertidige tabeller ikke. Til geng\u00e6ld <b>fjerner dette behovet for dobbelt dataregistrering<\/b> i heap + WAL, s\u00e5 INSERT\/UPDATE\/DELETE i den er betydeligt hurtigere.<\/p>\n<p>Men da en midlertidig tabel stadig er en \"n\u00e6sten almindelig\" tabel, kan den heller ikke oprettes p\u00e5 replika. I det mindste ikke endnu, selvom en passende patch har v\u00e6ret tilg\u00e6ngelig i lang tid.<\/p>\n<h3>1.2. Uregistrerede tabeller (UNLOGGED TABLE)<\/h3>\n<p>\nMen hvad g\u00f8r man, hvis man har en tung ETL-proces, som ikke kan implementeres i en enkelt transaktion, og man har stadig <b>pgbouncer i transaktionsmode<\/b>?..<\/p>\n<p>Eller hvis datastreamen er s\u00e5 stor, at <b>kapaciteten af \u00e9n forbindelse<\/b> til DB (l\u00e6s: \u00e9n proces p\u00e5 CPU) ikke er tilstr\u00e6kkelig? ..<\/p>\n<p>Eller hvis nogle operationer foreg\u00e5r <b>asynkront<\/b> i forskellige forbindelser? ..<\/p>\n<p>Her er der kun \u00e9n mulighed \u2014 <b>tilf\u00e6ldig oprettelse af en ikke-midlertidig tabel<\/b>. Sp\u00f8g, ikke? Det vil sige:<\/p>\n<ul>\n<li>oprettede \"mine\" tabeller med maksimalt tilf\u00e6ldige navne, s\u00e5 jeg ikke krydser med nogen<\/li>\n<li><b>Udvind<\/b>: indl\u00e6ste data i dem fra en ekstern kilde<\/li>\n<li><b>Transformere<\/b>: omformede, udfyldte n\u00f8gleforbindelsesfelter<\/li>\n<li><b>Belastning<\/b>: overf\u00f8rte de f\u00e6rdige data til m\u00e5l tabeller<\/li>\n<li>slettede \"mine\" tabeller<\/li>\n<\/ul>\n<p>\nMen nu \u2014 en teskefuld bitterhed. I princippet <b>er hele registreringen i PostgreSQL faktisk to gange<\/b> \u2014 <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/postgrespro\/blog\/461523\/\">f\u00f8rst i WAL<\/a><\/noindex>, derefter i tabeller\/indeksens krop. Alt dette er gjort for at underst\u00f8tte ACID og korrekt synlighed af data mellem <code>COMMIT<\/code>\u2018inddragne og <code>ROLLBACK<\/code>\u2018inddragne transaktioner.<\/p>\n<p>Men vi har ikke brug for det! Hele vores proces <b>er enten fuldf\u00f8rt med succes, eller ogs\u00e5 ikke.<\/b>. Det er ligegyldigt, hvor mange mellemtransaktioner der vil v\u00e6re \u2014 vi er ikke interesserede i at \"forts\u00e6tte processen fra midten\", is\u00e6r n\u00e5r det er uklart, hvor den var.<\/p>\n<p>Til dette form\u00e5l introducerede PostgreSQL-udviklerne allerede i version 9.1 s\u00e5dan en funktion som <noindex><a rel=\"nofollow\" href=\"https:\/\/postgrespro.ru\/docs\/postgresql\/12\/sql-createtable#SQL-CREATETABLE-UNLOGGED\">uregnede (UNLOGGED) tabeller.<\/a><\/noindex>:<\/p>\n<blockquote><p>Med denne indikation oprettes tabellen som uregistreret. Data, der skrives til uregistrerede tabeller, g\u00e5r ikke gennem pre-write-journalen (se Kap. 29), hvilket resulterer i, at s\u00e5danne tabeller <b>arbejder meget hurtigere end almindelige.<\/b>Men de er ikke beskyttet mod fejl; ved fejl eller n\u00f8dafbrydelse af serveren bliver den uregistrerede tabel <b>automatisk besk\u00e5ret.<\/b>Desuden replikeres indholdet af den uregistrerede tabel <b>ikke.<\/b> til styrede servere. Enhver indeks, der oprettes for en ikke-journaleret tabel, bliver automatisk ikke-journaleret.<\/p><\/blockquote>\n<p>Kort sagt, <b>vil det v\u00e6re meget hurtigere<\/b>, men hvis databaseserveren \"g\u00e5r ned\" \u2014 vil det v\u00e6re ubehageligt. Men hvor ofte sker det, og kan din ETL-proces korrekt bearbejde det \"fra midten\", efter \"genoplivning\" af databasen?..<\/p>\n<p>Hvis ikke, og dit tilf\u00e6lde ligner det ovenst\u00e5ende \u2014 brug <code>UNLOGGED<\/code>, men aldrig <b>aktiver denne egenskab p\u00e5 rigtige tabeller<\/b>, hvis dataene er vigtige for dig.<\/p>\n<h3>1.3. ON COMMIT { DELETE ROWS | DROP }<\/h3>\n<p>\nDenne konstruktion giver mulighed for, at der kan angives automatisk opf\u00f8rsel ved afslutningen af en transaktion ved oprettelse af en tabel.<\/p>\n<p>Om <code>P\u00c5 COMMIT <b>DROP<\/b><\/code> jeg har allerede skrevet ovenfor, den genererer <code>DROP TABLE<\/code>, men her med <code>P\u00c5 COMMIT <b>SLET RADER<\/b><\/code> situationen er mere interessant \u2014 her genereres <code>TRUNCATE TABLE<\/code>.<\/p>\n<p>Da hele infrastrukturen til opbevaring af metabeskrivelserne for den midlertidige tabel er pr\u00e6cis den samme som for en normal, <b>f\u00f8rer konstant oprettelse og sletning af midlertidige tabeller til kraftig \"oppustning\" af systemtabellerne<\/b> pg_class, pg_attribute, pg_attrdef, pg_depend,\u2026<\/p>\n<p>Forestil dig nu, at du har en worker med en direkte forbindelse til databasen, der hver sekund \u00e5bner en ny transaktion, opretter, fylder, behandler og sletter en midlertidig tabel\u2026 Der vil hobe sig affald op i systemtabellerne, hvilket medf\u00f8rer un\u00f8dvendige forsinkelser ved hver operation.<\/p>\n<p>Generelt, lad v\u00e6re med det! I dette tilf\u00e6lde er det langt mere effektivt <code>at placere CREATE TEMPORARY TABLE x ... ON COMMIT DELETE ROWS<\/code> uden for transaktionscyklussen \u2014 s\u00e5 vil tabellerne allerede <b>v\u00e6re eksisteret ved starten af hver ny transaktion<\/b> (vi sparer et kald <code>CREATE<\/code>), men <b>vil v\u00e6re tom<\/b>, takket v\u00e6re <code>TRUNCATE<\/code> (vi har ogs\u00e5 sparet dette kald) ved afslutningen af den tidligere transaktion.<\/p>\n<h3>1.4. SOM\u2026 INKLUSIVE \u2026<\/h3>\n<p>\nJeg n\u00e6vnte indledningsvis, at en af de typiske anvendelser for midlertidige tabeller er forskellige former for importer \u2014 og udvikleren kopierer tr\u00e6t listen over felterne fra den m\u00e5lrettede tabel til sin midlertidige erkl\u00e6ring\u2026<\/p>\n<p>Men dovenskab er fremdriftens driver! Derfor <b>kan man oprette en ny tabel \"efter skabelon\"<\/b> meget lettere:<\/p>\n<pre><code class=\"sql\">CREATE TEMPORARY TABLE import_table(\n  LIKE target_table\n);<\/code><\/pre>\n<p>\nDa der kan genereres mange data i denne tabel, vil s\u00f8gning i den ikke v\u00e6re hurtig. Men der er en traditionel l\u00f8sning mod dette \u2014 indekser! Og ja, <b>kan midlertidige tabeller ogs\u00e5 have indekser<\/b>.<\/p>\n<p>Da de n\u00f8dvendige indekser ofte er identiske med indekserne fra den m\u00e5lrettede tabel, kan man blot skrive <code>LIKE m\u00e5l_tabel <b>INDBEFATTER INDICES<\/b><\/code>.<\/p>\n<p>Hvis du ogs\u00e5 har brug for <code>DEFAULT<\/code>-v\u00e6rdier (for eksempel til udfyldelse af prim\u00e6rn\u00f8glev\u00e6rdier), kan du benytte <code>LIKE m\u00e5l_tabel <b>INDEHOLDENDE STANDARDINDSTILLINGER<\/b><\/code>. Eller bare \u2014 <code>LIKE m\u00e5l_tabel <b>INDSLUTTEN ALT<\/b><\/code> \u2014 kopierer standarder, indekser, begr\u00e6nsninger,\u2026<\/p>\n<p>Men her skal man forst\u00e5, at hvis du har oprettet <b>import-tabellen med indeks fra starten, s\u00e5 vil indtastningen v\u00e6re langsommere<\/b>, end hvis du f\u00f8rst indl\u00e6ser alt og derefter tilf\u00f8jer indekserne \u2014 se hvordan det g\u00f8r <noindex><a rel=\"nofollow\" href=\"https:\/\/postgrespro.ru\/docs\/postgresql\/12\/app-pgdump\">pg_dump<\/a><\/noindex>.<\/p>\n<p>Generelt, <noindex><a rel=\"nofollow\" href=\"https:\/\/postgrespro.ru\/docs\/postgresql\/12\/sql-createtable\">RTFM<\/a><\/noindex>!<\/p>\n<h2>2. Hvordan skriver man?<\/h2>\n<p>\nJeg vil sige det klart \u2014 brug <code><noindex><a rel=\"nofollow\" href=\"https:\/\/postgrespro.ru\/docs\/postgresql\/12\/sql-copy\">COPY<\/a><\/noindex><\/code>-str\u00f8m i stedet for \"bunker\" <code>INSERT<\/code>, <noindex><a rel=\"nofollow\" href=\"https:\/\/www.citusdata.com\/blog\/2017\/11\/08\/faster-bulk-loading-in-postgresql-with-copy\/\">acceleration mange gange<\/a><\/noindex>. Du kan endda g\u00f8re det direkte fra en foruddefineret fil.<\/p>\n<h2>3. Hvordan behandler man?<\/h2>\n<p>\nLad os sige, at vores input ser nogenlunde s\u00e5ledes ud:<\/p>\n<ul>\n<li>du har en tabel med klientdata i databasen <b>1M poster<\/b><\/li>\n<li>hver dag sender kunden dig en ny <b>fuld \"billede\"<\/b><\/li>\n<li>efter erfaring ved du, at der fra gang til gang <b>\u00e6ndres ikke mere end 10K poster<\/b><\/li>\n<\/ul>\n<p>\nEt klassisk eksempel p\u00e5 en s\u00e5dan situation er <noindex><a rel=\"nofollow\" href=\"https:\/\/www.gnivc.ru\/technical_support\/classifiers_reference\/kladr\/\">KLADR-databasen<\/a><\/noindex> \u2014 der er mange adresser i alt, men i hver ugentlig eksport med \u00e6ndringer (\u00e6ndringer af bynavne, sammensl\u00e5inger af gader, optr\u00e6dener af nye bygninger) er der ganske f\u00e5 selv p\u00e5 landsplan.<\/p>\n<h3>3.1. Algoritme for fuld synkronisering<\/h3>\n<p>\nFor enkelhedens skyld antag, at du ikke engang beh\u00f8ver at restrukturere data \u2014 bare bring tabellen i den \u00f8nskede form, det vil sige:<\/p>\n<ul>\n<li><b>at slette<\/b> alt, hvad der allerede ikke er<\/li>\n<li><b>Muligheder for processorerne \u00abElbrus\u00bb er tilg\u00e6ngelige efter<\/b> alt, hvad der allerede har v\u00e6ret, og som skal opdateres<\/li>\n<li><b>inds\u00e6tte<\/b> alt, hvad der endnu ikke har v\u00e6ret<\/li>\n<\/ul>\n<p>\nHvorfor skal man g\u00f8re operationer i denne r\u00e6kkef\u00f8lge? Fordi det p\u00e5 denne m\u00e5de vil minimere st\u00f8rrelsen af tabellen (<noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/tensor\/blog\/491366\/\">huske MVCC!<\/a><\/noindex>).<\/p>\n<h4>DELETE FROM dst<\/h4>\n<p>\nNej, selvf\u00f8lgelig kan man klare sig med kun to operationer:<\/p>\n<ul>\n<li><b>at slette<\/b> (<code>DELETE<\/code>) faktisk alt<\/li>\n<li><b>inds\u00e6tte<\/b> alt fra det nye billede<\/li>\n<\/ul>\n<p>\nMen takket v\u00e6re MVCC, <b>vil st\u00f8rrelsen af tabellen stige pr\u00e6cist til det dobbelte<\/b>! At f\u00e5 +1M billeder af poster i tabellen p\u00e5 grund af at opdatere 10K \u2014 ikke s\u00e6rlig god overfl\u00f8dighed\u2026<\/p>\n<h4>TRUNCATE dst<\/h4>\n<p>\nEn mere erfaren udvikler ved, at man ret billigt kan rense hele tabellen:<\/p>\n<ul>\n<li><b>ryd<\/b> (<code>TRUNCATE<\/code>) hele tabellen<\/li>\n<li><b>inds\u00e6tte<\/b> alt fra det nye billede<\/li>\n<\/ul>\n<p>\nEn effektiv metode, <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/tensor\/blog\/481866\/\">nogle gange helt anvendelig<\/a><\/noindex>, men der er en udfordring\u2026 At indl\u00e6se 1M poster vil tage lang tid, s\u00e5 vi kan ikke tillade os at lade tabellen v\u00e6re tom i denne periode (som vil ske uden at indg\u00e5 i en samlet transaktion).<\/p>\n<p>Det betyder:<\/p>\n<ul>\n<li>vi starter <b>en langvarig transaktion<\/b><\/li>\n<li><code>TRUNCATE<\/code> p\u00e5virker <b>AccessExclusive<\/b>-l\u00e5ser<\/li>\n<li>vi bruger lang tid p\u00e5 at inds\u00e6tte, og alle andre kan ikke engang <b>Noget her er ikke godt\u2026 <code>SELECT<\/code><\/b><\/li>\n<\/ul>\n<p>\nALTER TABLE\u2026 RENAME\u2026 \/ DROP TABLE \u2026<\/p>\n<h4>ALTER TABLE\u2026 RENAME\u2026 \/ DROP TABLE \u2026<\/h4>\n<p>\nEn mulighed er at uploade alt til en ny separat tabel og derefter blot omd\u00f8be den til den gamle. Der er et par irriterende sm\u00e5ting:<\/p>\n<ul>\n<li>det samme <b>AccessExclusive<\/b>, selvom det tager betydeligt mindre tid<\/li>\n<li>alle foresp\u00f8rgselsplaner\/statistik for denne tabel nulstilles, <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/tensor\/blog\/479656\/\">man skal k\u00f8re ANALYZE<\/a><\/noindex><\/li>\n<li><b>alle fremmedn\u00f8gler brydes<\/b> (FK) p\u00e5 tabellen<\/li>\n<\/ul>\n<p>\nDer var en WIP-patch fra Simon Riggs, der foreslog at lave <code>ALTER<\/code>-en operation for at erstatte tabelens krop p\u00e5 filniveau uden at r\u00f8re statistikken og FK, men der blev ikke samlet quorum.<\/p>\n<h4>DELETE, UPDATE, INSERT<\/h4>\n<p>\nS\u00e5 vi stopper ved en ikke-blokerende mulighed best\u00e5ende af tre operationer. N\u00e6sten tre... Hvordan kan vi g\u00f8re dette mest effektivt?<\/p>\n<pre><code class=\"sql\">-- alting sker inden for transaktionen, s\u00e5 ingen ser 'mellemliggende' tilstande\nBEGIN;\n\n-- opretter en midlertidig tabel med de importerede data\nCREATE TEMPORARY TABLE tmp(\n  LIKE dst INCLUDING INDEXES -- efter m\u00f8nster, sammen med indekserne\n) ON COMMIT DROP; -- uden for transaktionen er den ikke n\u00f8dvendig for os\n\n-- hurtigt flyder vi den nye version ind via COPY\nCOPY tmp FROM STDIN;\n-- ...\n-- .\n\n-- sletter de manglende\nDELETE FROM\n  dst D\nUSING\n  dst X\nLEFT JOIN\n  tmp Y\n    USING(pk1, pk2) -- felterne af den prim\u00e6re n\u00f8gle\nWHERE\n  (D.pk1, D.pk2) = (X.pk1, X.pk2) AND\n  Y IS NOT DISTINCT FROM NULL; -- 'anti-join'\n\n-- opdaterer de resterende\nUPDATE\n  dst D\nSET\n  (f1, f2, f3) = (T.f1, T.f2, T.f3)\nFROM\n  tmp T\nWHERE\n  (D.pk1, D.pk2) = (T.pk1, T.pk2) AND\n  (D.f1, D.f2, D.f3) IS DISTINCT FROM (T.f1, T.f2, T.f3); -- der er ikke nogen grund til at opdatere matchende\n\n-- inds\u00e6tter de manglende\nINSERT INTO\n  dst\nSELECT\n  T.*\nFROM\n  tmp T\nLEFT JOIN\n  dst D\n    USING(pk1, pk2)\nWHERE\n  D IS NOT DISTINCT FROM NULL;\n\nCOMMIT;\n<\/code><\/pre>\n<p><\/p>\n<h3>3.2. Postbehandling af import<\/h3>\n<p>\nI den samme KLADe skal alle \u00e6ndrede poster desuden gennemg\u00e5 postbehandling \u2014 normalisere, udtr\u00e6kke s\u00f8geord, bringe dem til de \u00f8nskede strukturer. Men hvordan ved man \u2014 <b>hvad pr\u00e6cist der blev \u00e6ndret<\/b>, uden at komplicere synkroniseringskoden, ideelt set uden at r\u00f8re ved den overhovedet?<\/p>\n<p>Hvis skriveadgangen under synkroniseringen kun er tilg\u00e6ngelig for din proces, kan man anvende en trigger, der indsamler alle \u00e6ndringer for os:<\/p>\n<pre><code class=\"sql\">-- m\u00e5l tabeller\nCREATE TABLE kladr(...);\nCREATE TABLE kladr_house(...);\n\n-- tabeller med \u00e6ndringshistorik\nCREATE TABLE kladr$log(\n  ro kladr, -- her ligger hele billeder af gamle\/ny registreringer\n  rn kladr\n);\n\nCREATE TABLE kladr_house$log(\n  ro kladr_house,\n  rn kladr_house\n);\n\n-- generel funktion til logf\u00f8ring af \u00e6ndringer\nCREATE OR REPLACE FUNCTION diff$log() RETURNS trigger AS $$\nDECLARE\n  dst varchar = TG_TABLE_NAME || '$log';\n  stmt text = '';\nBEGIN\n  -- tjekker n\u00f8dvendigheden af logging ved opdatering af registrering\n  IF TG_OP = 'UPDATE' THEN\n    IF NEW IS NOT DISTINCT FROM OLD THEN\n      RETURN NEW;\n    END IF;\n  END IF;\n  -- opretter en logpost\n  stmt = 'INSERT INTO ' || dst::text || '(ro,rn)VALUES(';\n  CASE TG_OP\n    WHEN 'INSERT' THEN\n      EXECUTE stmt || 'NULL,$1)' USING NEW;\n    WHEN 'UPDATE' THEN\n      EXECUTE stmt || '$1,$2)' USING OLD, NEW;\n    WHEN 'DELETE' THEN\n      EXECUTE stmt || '$1,NULL)' USING OLD;\n  END CASE;\n  RETURN NEW;\nEND;\n$$ LANGUAGE plpgsql;\n<\/code><\/pre>\n<p>\nNu kan vi, f\u00f8r synkroniseringen, anvende (eller aktivere via <code>ALTER TABLE ... ENABLE TRIGGER ...<\/code>):<\/p>\n<pre><code class=\"sql\">CREATE TRIGGER log\n  AFTER INSERT OR UPDATE OR DELETE\n  ON kladr\n    FOR EACH ROW\n      EXECUTE PROCEDURE diff$log();\n\nCREATE TRIGGER log\n  AFTER INSERT OR UPDATE OR DELETE\n  ON kladr_house\n    FOR EACH ROW\n      EXECUTE PROCEDURE diff$log();\n<\/code><\/pre>\n<p>\nOg derefter kan vi roligt hente de n\u00f8dvendige \u00e6ndringer fra log-tabellerne og viderebehandle dem.<\/p>\n<h3>3.3. Import af relaterede datas\u00e6t<\/h3>\n<p>\nOvenfor har vi set tilf\u00e6lde, hvor datakstruktur i kilden og modtageren stemmer overens. Men hvad g\u00f8r vi, hvis eksporten fra et eksternt system har et format, der adskiller sig fra strukturen i vores database?<\/p>\n<p>Lad os tage opbevaring af kunder og deres fakturaer som eksempel, et klassisk eksempel p\u00e5 \"mange-til-en\":<\/p>\n<pre><code class=\"sql\">CREATE TABLE client(\n  client_id\n    serial\n      PRIMARY KEY\n, inn\n    varchar\n      UNIQUE\n, name\n    varchar\n);\n\nCREATE TABLE invoice(\n  invoice_id\n    serial\n      PRIMARY KEY\n, client_id\n    integer\n      REFERENCES client(client_id)\n, number\n    varchar\n, dt\n    date\n, sum\n    numeric(32,2)\n);<\/code><\/pre>\n<p>\nOg her kommer eksporten fra den eksterne kilde som en \"alt-i-\u00e9n\":<\/p>\n<pre><code class=\"sql\">CREATE TEMPORARY TABLE invoice_import(\n  client_inn\n    varchar\n, client_name\n    varchar\n, invoice_number\n    varchar\n, invoice_dt\n    date\n, invoice_sum\n    numeric(32,2)\n);<\/code><\/pre>\n<p>\nDet er \u00e5benlyst, at kundedata kan duplikere i denne variant, og hovedposten er \"fakturaen\":<\/p>\n<pre><code class=\"plaintext\">0123456789;Vasya;A-01;2020-03-16;1000.00\n9876543210;Petya;A-02;2020-03-16;666.00\n0123456789;Vasya;B-03;2020-03-16;9999.00\n<\/code><\/pre>\n<p>\nFor modellen vil vi blot inds\u00e6tte vores testdata, men husk \u2014 <code>COPY<\/code> mere effektivt!<\/p>\n<pre><code class=\"sql\">INSERT INTO invoice_import\nVALUES\n  ('0123456789', 'Vasya', 'A-01', '2020-03-16', 1000.00)\n, ('9876543210', 'Petya', 'A-02', '2020-03-16', 666.00)\n, ('0123456789', 'Vasya', 'B-03', '2020-03-16', 9999.00);<\/code><\/pre>\n<p>\nF\u00f8rst udpeger vi de \"skiver\", som vores \"fakta\" henviser til. I vores tilf\u00e6lde henviser fakturaer til kunder:<\/p>\n<pre><code class=\"sql\">CREATE TEMPORARY TABLE client_import AS\nSELECT DISTINCT ON(client_inn)\n-- kan blot bruge SELECT DISTINCT, hvis dataene er kendt for ikke at v\u00e6re modstridende\n  client_inn inn\n, client_name \"name\"\nFROM\n  invoice_import;<\/code><\/pre>\n<p>\nFor korrekt at knytte regninger til kunde-IDs, skal vi f\u00f8rst finde eller generere disse identifikatorer. Vi tilf\u00f8jer felter til dem:<\/p>\n<pre><code class=\"sql\">ALTER TABLE invoice_import TILF\u00d8J KOLONNE client_id integer;\nALTER TABLE client_import TILF\u00d8J KOLONNE client_id integer;<\/code><\/pre>\n<p>\nVi vil bruge den ovenfor beskrevne metode til synkronisering af tabellerne med en lille tilpasning \u2014 vi opdaterer og sletter ikke noget i m\u00e5ltabellen, da importen af kunder er \"append-only\":<\/p>\n<pre><code class=\"sql\">-- s\u00e6tter ID'er for allerede eksisterende poster i importtabel\nOPDATER\n  client_import T\nSET\n  client_id = D.client_id\nFRA\n  client D\nHVOR\n  T.inn = D.inn; -- unik n\u00f8gle\n\n-- inds\u00e6tter manglende poster og s\u00e6tter deres ID'er\nMED ins AS (\n  INDS\u00c6T I client(\n    inn\n  , name\n  )\n  V\u00c6LG\n    inn\n  , name\n  FRA\n    client_import\n  HVOR\n    client_id ER NULL -- hvis ID ikke blev sat\n  RETURNING *\n)\nOPDATER\n  client_import T\nSET\n  client_id = D.client_id\nFRA\n  ins D\nHVOR\n  T.inn = D.inn; -- unik n\u00f8gle\n\n-- s\u00e6tter klient-ID'er p\u00e5 regningsposterne\nOPDATER\n  invoice_import T\nSET\n  client_id = D.client_id\nFRA\n  client_import D\nHVOR\n  T.client_inn = D.inn; -- anvendelig n\u00f8gle\n<\/code><\/pre>\n<p>\nFaktisk, det er alt \u2014 i <code>invoice_import<\/code> har vi nu udfyldt forbindelsesfeltet <code>client_id<\/code>, som vi vil bruge til at inds\u00e6tte regningen.<br \/>\n<br \/>Kilde: <a content=\"nofollow\" rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/tensor\/blog\/492464\/\">habr.com<\/a> <\/p>","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"excerpt":{"rendered":"<p>\u041f\u0440\u0438 \u0441\u043b\u043e\u0436\u043d\u043e\u0439 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0435 \u0431\u043e\u043b\u044c\u0448\u0438\u0445 \u043d\u0430\u0431\u043e\u0440\u043e\u0432 \u0434\u0430\u043d\u043d\u044b\u0445 (\u0440\u0430\u0437\u043d\u044b\u0435 ETL-\u043f\u0440\u043e\u0446\u0435\u0441\u0441\u044b: \u0438\u043c\u043f\u043e\u0440\u0442\u044b, \u043a\u043e\u043d\u0432\u0435\u0440\u0442\u0430\u0446\u0438\u0438 \u0438 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0430\u0446\u0438\u0438 \u0441 \u0432\u043d\u0435\u0448\u043d\u0438\u043c \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u043e\u043c) \u0447\u0430\u0441\u0442\u043e \u0432\u043e\u0437\u043d\u0438\u043a\u0430\u0435\u0442 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u044c \u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e \u00ab\u0437\u0430\u043f\u043e\u043c\u043d\u0438\u0442\u044c\u00bb, \u0438 \u0441\u0440\u0430\u0437\u0443 \u0431\u044b\u0441\u0442\u0440\u043e \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0447\u0442\u043e-\u0442\u043e \u043e\u0431\u044a\u0435\u043c\u043d\u043e\u0435. \u0422\u0438\u043f\u043e\u0432\u0430\u044f \u0437\u0430\u0434\u0430\u0447\u0430 \u043f\u043e\u0434\u043e\u0431\u043d\u043e\u0433\u043e \u0440\u043e\u0434\u0430 \u0437\u0432\u0443\u0447\u0438\u0442 \u043e\u0431\u044b\u0447\u043d\u043e \u043f\u0440\u0438\u043c\u0435\u0440\u043d\u043e \u0442\u0430\u043a: \u00ab\u0412\u043e\u0442 \u0442\u0443\u0442 \u0431\u0443\u0445\u0433\u0430\u043b\u0442\u0435\u0440\u0438\u044f \u0432\u044b\u0433\u0440\u0443\u0437\u0438\u043b\u0430 \u0438\u0437 \u043a\u043b\u0438\u0435\u043d\u0442-\u0431\u0430\u043d\u043a\u0430 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0435 \u043f\u043e\u0441\u0442\u0443\u043f\u0438\u0432\u0448\u0438\u0435 \u043e\u043f\u043b\u0430\u0442\u044b, \u043d\u0430\u0434\u043e \u0438\u0445 \u0431\u044b\u0441\u0442\u0440\u0435\u043d\u044c\u043a\u043e \u0432\u043a\u0430\u0447\u0430\u0442\u044c \u043d\u0430 \u0441\u0430\u0439\u0442 \u0438 \u043f\u0440\u0438\u0432\u044f\u0437\u0430\u0442\u044c \u043a \u0441\u0447\u0435\u0442\u0430\u043c\u00bb \u041d\u043e \u043a\u043e\u0433\u0434\u0430 [&hellip;]<\/p>\n","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"author":1,"featured_media":74954,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[688],"tags":[],"class_list":["post-74953","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-administrirovanie"],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 5.0.3 - aioseo.com -->\n\t<meta name=\"description\" content=\"\u041f\u0440\u0438 \u0441\u043b\u043e\u0436\u043d\u043e\u0439 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0435 \u0431\u043e\u043b\u044c\u0448\u0438\u0445 \u043d\u0430\u0431\u043e\u0440\u043e\u0432 \u0434\u0430\u043d\u043d\u044b\u0445 (\u0440\u0430\u0437\u043d\u044b\u0435 ETL-\u043f\u0440\u043e\u0446\u0435\u0441\u0441\u044b: \u0438\u043c\u043f\u043e\u0440\u0442\u044b, \u043a\u043e\u043d\u0432\u0435\u0440\u0442\u0430\u0446\u0438\u0438 \u0438 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0430\u0446\u0438\u0438 \u0441 \u0432\u043d\u0435\u0448\u043d\u0438\u043c \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u043e\u043c) \u0447\u0430\u0441\u0442\u043e.\" \/>\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"author\" content=\"Yuri Gagarin\"\/>\n\t<link rel=\"canonical\" href=\"https:\/\/prohoster.info\/da\/blog\/administrirovanie\/dba-gramotno-organizovyvaem-sinhronizaczii-i-importy\" \/>\n\t\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.3\" \/>\n\t\t<meta property=\"og:locale\" content=\"da_DK\" \/>\n\t\t<meta property=\"og:site_name\" content=\"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b\" \/>\n\t\t<meta property=\"og:type\" content=\"article\" \/>\n\t\t<meta property=\"og:title\" content=\"\ud83e\udd47DBA: \u0433\u0440\u0430\u043c\u043e\u0442\u043d\u043e \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u043e\u0432\u044b\u0432\u0430\u0435\u043c \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0430\u0446\u0438\u0438 \u0438 \u0438\u043c\u043f\u043e\u0440\u0442\u044b | ProHoster\" \/>\n\t\t<meta property=\"og:description\" content=\"\u041f\u0440\u0438 \u0441\u043b\u043e\u0436\u043d\u043e\u0439 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0435 \u0431\u043e\u043b\u044c\u0448\u0438\u0445 \u043d\u0430\u0431\u043e\u0440\u043e\u0432 \u0434\u0430\u043d\u043d\u044b\u0445 (\u0440\u0430\u0437\u043d\u044b\u0435 ETL-\u043f\u0440\u043e\u0446\u0435\u0441\u0441\u044b: \u0438\u043c\u043f\u043e\u0440\u0442\u044b, \u043a\u043e\u043d\u0432\u0435\u0440\u0442\u0430\u0446\u0438\u0438 \u0438 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0430\u0446\u0438\u0438 \u0441 \u0432\u043d\u0435\u0448\u043d\u0438\u043c \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u043e\u043c) \u0447\u0430\u0441\u0442\u043e.\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/prohoster.info\/da\/blog\/administrirovanie\/dba-gramotno-organizovyvaem-sinhronizaczii-i-importy\" \/>\n\t\t<meta property=\"og:image\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:secure_url\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:width\" content=\"350\" \/>\n\t\t<meta property=\"og:image:height\" content=\"350\" \/>\n\t\t<meta property=\"article:published_time\" content=\"2020-03-22T05:42:22+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2020-03-22T05:42:22+00:00\" \/>\n\t\t<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<meta property=\"article:author\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<!-- All in One SEO -->\n\n","aioseo_head_json":{"title":"\ud83e\udd47DBA: Organiserer synkroniseringer og importer effektivt | ProHoster","description":"Ved kompleks behandling af store datas\u00e6t (forskellige ETL-processer: importer, konverteringer og synkroniseringer med eksterne kilder) ofte.","canonical_url":"https:\/\/prohoster.info\/da\/blog\/administrirovanie\/dba-gramotno-organizovyvaem-sinhronizaczii-i-importy","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"da_DK","og:site_name":"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b","og:type":"article","og:title":"\ud83e\udd47DBA: \u0433\u0440\u0430\u043c\u043e\u0442\u043d\u043e \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u043e\u0432\u044b\u0432\u0430\u0435\u043c \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0430\u0446\u0438\u0438 \u0438 \u0438\u043c\u043f\u043e\u0440\u0442\u044b | ProHoster","og:description":"\u041f\u0440\u0438 \u0441\u043b\u043e\u0436\u043d\u043e\u0439 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0435 \u0431\u043e\u043b\u044c\u0448\u0438\u0445 \u043d\u0430\u0431\u043e\u0440\u043e\u0432 \u0434\u0430\u043d\u043d\u044b\u0445 (\u0440\u0430\u0437\u043d\u044b\u0435 ETL-\u043f\u0440\u043e\u0446\u0435\u0441\u0441\u044b: \u0438\u043c\u043f\u043e\u0440\u0442\u044b, \u043a\u043e\u043d\u0432\u0435\u0440\u0442\u0430\u0446\u0438\u0438 \u0438 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0430\u0446\u0438\u0438 \u0441 \u0432\u043d\u0435\u0448\u043d\u0438\u043c \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u043e\u043c) \u0447\u0430\u0441\u0442\u043e.","og:url":"https:\/\/prohoster.info\/da\/blog\/administrirovanie\/dba-gramotno-organizovyvaem-sinhronizaczii-i-importy","og:image":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:secure_url":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:width":350,"og:image:height":350,"article:published_time":"2020-03-22T05:42:22+00:00","article:modified_time":"2020-03-22T05:42:22+00:00","article:publisher":"https:\/\/www.facebook.com\/prohoster","article:author":"https:\/\/www.facebook.com\/prohoster"},"aioseo_meta_data":{"post_id":"74953","title":null,"description":null,"keywords":null,"keyphrases":null,"primary_term":null,"canonical_url":null,"og_title":null,"og_description":null,"og_object_type":"default","og_image_type":"default","og_image_url":null,"og_image_width":null,"og_image_height":null,"og_image_custom_url":null,"og_image_custom_fields":null,"og_video":null,"og_custom_url":null,"og_article_section":null,"og_article_tags":null,"twitter_use_og":false,"twitter_card":"default","twitter_image_type":"default","twitter_image_url":null,"twitter_image_custom_url":null,"twitter_image_custom_fields":null,"twitter_title":null,"twitter_description":null,"schema":{"blockGraphs":[],"customGraphs":[],"default":{"data":{"Article":[],"Course":[],"Dataset":[],"FAQPage":[],"Movie":[],"Person":[],"Product":[],"ProductReview":[],"Car":[],"Recipe":[],"Service":[],"SoftwareApplication":[],"WebPage":[]},"graphName":"","isEnabled":true},"graphs":[]},"schema_type":null,"schema_type_options":null,"pillar_content":false,"robots_default":true,"robots_noindex":false,"robots_noarchive":false,"robots_nosnippet":false,"robots_nofollow":false,"robots_noimageindex":false,"robots_noodp":false,"robots_notranslate":false,"robots_max_snippet":null,"robots_max_videopreview":null,"robots_max_imagepreview":"large","priority":null,"frequency":null,"local_seo":null,"seo_analyzer_scan_date":null,"breadcrumb_settings":null,"limit_modified_date":false,"reviewed_by":null,"ai":null,"created":"2021-02-28 18:04:26","updated":"2022-09-30 13:25:20","focus_keyword":null,"additional_keywords":null,"truseo_locale":null},"gt_translate_keys":[{"key":"link","format":"url"}],"_links":{"self":[{"href":"https:\/\/prohoster.info\/da\/wp-json\/wp\/v2\/posts\/74953","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/da\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/da\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/da\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/da\/wp-json\/wp\/v2\/comments?post=74953"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/da\/wp-json\/wp\/v2\/posts\/74953\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/da\/wp-json\/wp\/v2\/media\/74954"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/da\/wp-json\/wp\/v2\/media?parent=74953"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/da\/wp-json\/wp\/v2\/categories?post=74953"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/da\/wp-json\/wp\/v2\/tags?post=74953"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}