{"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\/it\/blog\/administrirovanie\/dba-gramotno-organizovyvaem-sinhronizaczii-i-importy","title":{"rendered":"DBA: organizziamo in modo efficace le sincronizzazioni e gli import","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>Quando si tratta di elaborare grandi set di dati complessi (diversi <noindex><a rel=\"nofollow\" href=\"https:\/\/ru.wikipedia.org\/wiki\/ETL\">processi ETL<\/a><\/noindex>: importazioni, conversioni e sincronizzazioni con una fonte esterna) sorge spesso la necessit\u00e0 <b>di 'memorizzare' temporaneamente e trattare rapidamente<\/b> qualcosa di voluminoso.<\/p>\n<p>Un compito tipico di questo tipo \u00e8 di solito formulato pi\u00f9 o meno cos\u00ec: <i>\u00abEcco qui <noindex><a rel=\"nofollow\" href=\"https:\/\/sbis.ru\/accounting\">la contabilit\u00e0 ha estratto dal client banking<\/a><\/noindex> gli ultimi pagamenti ricevuti, bisogna caricarli rapidamente sul sito e associarli ai conti\u00bb<\/i><\/p>\n<p>Ma quando il volume di questa \"cosa\" inizia a misurarsi in centinaia di megabyte, e il servizio deve continuare a lavorare con il database in modalit\u00e0 24\/7, sorgono numerosi effetti collaterali che rovineranno la tua vita.<br \/>\n<img decoding=\"async\" alt=\"DBA: organizziamo in modo efficace le sincronizzazioni e gli import\" src=\"\/wp-content\/uploads\/2020\/03\/f74afb2cd6f5f8de26a0932166933c95.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nPer affrontarli in PostgreSQL (e non solo in esso), \u00e8 possibile utilizzare alcune funzionalit\u00e0 di ottimizzazione che consentono di elaborare tutto pi\u00f9 rapidamente e con un minore consumo di risorse.<br \/>\n<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<h2>1. Dove caricare?<\/h2>\n<p>\nPrima di tutto, definiamo dove possiamo caricare i dati che vogliamo 'processare'.<\/p>\n<h3>1.1. Tabelle temporanee (TEMPORARY TABLE)<\/h3>\n<p>\nIn linea di principio, per PostgreSQL le tabelle temporanee sono tabelle proprio come tutte le altre. Quindi le superstizioni come <i><b>\u00abl\u00ec tutto \u00e8 memorizzato solo in memoria, e questa potrebbe finire\u00bb<\/b><\/i>non sono corrette. Ma ci sono anche diverse differenze sostanziali.<\/p>\n<h4>Uno 'spazio dei nomi' per ogni connessione al DB<\/h4>\n<p>\nSe due connessioni tentano di eseguire contemporaneamente <code>CREATE TABLE x<\/code>, allora qualcuna ricever\u00e0 sicuramente <b>un errore di non unicit\u00e0<\/b> degli oggetti DB.<\/p>\n<p>Ma se entrambi tentano di eseguire <code>CREATE <b>TEMPORANEO<\/b> TABELLA x<\/code>, allora entrambi lo faranno correttamente e ciascuno ricever\u00e0 <b>la propria istanza<\/b> della tabella. E non ci sar\u00e0 nulla in comune tra loro.<\/p>\n<h4>\u00abAutoannullamento\u00bb alla disconnessione<\/h4>\n<p>\nQuando la connessione si chiude, tutte le tabelle temporanee vengono automaticamente rimosse, quindi eseguire manualmente <code>DROP TABLE x<\/code> non ha alcun senso, tranne...<\/p>\n<p>Se si lavora tramite <b>pgbouncer in modalit\u00e0 transazione<\/b>, il database continuer\u00e0 a ritenere che questa connessione sia ancora attiva, e in essa questa tabella temporanea esister\u00e0 ancora.<\/p>\n<p>Quindi, il tentativo di crearla di nuovo, gi\u00e0 da un'altra connessione a pgbouncer, porter\u00e0 a un errore. Ma questo si pu\u00f2 aggirare, utilizzando <code>CREARE UNA TAVOLO TEMPORANEA <b>SE NON ESISTE<\/b> x<\/code>.<\/p>\n<p>\u00c8 vero, sarebbe meglio non farlo, perch\u00e9 poi si potrebbe \u00abscoprire all'improvviso\u00bb l\u00ec dentro i dati rimasti dal \u00abproprietario precedente\u00bb. \u00c8 molto meglio leggere il manuale e vedere che nella creazione della tabella c'\u00e8 la possibilit\u00e0 di aggiungere <code>ALLA COMMIT <b>DROP<\/b><\/code> \u2014 cio\u00e8 al completamento della transazione, la tabella verr\u00e0 eliminata automaticamente.<\/p>\n<h4>Non replicazione<\/h4>\n<p>\nA causa della sua appartenenza solo a una certa connessione, le tabelle temporanee non vengono replicate. Tuttavia, <b>questo elimina la necessit\u00e0 di una doppia registrazione dei dati<\/b> in heap + WAL, quindi INSERT\/UPDATE\/DELETE in essa \u00e8 notevolmente pi\u00f9 veloce.<\/p>\n<p>Ma poich\u00e9 una tabella temporanea \u00e8 comunque una tabella \u00abquasi normale\u00bb, non \u00e8 possibile crearla nemmeno sulla replica. Almeno, per ora, sebbene la patch corrispondente circoli da molto tempo.<\/p>\n<h3>1.2. Tabelle non registrate (UNLOGGED TABLE)<\/h3>\n<p>\nMa cosa fare, ad esempio, se hai un qualche pesante processo ETL che non riesci a realizzare all'interno di una singola transazione, e hai comunque <b>pgbouncer in modalit\u00e0 transazione<\/b>?..<\/p>\n<p>Oppure il flusso di dati \u00e8 cos\u00ec grande che <b>non c'\u00e8 sufficiente larghezza di banda di una connessione<\/b> con il DB (leggi, un processo per CPU)?..<\/p>\n<p>Oppure parte delle operazioni avviene <b>in modo asincrono<\/b> in diverse connessioni?..<\/p>\n<p>L'unica opzione qui \u00e8 \u2014 <b>creare temporaneamente una tabella non temporanea<\/b>. Gioco di parole, eh. Cio\u00e8:<\/p>\n<ul>\n<li>ho creato \u00able mie\u00bb tabelle con nomi massimamente casuali, per non sovrappormi a nessuno<\/li>\n<li><b>Estrai<\/b>: ho caricato i dati da una fonte esterna<\/li>\n<li><b>Trasforma<\/b>: ho trasformato, riempito i campi chiave di collegamento<\/li>\n<li><b>Carico<\/b>: ho trasferito i dati pronti nelle tabelle di destinazione<\/li>\n<li>ho eliminato \u00able mie\u00bb tabelle<\/li>\n<\/ul>\n<p>\nE ora \u2014 un cucchiaio di catrame. Fondamentalmente, <b>tutta la registrazione in PostgreSQL avviene due volte<\/b> \u2014 <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/postgrespro\/blog\/461523\/\">prima nel WAL<\/a><\/noindex>, poi nei corpi delle tabelle\/indici. Tutto ci\u00f2 \u00e8 stato fatto per supportare ACID e una corretta visibilit\u00e0 dei dati tra <code>COMMIT<\/code>\u2018inseriti e <code>ROLLBACK<\/code>\u2018inseriti nelle transazioni.<\/p>\n<p>Ma a noi non serve! Tutto il nostro processo <b>\u00e8 andato a buon fine, o no<\/b>. Non importa quante transazioni intermedie ci siano \u2014 non ci interessa \u00abcontinuare il processo da met\u00e0\u00bb, soprattutto quando non \u00e8 chiaro dove fosse.<\/p>\n<p>Per questo gli sviluppatori di PostgreSQL gi\u00e0 nella versione 9.1 hanno introdotto una funzione come <noindex><a rel=\"nofollow\" href=\"https:\/\/postgrespro.ru\/docs\/postgresql\/12\/sql-createtable#SQL-CREATETABLE-UNLOGGED\">tabelle non registrate (UNLOGGED)<\/a><\/noindex>:<\/p>\n<blockquote><p>Con questa indicazione, la tabella viene creata come non registrata. I dati scritti nelle tabelle non registrate non passano attraverso il log delle scritture (vedi Capitolo 29), risultando in tabelle di questo tipo <b>lavorano molto pi\u00f9 velocemente delle normali<\/b>. Tuttavia, non sono protette da guasti; in caso di guasto o spegnimento anomalo del server, la tabella non registrata <b>viene troncata automaticamente<\/b>. Inoltre, il contenuto della tabella non registrata <b>non viene replicato<\/b> ai server secondari. Qualsiasi indice creato per la tabella non registrata diventa automaticamente non registrato.<\/p><\/blockquote>\n<p>In breve, <b>sar\u00e0 notevolmente pi\u00f9 veloce<\/b>, ma se il server del DB va in crash \u2014 sar\u00e0 sgradevole. Ma succede spesso? E il vostro processo ETL \u00e8 in grado di riprendersi correttamente \"a met\u00e0\" dopo il \"riavvio\" del DB?...<\/p>\n<p>Se non \u00e8 cos\u00ec, e il caso sopra \u00e8 simile al vostro \u2014 usate <code>UNLOGGED<\/code>, ma non attivate mai questo attributo su tabelle reali <b>, i cui dati sono preziosi per voi.<\/b>1.3. ON COMMIT { DELETE ROWS | DROP }<\/p>\n<h3>Questa struttura consente di impostare un comportamento automatico al termine della transazione durante la creazione della tabella.<\/h3>\n<p>\nHo gi\u00e0 scritto sopra, genera<\/p>\n<p>Su <code>ALLA COMMIT <b>DROP<\/b><\/code> DROP TABLE <code>, ma la situazione con<\/code>\u00e8 pi\u00f9 interessante \u2014 qui viene generato <code>ALLA COMMIT <b>ELIMINA RIGHE<\/b><\/code> TRUNCATE TABLE <code>Poich\u00e9 l'intera infrastruttura di archiviazione della descrizione meta per la tabella temporanea \u00e8 esattamente la stessa di quella della tabella normale,<\/code>.<\/p>\n<p>la creazione e cancellazione frequente di tabelle temporanee porta a un forte \"aumento\" delle tabelle di sistema <b>pg_class, pg_attribute, pg_attrdef, pg_depend,\u2026<\/b> Ora immaginate di avere un worker connesso direttamente al DB, che ogni secondo apre una nuova transazione, crea, riempie, elabora e cancella una tabella temporanea\u2026 Si accumuler\u00e0 spazzatura nelle tabelle di sistema, causando rallentamenti in ogni operazione.<\/p>\n<p>In generale, non si dovrebbe fare cos\u00ec! In questo caso \u00e8 molto pi\u00f9 efficiente<\/p>\n<p>CREATE TEMPORARY TABLE x ... ON COMMIT DELETE ROWS <code>realizzare al di fuori del ciclo delle transazioni \u2014 cos\u00ec all'inizio di ogni nuova transazione la tabella<\/code> esister\u00e0 gi\u00e0 <b>(risparmiando una chiamata<\/b> ), ma <code>CREATE<\/code>sar\u00e0 vuota <b>, grazie a<\/b>TRUNCATE <code>(abbiamo risparmiato anche quella chiamata) al termine della transazione precedente.<\/code> 1.4. LIKE\u2026 INCLUDING \u2026<\/p>\n<h3>1.4. COME\u2026 INCLUDENDO \u2026<\/h3>\n<p>\nMa la pigrizia \u00e8 il motore del progresso! Quindi<\/p>\n<p>creare una nuova tabella \"per esempio\" <b>si pu\u00f2 fare molto pi\u00f9 facilmente:<\/b> CREATE TEMPORARY TABLE import_table(\n  LIKE target_table\n);<\/p>\n<pre><code class=\"sql\">CREA UNA TABELLA TEMPORANEA import_table(\n  SIMILE a target_table\n);<\/code><\/pre>\n<p>\nPoich\u00e9 \u00e8 possibile generare un numero considerevole di dati in questa tabella, le ricerche in essa non saranno affatto rapide. Ma c'\u00e8 una soluzione tradizionale a questo problema: gli indici! E s\u00ec, <b>anche la tabella temporanea pu\u00f2 avere indici<\/b>.<\/p>\n<p>Poich\u00e9 spesso gli indici necessari coincidono con quelli della tabella di destinazione, \u00e8 possibile semplicemente scrivere <code>LIKE target_table <b>INCLUSI GLI INDICI<\/b><\/code>.<\/p>\n<p>Se hai bisogno anche di <code>DEFAULT<\/code>-valori (ad esempio, per popolare i valori della chiave primaria), puoi avvalerti di <code>LIKE target_table <b>INCLUDENDO I VALORI PREDEFINITI<\/b><\/code>. O semplicemente \u2014 <code>LIKE target_table <b>INCLUSO TUTTO<\/b><\/code> \u2014 copier\u00e0 i valori predefiniti, gli indici, i vincoli,\u2026<\/p>\n<p>Ma qui \u00e8 importante capire che se hai creato <b>una tabella di importazione direttamente con indici, il caricamento dei dati richieder\u00e0 pi\u00f9 tempo<\/b>, rispetto a prima caricare tutti i dati e poi applicare gli indici \u2014 guarda come lo fa <noindex><a rel=\"nofollow\" href=\"https:\/\/postgrespro.ru\/docs\/postgresql\/12\/app-pgdump\">pg_dump<\/a><\/noindex>.<\/p>\n<p>In generale, <noindex><a rel=\"nofollow\" href=\"https:\/\/postgrespro.ru\/docs\/postgresql\/12\/sql-createtable\">RTFM<\/a><\/noindex>!<\/p>\n<h2>2. Come scrivere?<\/h2>\n<p>\nDico semplicemente: utilizza <code><noindex><a rel=\"nofollow\" href=\"https:\/\/postgrespro.ru\/docs\/postgresql\/12\/sql-copy\">COPY<\/a><\/noindex><\/code>-flusso invece di \"batch\" <code>INSERISCI<\/code>, <noindex><a rel=\"nofollow\" href=\"https:\/\/www.citusdata.com\/blog\/2017\/11\/08\/faster-bulk-loading-in-postgresql-with-copy\/\">acceleration di diversi ordini di grandezza<\/a><\/noindex>. Puoi anche farlo direttamente da un file precedentemente formato.<\/p>\n<h2>3. Come elaborare?<\/h2>\n<p>\nDunque, supponiamo che il nostro input sia pi\u00f9 o meno cos\u00ec:<\/p>\n<ul>\n<li>hai nel database una tabella con dati dei clienti di <b>1M record<\/b><\/li>\n<li>ogni giorno il cliente ti invia un nuovo <b>full \"snapshot\"<\/b><\/li>\n<li>sulla base della tua esperienza sai che di volta in volta <b>cambiano non pi\u00f9 di 10K record<\/b><\/li>\n<\/ul>\n<p>\nUn classico esempio di una situazione simile \u00e8 <noindex><a rel=\"nofollow\" href=\"https:\/\/www.gnivc.ru\/technical_support\/classifiers_reference\/kladr\/\">il database CLADR<\/a><\/noindex> \u2014 ci sono molti indirizzi in totale, ma in ciascun'esportazione settimanale di cambiamenti (rinominazioni di localit\u00e0, fusioni di strade, comparsa di nuovi edifici) ci sono davvero pochi cambiamenti anche in scala nazionale.<\/p>\n<h3>3.1. Algoritmo di sincronizzazione completa<\/h3>\n<p>\nPer semplicit\u00e0 presupponiamo che non sia necessario neppure ristrutturare i dati \u2014 basta portare la tabella nella forma desiderata, cio\u00e8:<\/p>\n<ul>\n<li><b>a eliminare<\/b> tutto ci\u00f2 che non esiste pi\u00f9<\/li>\n<li><b>aggiornare<\/b> tutto ci\u00f2 che esisteva gi\u00e0 e deve essere aggiornato<\/li>\n<li><b>inserire<\/b> tutto ci\u00f2 che non esisteva ancora<\/li>\n<\/ul>\n<p>\nPerch\u00e9 \u00e8 importante eseguire le operazioni in questo ordine? Perch\u00e9 in questo modo la dimensione della tabella crescer\u00e0 in modo minimo (<noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/tensor\/blog\/491366\/\">ricorda il MVCC!<\/a><\/noindex>).<\/p>\n<h4>DELETE FROM dst<\/h4>\n<p>\nNo, certo \u00e8 possibile ridursi a sole due operazioni:<\/p>\n<ul>\n<li><b>a eliminare<\/b> (<code>DELETE<\/code>) tutto ci\u00f2 che<\/li>\n<li><b>inserire<\/b> tutto dal nuovo snapshot<\/li>\n<\/ul>\n<p>\nMa a questo punto, grazie al MVCC, <b>la dimensione della tabella aumenter\u00e0 esattamente del doppio<\/b>! Ottenere +1M di snapshot di record nella tabella a causa dell'aggiornamento di 10K \u00e8 un'eccessivit\u00e0\u2026<\/p>\n<h4>TRUNCATE dst<\/h4>\n<p>\nUn sviluppatore pi\u00f9 esperto sa che l'intera tabella pu\u00f2 essere pulita a un costo abbastanza basso:<\/p>\n<ul>\n<li><b>pulire<\/b> (<code>(abbiamo risparmiato anche quella chiamata) al termine della transazione precedente.<\/code>) l'intera tabella<\/li>\n<li><b>inserire<\/b> tutto dal nuovo snapshot<\/li>\n<\/ul>\n<p>\nUn metodo efficace, <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/tensor\/blog\/481866\/\">a volte del tutto applicabile<\/a><\/noindex>, ma c'\u00e8 un problema... Iniettare 1M di record ci vorr\u00e0 molto tempo, quindi non possiamo permetterci di tenere la tabella vuota per tutto questo tempo (come accadrebbe senza racchiudere in un'unica transazione).<\/p>\n<p>E quindi:<\/p>\n<ul>\n<li>iniziamo <b>una lunga transazione<\/b><\/li>\n<li><code>(abbiamo risparmiato anche quella chiamata) al termine della transazione precedente.<\/code> applica <b>AccessExclusive<\/b>-bloccando<\/li>\n<li>facciamo inserimenti a lungo, mentre tutti gli altri in quel momento <b>non possono neanche <code>SELECT<\/code><\/b><\/li>\n<\/ul>\n<p>\nC'\u00e8 qualcosa che non va...<\/p>\n<h4>ALTER TABLE\u2026 RINOMINA\u2026 \/ DROP TABLE \u2026<\/h4>\n<p>\nCome opzione, caricare tutto in una nuova tabella separata, e poi semplicemente rinominarla al posto della vecchia. Un paio di cose antipatiche:<\/p>\n<ul>\n<li>anche questo <b>AccessExclusive<\/b>, anche se molto meno nel tempo<\/li>\n<li>tutti i piani delle query\/statistiche di questa tabella vengono azzerati, <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/tensor\/blog\/479656\/\">\u00e8 necessario eseguire ANALYZE<\/a><\/noindex><\/li>\n<li><b>tutte le chiavi esterne<\/b> (FK) sulla tabella<\/li>\n<\/ul>\n<p>\nC'era una patch WIP da Simon Riggs, che suggeriva di fare <code>ALTER<\/code>-operazione per sostituire il corpo della tabella a livello di file, senza toccare le statistiche e le FK, ma non ha raggiunto il quorum.<\/p>\n<h4>DELETE, UPDATE, INSERT<\/h4>\n<p>\nQuindi, ci fermiamo sull'opzione non bloccante delle tre operazioni. Quasi tre... Come possiamo farlo nel modo pi\u00f9 efficace?<\/p>\n<pre><code class=\"sql\">-- tutto si svolge all'interno della transazione, in modo che nessuno possa vedere gli \"stati intermedi\"\nBEGIN;\n\n-- creiamo una tabella temporanea con i dati importati\nCREATE TEMPORARY TABLE tmp(\n  LIKE dst INCLUDING INDEXES -- in forma simile, insieme agli indici\n) ON COMMIT DROP; -- al di fuori della transazione non ci serve\n\n-- rapidamente importiamo il nuovo insieme tramite COPY\nCOPY tmp FROM STDIN;\n-- ...\n-- .\n\n-- rimuoviamo gli assenti\nDELETE FROM\n  dst D\nUSING\n  dst X\nLEFT JOIN\n  tmp Y\n    USING(pk1, pk2) -- campi della chiave primaria\nWHERE\n  (D.pk1, D.pk2) = (X.pk1, X.pk2) AND\n  Y IS NOT DISTINCT FROM NULL; -- \"anti-join\"\n\n-- aggiorniamo i rimanenti\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); -- non ha senso aggiornare i coincidenti\n\n-- inseriamo gli assenti\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. Post-elaborazione dell'importazione<\/h3>\n<p>\nNello stesso KLDAR, tutte le registrazioni modificate devono essere ulteriormente elaborate \u2014 normalizzate, estratte le parole chiave, portate nelle strutture desiderate. Ma come sapere \u2014 <b>cosa \u00e8 stato effettivamente modificato<\/b>, senza complicare il codice di sincronizzazione, idealmente, senza neanche toccarlo?<\/p>\n<p>Se l'accesso in scrittura al momento della sincronizzazione \u00e8 disponibile solo per il tuo processo, puoi utilizzare un trigger che raccoglie tutte le modifiche per noi:<\/p>\n<pre><code class=\"sql\">-- tabelle di destinazione\nCREATE TABLE kladr(...);\nCREATE TABLE kladr_house(...);\n\n-- tabelle con la cronologia delle modifiche\nCREATE TABLE kladr$log(\n  ro kladr, -- qui si trovano le copie complete delle vecchie\/nome nuove registrazioni\n  rn kladr\n);\n\nCREATE TABLE kladr_house$log(\n  ro kladr_house,\n  rn kladr_house\n);\n\n-- funzione generale per la registrazione delle modifiche\nCREATE OR REPLACE FUNCTION diff$log() RETURNS trigger AS $$\nDECLARE\n  dst varchar = TG_TABLE_NAME || '$log';\n  stmt text = '';\nBEGIN\n  -- controlliamo la necessit\u00e0 di registrazione durante l'aggiornamento della registrazione\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  -- creiamo la registrazione del log\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>\nOra possiamo applicare (o attivare tramite <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>\nE poi estraiamo tranquillamente tutte le modifiche necessarie dalle tabelle di log e le inoltriamo ai gestori aggiuntivi.<\/p>\n<h3>3.3. Importazione di set correlati<\/h3>\n<p>\nIn precedenza abbiamo esaminato i casi in cui le strutture dei dati della sorgente e del ricevente coincidono. Ma cosa fare se l'esportazione da un sistema esterno ha un formato diverso dalla struttura di archiviazione nel nostro database?<\/p>\n<p>Consideriamo come esempio la memorizzazione dei clienti e delle fatture per essi, un caso classico di \"molti-a-uno\":<\/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>\nEcco che l'esportazione da una fonte esterna ci arriva in forma di \"tutto in uno\":<\/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>\nEvidentemente, i dati sui clienti possono essere duplicati in questa forma, e la registrazione principale \u00e8 \"fattura\":<\/p>\n<pre><code class=\"plaintext\">0123456789;Vasja;A-01;2020-03-16;1000.00\n9876543210;Petja;A-02;2020-03-16;666.00\n0123456789;Vasja;B-03;2020-03-16;9999.00\n<\/code><\/pre>\n<p>\nPer il modello inseriremo semplicemente i nostri dati di prova, ma ricordiamo \u2014 <code>COPY<\/code> pi\u00f9 efficace!<\/p>\n<pre><code class=\"sql\">INSERT INTO invoice_import\nVALUES\n  ('0123456789', 'Vasja', 'A-01', '2020-03-16', 1000.00)\n, ('9876543210', 'Petja', 'A-02', '2020-03-16', 666.00)\n, ('0123456789', 'Vasja', 'B-03', '2020-03-16', 9999.00);<\/code><\/pre>\n<p>\nIniziamo identificando i \"tagli\" a cui i nostri \"fatti\" si riferiscono. Nel nostro caso, le fatture si riferiscono ai clienti:<\/p>\n<pre><code class=\"sql\">CREATE TEMPORARY TABLE client_import AS\nSELECT DISTINCT ON(client_inn)\n-- si pu\u00f2 semplicemente usare SELECT DISTINCT, se i dati sono di per s\u00e9 non contraddittori\n  client_inn inn\n, client_name \"name\"\nFROM\n  invoice_import;<\/code><\/pre>\n<p>\nPer collegare correttamente le fatture agli ID dei clienti, dobbiamo prima conoscere o generare questi identificatori. Aggiungiamo i campi per essi:<\/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>\nUtilizzeremo il metodo descritto sopra per sincronizzare le tabelle con una piccola modifica: non aggiorneremo n\u00e9 elimineremo nulla nella tabella di destinazione, poich\u00e9 l'importazione dei clienti \u00e8 \"append-only\":<\/p>\n<pre><code class=\"sql\">-- aggiorniamo la tabella di importazione con gli ID delle registrazioni gi\u00e0 esistenti\nUPDATE\n  client_import T\nSET\n  client_id = D.client_id\nFROM\n  client D\nWHERE\n  T.inn = D.inn; -- chiave unica\n\n-- inseriamo le registrazioni mancanti e impostiamo i loro ID\nWITH ins AS (\n  INSERT INTO client(\n    inn\n  , name\n  )\n  SELEZIONA\n    inn\n  , name\n  DA\n    client_import\n  DOVE\n    client_id IS NULL -- se l'ID non \u00e8 stato impostato\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; -- chiave unica\n\n-- impostiamo gli ID dei clienti nei record delle fatture\nUPDATE\n  invoice_import T\nSET\n  client_id = D.client_id\nFROM\n  client_import D\nWHERE\n  T.client_inn = D.inn; -- chiave applicativa\n<\/code><\/pre>\n<p>\nIn effetti, \u00e8 tutto \u2014 in <code>invoice_import<\/code> ora abbiamo il campo di collegamento compilato <code>client_id<\/code>, con il quale inseriremo la fattura.<br \/>\n<br \/>Fonte: <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\/it\/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=\"it_IT\" \/>\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\/it\/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: organizziamo in modo efficace sincronizzazioni e importazioni | ProHoster","description":"Nell'elaborazione complessa di grandi set di dati (vari processi ETL: importazioni, conversioni e sincronizzazioni con fonti esterne) spesso.","canonical_url":"https:\/\/prohoster.info\/it\/blog\/administrirovanie\/dba-gramotno-organizovyvaem-sinhronizaczii-i-importy","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"it_IT","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\/it\/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\/it\/wp-json\/wp\/v2\/posts\/74953","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/comments?post=74953"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/posts\/74953\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/media\/74954"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/media?parent=74953"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/categories?post=74953"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/tags?post=74953"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}