{"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\/et\/blog\/administrirovanie\/dba-gramotno-organizovyvaem-sinhronizaczii-i-importy","title":{"rendered":"DBA: korraldame s\u00fcnkroniseerimisi ja importimist oskuslikult","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>Kui keerukalt t\u00f6\u00f6tleda suuri andmekogusid (erinevad <noindex><a rel=\"nofollow\" href=\"https:\/\/ru.wikipedia.org\/wiki\/ETL\">ETL-protsessid<\/a><\/noindex>: impordid, konverteerimised ja s\u00fcnkroonimised v\u00e4lise allikaga) tekib sageli vajadus <b>ajaliselt \"m\u00e4luda\" ja korraga kiiresti t\u00f6\u00f6delda<\/b> m\u00f5nda suurust.<\/p>\n<p>Selline t\u00fc\u00fcpiline \u00fclesanne k\u00f5lab tavaliselt nii: <i>\"Siin on <noindex><a rel=\"nofollow\" href=\"https:\/\/sbis.ru\/accounting\">raamatupidamine eksportinud kliendipangast<\/a><\/noindex> viimased laekunud maksed, tuleb need kiiresti veebilehele laadida ja siduda kontodega\"<\/i><\/p>\n<p>Kuid kui selle \"millegi\" maht hakkab m\u00f5\u00f5tma sadade megabaidiga ja teenus peab samal ajal t\u00f6\u00f6tama andmebaasiga re\u017eiimis 24&#215;7, siis tekib palju k\u00f5rvalm\u00f5jusid, mis rikuvad teie elu.<br \/>\n<img decoding=\"async\" alt=\"DBA: korraldame s\u00fcnkroniseerimisi ja importimist oskuslikult\" src=\"\/wp-content\/uploads\/2020\/03\/f74afb2cd6f5f8de26a0932166933c95.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nNende vastu v\u00f5itlemiseks PostgreSQL-is (ja mitte ainult seal) saab kasutada teatavaid optsioone optimeerimiseks, mis v\u00f5imaldavad k\u00f5ike t\u00f6\u00f6tleda kiiremini ja v\u00e4iksema ressursikulu.<br \/>\n<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<h2>1. Kuhu laadida?<\/h2>\n<p>\nEsmalt laske meil m\u00f5ista, kuhu me saame laadida andmed, mida soovime \"t\u00f6\u00f6deldaks\".<\/p>\n<h3>1.1. Ajutised tabelid (TEMPORARY TABLE)<\/h3>\n<p>\nP\u00f5him\u00f5tteliselt on ajutised tabelid PostgreSQL-is samasugused tabelid nagu k\u00f5ik teised. Seet\u00f5ttu ei ole vale uskumine, et <i><b>\"seal hoitakse k\u00f5ike ainult m\u00e4lus ja see v\u00f5ib otsa saada\"<\/b><\/i>. Kuid on ka mitmeid olulisi erinevusi.<\/p>\n<h4>Igal DB \u00fchendusel on oma \"nimetamise ruum\"<\/h4>\n<p>\nKui kaks \u00fchendust \u00fcritavad samal ajal k\u00e4ivitada <code>CREATE TABLE x<\/code>, siis keegi saab kindlasti <b>andmebaasi objektide ainulaadsuse viga.<\/b> Aga kui m\u00f5lemad \u00fcritavad k\u00e4ivitada<\/p>\n<p>, siis m\u00f5lemad teevad seda normaalselt ja iga\u00fcks saab <code>CREATE <b>AJUTINE<\/b> TABEL x<\/code>oma eksemplari <b>tabelist. Ja neil ei ole \u00fcksteisega midagi \u00fchist.<\/b> \"Eneseh\u00e4vitamine\" sulgemisel<\/p>\n<h4>\u00dchenduse sulgemisel kustutatakse k\u00f5ik ajutised tabelid automaatselt, seega pole \"k\u00e4si\" teostada<\/h4>\n<p>\nDROP TABLE x <code>mingit m\u00f5tet, v\u00e4lja arvatud...<\/code> Kui t\u00f6\u00f6tate<\/p>\n<p>pgbouncer'i tehingure\u017eiimis <b>, siis andmebaas arvab endiselt, et see \u00fchendus on endiselt aktiivne, ja selles eksisteerib see ajutine tabel endiselt.<\/b>Seega toob p\u00fc\u00fce seda uuesti luua, juba teise \u00fchenduse kaudu pgbouncerile, kaasa vea. Kuid seda saab v\u00e4ltida, kasutades<\/p>\n<p>Kuid parem on seda ikkagi mitte teha, sest siis v\u00f5ite \"ootamatult\" avastada seal \"eelmistest omanikest\" j\u00e4\u00e4ne andmed. Selle asemel on palju parem lugeda juhendit ja n\u00e4ha, et tabeli loomisel on v\u00f5imalus juurde kirjutada <code>LOO TEMPORARY TABLE <b>KUI EI OLE<\/b> x<\/code>.<\/p>\n<p>T\u00f5epoolest, parem on seda mitte teha, kuna siis v\u00f5ib \"\u00fcht\u00e4kki\" avastada seal endiselt olemasolevaid andmeid \"eelmiselt omanikult\". Selle asemel on palju parem lugeda manuaali ja n\u00e4ha, et tabeli loomisel on v\u00f5imalus t\u00e4iendavaid andmeid sisestada. <code>REKISTERIMISES <b>DROP<\/b><\/code> \u2014 see, when the transaction is completed, the table will be automatically deleted.<\/p>\n<h4>Non-replication<\/h4>\n<p>\nDue to being assigned to a specific connection only, temporary tables are not replicated. However, <b>this eliminates the need for double data writing<\/b> in heap + WAL, hence INSERT\/UPDATE\/DELETE into it is significantly faster.<\/p>\n<p>But since a temporary table is still \"almost a regular\" table, it can't be created on a replica either. At least, not yet, although the corresponding patch has been around for a long time.<\/p>\n<h3>1.2. Unlogged tables (UNLOGGED TABLE)<\/h3>\n<p>\nBut what if, for example, you have some bulky ETL process that can't be implemented within a single transaction, and you indeed <b>, siis andmebaas arvab endiselt, et see \u00fchendus on endiselt aktiivne, ja selles eksisteerib see ajutine tabel endiselt.<\/b>?..<\/p>\n<p>Or the data stream is so large that <b>the bandwidth of a single connection<\/b> to the DB (read, one process on the CPU)?..<\/p>\n<p>Or part of the operations go <b>asynchronously<\/b> in different connections?..<\/p>\n<p>In this case, there is only one option \u2014 <b>temporarily create a non-temporary table<\/b>. A pun, indeed. That is:<\/p>\n<ul>\n<li>created \"my\" tables with maximally-random names to avoid any overlap<\/li>\n<li><b>Extract<\/b>: loaded data from an external source into them<\/li>\n<li><b>Transform<\/b>: transformed, filled key linking fields<\/li>\n<li><b>Laadi<\/b>: poured the prepared data into target tables<\/li>\n<li>deleted \"my\" tables<\/li>\n<\/ul>\n<p>\nAnd now \u2014 a teaspoon of tar. Essentially, <b>all writing in PostgreSQL happens twice<\/b> \u2014 <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/postgrespro\/blog\/461523\/\">first in the WAL<\/a><\/noindex>, then in the bodies of tables\/indices. All this is done to support ACID and correct data visibility between <code>COMMIT<\/code>&#8216;kehtestatud ja <code>ROLLBACK<\/code>&#8216;kehtestatud tehingute.<\/p>\n<p>But we don\u2019t need this! Our entire process <b>must either have completely succeeded or not at all.<\/b>It doesn\u2019t matter how many intermediate transactions there will be \u2014 we are not interested in \"continuing the process from the middle\", especially when it\u2019s not clear where it was.<\/p>\n<p>For this, PostgreSQL developers introduced a feature as early as version 9.1 called <noindex><a rel=\"nofollow\" href=\"https:\/\/postgrespro.ru\/docs\/postgresql\/12\/sql-createtable#SQL-CREATETABLE-UNLOGGED\">unlogged (UNLOGGED) tables.<\/a><\/noindex>:<\/p>\n<blockquote><p>With this indication, the table is created as unlogged. Data written to unlogged tables does not go through the write-ahead log (see Chapter 29), resulting in such tables <b>working much faster than regular ones.<\/b>However, they are not crash-safe; in case of a crash or unexpected server shutdown, the unlogged table <b>is automatically truncated.<\/b>Moreover, the content of an unlogged table <b>is not replicated.<\/b> juhitud serverite jaoks. Iga indeksi, mis luuakse logimata tabeli jaoks, muutub automaatselt logimata.<\/p><\/blockquote>\n<p>L\u00fchidalt, <b>on see oluliselt kiirem<\/b>, kuid kui andmebaasi server \"kukkub\", on see ebameeldiv. Kuid kui sageli see juhtub ja kas teie ETL-protsess suudab seda \u00f5igesti \"keskelt\" taastada p\u00e4rast andmebaasi \"taaselustamist\"?..<\/p>\n<p>Kui ei, ja eespool mainitud olukord sarnaneb teie omaga \u2014 kasutage <code>UNLOGGED<\/code>, kuid kunagi <b>\u00e4rge l\u00fclitage seda atribuuti sisse reaalsetele tabelitele<\/b>, mille andmed on teile olulised.<\/p>\n<h3>1.3. ON COMMIT { KUSTUTA RIDA | KUSTUTA }<\/h3>\n<p>\nSee konstruktsioon v\u00f5imaldab tabeli loomisel m\u00e4\u00e4rata automaatse k\u00e4itumise tehingu l\u00f5petamisel.<\/p>\n<p>K\u00fcsimus <code>REKISTERIMISES <b>DROP<\/b><\/code> nagu ma juba eespool mainisin, genereerib see <code>DROP TABLE<\/code>, kuid siin on <code>REKISTERIMISES <b>KUSTUTA VREAD<\/b><\/code> olukord huvitavam \u2014 siin genereeritakse <code>TRUNCATE TABLE<\/code>.<\/p>\n<p>Kuna ajutise tabeli metaandmete salvestus infrastruktuur on sama, mis tavalistel, siis <b>ajutiste tabelite pidev loomine-kustutamine toob kaasa s\u00fcsteemsete tabelite tugeva \"paisumise\"<\/b> pg_class, pg_attribute, pg_attrdef, pg_depend,\u2026<\/p>\n<p>Kujutage n\u00fc\u00fcd ette, et teil on t\u00f6\u00f6taja, kes on otse\u00fchenduses andmebaasiga, ja avab igal sekundil uue tehingu, loob, t\u00e4idab, t\u00f6\u00f6tleb ja kustutab ajutise tabeli\u2026 S\u00fcsteemsetesse tabelitesse koguneb liiga palju pr\u00fcgi, mis toob kaasa tarbetud lagged igas operatsioonis.<\/p>\n<p>\u00dcldiselt, \u00e4rge tehke nii! Sel juhul on palju t\u00f5husam <code>LOO AJUTINE TABEL x ... ON COMMIT KUSTUTA RIDA<\/code> toimetada tehingu ts\u00fcklist v\u00e4lja \u2014 siis on iga uue tehingu alguseks tabelid juba <b>olemas<\/b> (s\u00e4\u00e4stame kutset <code>CREATE<\/code>), kuid <b>olevad t\u00fchjad<\/b>, t\u00e4nu <code>TRUNCATE<\/code> (me s\u00e4\u00e4stsime ka selle kutse) eelmise tehingu l\u00f5petamisel.<\/p>\n<h3>1.4. NAGU... KAAKS...<\/h3>\n<p>\nMainisin alguses, et \u00fcks t\u00fc\u00fcpiline ajutiste tabelite kasutusjuht on erinevad impordid \u2014 ja arendaja kopeerib v\u00e4sinult sihttabeli v\u00e4ljade nimekirja oma ajutise kuulutusse\u2026<\/p>\n<p>Kuid laiskus on progressi mootor! Seet\u00f5ttu <b>uue tabeli loomine \u201emudeleid pidi\u201c<\/b> on palju lihtsam:<\/p>\n<pre><code class=\"sql\">LOO AJUTINE TABEL import_table(\n  LIKE target_table\n);<\/code><\/pre>\n<p>\nKuna sellesse tabelisse on v\u00f5imalik hiljem genereerida palju andmeid, siis otsingud selles muutuvad \u00e4\u00e4rmiselt aeglaseks. Kuid selle vastu on traditsiooniline lahendus \u2014 indeksid! Ja jah, <b>ajutistel tabelitel v\u00f5ivad samuti olla indeksid<\/b>.<\/p>\n<p>Kuna sageli j\u00e4\u00e4vad vajalikud indeksid kokku sihtp\u00e4randi tabeli indeksitega, v\u00f5ib lihtsalt kirjutada <code>NAGU target_table <b>KAASAM N\u00c4IDIKUD<\/b><\/code>.<\/p>\n<p>Kui vajate veel ka <code>DEFAULT<\/code>-v\u00e4\u00e4rtusi (n\u00e4iteks primaarsete v\u00f5tmete v\u00e4\u00e4rtuste t\u00e4itmiseks), saab kasutada <code>NAGU target_table <b>KAASIED S\u00c4HRIT<\/b><\/code>. Noh, v\u00f5i lihtsalt \u2014 <code>NAGU target_table <b>KAAS K\u00d5IK<\/b><\/code> \u2014 kopeerib vaikeseaded, indeksid, piirangud,\u2026<\/p>\n<p>Aga siin tuleb juba aru saada, et kui olete loonud <b>impordi-tabeli kohe indeksitega, siis andmete \u00fcleslaadimine kestab kauem<\/b>, kui kui k\u00f5igepealt kogu sisu \u00fcles laadida ja alles seej\u00e4rel indeksid rakendada \u2014 vaadake n\u00e4iteks, kuidas seda teeb <noindex><a rel=\"nofollow\" href=\"https:\/\/postgrespro.ru\/docs\/postgresql\/12\/app-pgdump\">pg_dump<\/a><\/noindex>.<\/p>\n<p>\u00dches\u00f5naga, <noindex><a rel=\"nofollow\" href=\"https:\/\/postgrespro.ru\/docs\/postgresql\/12\/sql-createtable\">RTFM<\/a><\/noindex>!<\/p>\n<h2>2. Kuidas kirjutada?<\/h2>\n<p>\n\u00dctlen lihtsalt \u2014 kasutage <code><noindex><a rel=\"nofollow\" href=\"https:\/\/postgrespro.ru\/docs\/postgresql\/12\/sql-copy\">COPY<\/a><\/noindex><\/code>-voogu asemel \u201epartiid\u201c <code>INSERT<\/code>, <noindex><a rel=\"nofollow\" href=\"https:\/\/www.citusdata.com\/blog\/2017\/11\/08\/faster-bulk-loading-in-postgresql-with-copy\/\">kiirus mitu korda<\/a><\/noindex>. V\u00f5ite isegi otse eelnevalt vormistatud failist.<\/p>\n<h2>3. Kuidas t\u00f6\u00f6delda?<\/h2>\n<p>\nOletame, et meie sisend n\u00e4eb v\u00e4lja umbes nii:<\/p>\n<ul>\n<li>teil on andmebaasis tabel kliendiandmetega <b>1M kirjet<\/b><\/li>\n<li>iga p\u00e4ev saadab klient teile uue <b>t\u00e4ieliku \u201epildi\u201c<\/b><\/li>\n<li>kogenud inimesed teavad, et iga kord <b>muutub vaid 10K kirjet<\/b><\/li>\n<\/ul>\n<p>\nKlassikaline n\u00e4ide taolisest olukorrast on <noindex><a rel=\"nofollow\" href=\"https:\/\/www.gnivc.ru\/technical_support\/classifiers_reference\/kladr\/\">KLDAR andmebaas<\/a><\/noindex> \u2014 aadresse on palju, kuid igan\u00e4dalasel andmevoolul on muutusi (asulate nimede muutmine, t\u00e4navate \u00fchendamine, uute majade ilmumine) v\u00e4he isegi kogu riigi ulatuses.<\/p>\n<h3>3.1. T\u00e4ieliku s\u00fcnkroonimise algoritm<\/h3>\n<p>\nLihtsuse huvides oletame, et andmete struktureerimist pole vaja \u2014 lihtsalt viige tabel soovitud vormi, st:<\/p>\n<ul>\n<li><b>kustutada<\/b> k\u00f5ik, mida enam ei ole<\/li>\n<li><b>uuendada<\/b> k\u00f5ik, mis oli, ja tuleb uuendada<\/li>\n<li><b>sisestada<\/b> k\u00f5ik, mida polnud veel<\/li>\n<\/ul>\n<p>\nMiks tuleb neid toiminguid t\u00e4pselt niimoodi teha? Sest just nii kasvab tabeli suurus minimaalsetena (<noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/tensor\/blog\/491366\/\">peab meeles MVCC!<\/a><\/noindex>).<\/p>\n<h4>DELETE FROM dst<\/h4>\n<p>\nEi, loomulikult saab hakkama ka vaid kahe toiminguga:<\/p>\n<ul>\n<li><b>kustutada<\/b> (<code>DELETE<\/code>) tegelikult k\u00f5ik<\/li>\n<li><b>sisestada<\/b> k\u00f5ik uuest pildist<\/li>\n<\/ul>\n<p>\nAga samas, MVCC t\u00f5ttu, <b>tabeli suurus kahekordistub<\/b>! Saada +1M rekordite pilte tabelisse 10K uuendamise t\u00f5ttu \u2014 pole just ideaalne \u00fcleliigsus\u2026<\/p>\n<h4>TRUNCATE dst<\/h4>\n<p>\nKogenum arendaja teab, et kogu tabelit on v\u00f5imalik rahaliselt odavalt puhastada:<\/p>\n<ul>\n<li><b>puhta<\/b> (<code>TRUNCATE<\/code>) kogu tabel<\/li>\n<li><b>sisestada<\/b> k\u00f5ik uuest pildist<\/li>\n<\/ul>\n<p>\nMeetod on t\u00f5hus, <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/tensor\/blog\/481866\/\">m\u00f5nikord t\u00e4iesti rakendatav<\/a><\/noindex>, kuid see toob kaasa probleemi\u2026 Me joonistame 1M kirjet \u00fcle v\u00e4ga kaua, seega ei saa me endale lubada, et tabel oleks kogu selle aja t\u00fchjaks j\u00e4etud (nagu juhtuks ilma \u00fcheainsa tehingu katmiseneta).<\/p>\n<p>Seega:<\/p>\n<ul>\n<li>me alustame <b>pikk tehing<\/b><\/li>\n<li><code>TRUNCATE<\/code> kehtestab <b>AccessExclusive<\/b>-lukku<\/li>\n<li>teeme inserte pikka aega, kuid k\u00f5ik teised selle ajal <b>ei saa isegi <code>SELECT<\/code><\/b><\/li>\n<\/ul>\n<p>\nMidagi ei tundu h\u00e4sti tulevat\u2026<\/p>\n<h4>ALTER TABLE... NIME MUUTMINE... \/ KUSTUTA TABEL...<\/h4>\n<p>\nV\u00f5imalusena v\u00f5iks k\u00f5ik \u00fcle kanda uude eraldi tabelisse ja seej\u00e4rel lihtsalt vana tabeli nime muuta. Paar ebameeldivat pisiasja:<\/p>\n<ul>\n<li>ka need <b>AccessExclusive<\/b>, kuigi oluliselt v\u00e4hem ajakulu<\/li>\n<li>kustuvad k\u00f5ik p\u00e4ringute plaanid\/statistika selle tabeli kohta, <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/tensor\/blog\/479656\/\">peab k\u00e4ivitama ANALYZE<\/a><\/noindex><\/li>\n<li><b>katkeb k\u00f5ik v\u00e4lised v\u00f5tmed<\/b> (FK) sellele tabelile<\/li>\n<\/ul>\n<p>\nOli WIP-patch Simon Riggsilt, mis pakkus v\u00e4lja <code>ALTER<\/code>-operatsiooni tabeli sisu asendamiseks failitasandil, m\u00f5jutamata statistikat ja FK-d, kuid ei kogunud kvoori.<\/p>\n<h4>DELETE, UPDATE, INSERT<\/h4>\n<p>\nNii et j\u00e4\u00e4me kolme operatsiooni mitteblokeeriva variandi juurde. Peaaegu kolme\u2026 Kuidas seda k\u00f5ige t\u00f5husamalt teha?<\/p>\n<pre><code class=\"sql\">-- teeme k\u00f5ik tehingu raames, et keegi ei n\u00e4eks \"vahepealseid\" olekuid\nBEGIN;\n\n-- loome ajutise tabeli imporditud andmetega\nCREATE TEMPORARY TABLE tmp(\n  LIKE dst INCLUDING INDEXES -- koopia koos indeksitega\n) ON COMMIT DROP; -- tehingu raames ei ole meil seda vaja\n\n-- kiiresti sisestame uue koopia l\u00e4bi COPY\nCOPY tmp FROM STDIN;\n-- ...\n-- .\n\n-- eemaldame puuduvad\nDELETE FROM\n  dst D\nUSING\n  dst X\nLEFT JOIN\n  tmp Y\n    USING(pk1, pk2) -- esmase v\u00f5tme v\u00e4ljad\nWHERE\n  (D.pk1, D.pk2) = (X.pk1, X.pk2) AND\n  Y IS NOT DISTINCT FROM NULL; -- \"anti-join\"\n\n-- uuendame j\u00e4\u00e4knud\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); -- pole m\u00f5tet uuendada vastavaid\n\n-- sisestame puuduvad\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. Impordi j\u00e4relprotsessimine<\/h3>\n<p>\nSamas KLADris tuleb k\u00f5ik muudetud kirjed t\u00e4iendavalt l\u00e4bi viia j\u00e4relprotsessimine \u2014 normaliseerida, eraldada m\u00e4rks\u00f5nad, viia sobivatesse struktuuridesse. Kuid kuidas teada \u2014 <b>mida t\u00e4pselt muudeti<\/b>, keerukust s\u00fcvenemata s\u00fcnkroniseerimise koodi, ideaaljuhul, mitte puudutades seda \u00fcldse?<\/p>\n<p>Kui kirjutamis\u00f5igus s\u00fcnkroniseerimise hetkel on ainult teie protsessil, siis v\u00f5ib kasutada k\u00e4ivitusklahvi, mis kogub k\u00f5ik meie muudatused:<\/p>\n<pre><code class=\"sql\">-- sihtlaud tabelid\nCREATE TABLE kladr(...);\nCREATE TABLE kladr_house(...);\n\n-- muutuste ajalooga tabelid\nCREATE TABLE kladr$log(\n  ro kladr, -- siin on vanade\/uute kirgede t\u00e4psed koopiad\n  rn kladr\n);\n\nCREATE TABLE kladr_house$log(\n  ro kladr_house,\n  rn kladr_house\n);\n\n-- \u00fcldine funktsioon muudatuste logimiseks\nCREATE OR REPLACE FUNCTION diff$log() RETURNS trigger AS $$\nDECLARE\n  dst varchar = TG_TABLE_NAME || '$log';\n  stmt text = '';\nBEGIN\n  -- kontrollime vajadust logimise j\u00e4rel uuendamisel\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  -- loome logikirje\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>\nN\u00fc\u00fcd saame enne s\u00fcnkroniseerimise algust k\u00e4ivitada (v\u00f5i aktiveerida 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>\nSeej\u00e4rel saame logitabelitest rahulikult v\u00e4lja t\u00f5mmata k\u00f5ik vajalikud muudatused ning saata need edasi t\u00e4iendavatele t\u00f6\u00f6tlejatele.<\/p>\n<h3>3.3. Seotud komplektide import<\/h3>\n<p>\n\u00dclal arutlesime juhtumeid, kus allika ja sihtkoha andmestruktuurid kattuvad. Kuid mis juhul, kui v\u00e4listest s\u00fcsteemidest saadud eksport on formaadilt erinev meie andmebaasi ladustamisstruktuurist?<\/p>\n<p>V\u00f5tame n\u00e4iteks klientide ja nende arvete hoidmise, klassikalise \u201e\u00fched-palat\u201c variandi:<\/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>\nKuid v\u00e4line allika eksport tuleb meile \u201ek\u00f5ik \u00fches\u201c kujul:<\/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>\nIlmselt v\u00f5ivad klientide andmed sellises variandis dubleeruda, kuid peamine kirje on \u201earve\u201c:<\/p>\n<pre><code class=\"plaintext\">0123456789;Vasily;A-01;2020-03-16;1000.00\n9876543210;Peter;A-02;2020-03-16;666.00\n0123456789;Vasily;B-03;2020-03-16;9999.00\n<\/code><\/pre>\n<p>\nMudeli jaoks sisestame lihtsalt oma testandmed, kuid peame meeles pidama \u2014 <code>COPY<\/code> efektiivsemalt!<\/p>\n<pre><code class=\"sql\">INSERT INTO invoice_import\nVALUES\n  ('0123456789', 'Vasily', 'A-01', '2020-03-16', 1000.00)\n, ('9876543210', 'Peter', 'A-02', '2020-03-16', 666.00)\n, ('0123456789', 'Vasily', 'B-03', '2020-03-16', 9999.00);<\/code><\/pre>\n<p>\nAlustame nende \u201ev\u00f5tmete\u201c esitlemisest, millele meie \u201efaktid\u201c viitavad. Meie puhul viitavad arved klientidele:<\/p>\n<pre><code class=\"sql\">CREATE TEMPORARY TABLE client_import AS\nSELECT DISTINCT ON(client_inn)\n-- v\u00f5ib kasutada lihtsalt SELECT DISTINCT, kui andmed on kindlasti vastuolulised\n  client_inn inn\n, client_name \"name\"\nFROM\n  invoice_import;<\/code><\/pre>\n<p>\nKuna soovime \u00f5igesti siduda arvete ID-d klientidega, peame esmalt need identifikaatorid v\u00e4lja selgitama v\u00f5i genereerima. Lisame nende jaoks v\u00e4ljad:<\/p>\n<pre><code class=\"sql\">ALTER TABLE invoice_import ADD COLUMN client_id integer;\nALTER TABLE client_import ADD COLUMN client_id integer;<\/code><\/pre>\n<p>\nKasutame \u00fclaltoodud viisi tabelite s\u00fcnkroniseerimiseks, kuid v\u00e4ikese muudatusega \u2014 me ei uuenda ega kustuta siht-tabletis, kuna klientide import on meil \"append-only\":<\/p>\n<pre><code class=\"sql\">-- m\u00e4\u00e4rame imporditabelis olemasolevate kirjete ID-d\nUPDATE\n  client_import T\nSET\n  client_id = D.client_id\nFROM\n  client D\nWHERE\n  T.inn = D.inn; -- unikaalne v\u00f5ti\n\n-- sisestame puuduvad kirjed ja m\u00e4\u00e4rame nende ID-d\nWITH ins AS (\n  INSERT INTO client(\n    inn\n  , name\n  )\n  SELECT\n    inn\n  , name\n  FROM\n    client_import\n  WHERE\n    client_id IS NULL -- kui ID ei ole m\u00e4\u00e4ratud\n  RETURNING *\n)\nUPDATE\n  client_import T\nSET\n  client_id = D.client_id\nFROM\n  ins D\nWHERE\n  T.inn = D.inn; -- unikaalne v\u00f5ti\n\n-- m\u00e4\u00e4rame klientide ID-d arvete kirjetes\nUPDATE\n  invoice_import T\nSET\n  client_id = D.client_id\nFROM\n  client_import D\nWHERE\n  T.client_inn = D.inn; -- rakenduslik v\u00f5ti\n<\/code><\/pre>\n<p>\nSisuliselt on k\u00f5ik \u2014 <code>invoice_import<\/code> n\u00fc\u00fcd on meil t\u00e4idetud seose v\u00e4li <code>client_id<\/code>, millega me ka arve sisestame.<br \/>\n<br \/>Allikas: <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.1.1 - 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\/et\/blog\/administrirovanie\/dba-gramotno-organizovyvaem-sinhronizaczii-i-importy\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.1.1\" \/>\n\t\t<meta property=\"og:locale\" content=\"et_EE\" \/>\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\/et\/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\udd47 DBA: korralik s\u00fcnkroniseerimine ja impordid | ProHoster","description":"Suure andmehulkade keerulise t\u00f6\u00f6tlemise korral (erinevad ETL-protsessid: import, konverteerimine ja s\u00fcnkroniseerimine v\u00e4lise allika kaudu) sageli.","canonical_url":"https:\/\/prohoster.info\/et\/blog\/administrirovanie\/dba-gramotno-organizovyvaem-sinhronizaczii-i-importy","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"et_EE","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\/et\/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\/et\/wp-json\/wp\/v2\/posts\/74953","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/et\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/et\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/et\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/et\/wp-json\/wp\/v2\/comments?post=74953"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/et\/wp-json\/wp\/v2\/posts\/74953\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/et\/wp-json\/wp\/v2\/media\/74954"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/et\/wp-json\/wp\/v2\/media?parent=74953"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/et\/wp-json\/wp\/v2\/categories?post=74953"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/et\/wp-json\/wp\/v2\/tags?post=74953"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}