{"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\/sv\/blog\/administrirovanie\/dba-gramotno-organizovyvaem-sinhronizaczii-i-importy","title":{"rendered":"DBA: vi organiserar synkroniseringar och importer p\u00e5 ett smart s\u00e4tt","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>Vid komplex bearbetning av stora datam\u00e4ngder (olika <noindex><a rel=\"nofollow\" href=\"https:\/\/ru.wikipedia.org\/wiki\/ETL\">ETL-processer<\/a><\/noindex>: importer, konverteringar och synkronisering med externa k\u00e4llor) uppst\u00e5r ofta behovet <b>att tempor\u00e4rt \"komma ih\u00e5g\", och snabbt bearbeta<\/b> n\u00e5got omfattande.<\/p>\n<p>En typisk uppgift av detta slag l\u00e5ter oftast s\u00e5 h\u00e4r: <i>\"H\u00e4r har <noindex><a rel=\"nofollow\" href=\"https:\/\/sbis.ru\/accounting\">bokf\u00f6ringen laddat ner senaste betalningar fr\u00e5n kundens bank<\/a><\/noindex> som snabbt m\u00e5ste laddas upp p\u00e5 webbplatsen och kopplas till kontona\".<\/i><\/p>\n<p>Men n\u00e4r volymen av detta \"n\u00e5got\" b\u00f6rjar m\u00e4tas i hundratals megabyte, och tj\u00e4nsten samtidigt m\u00e5ste forts\u00e4tta fungera med databasen dygnet runt, uppst\u00e5r det m\u00e5nga biverkningar som kommer att f\u00f6rst\u00f6ra din vardag.<br \/>\n<img decoding=\"async\" alt=\"DBA: vi organiserar synkroniseringar och importer p\u00e5 ett smart s\u00e4tt\" src=\"\/wp-content\/uploads\/2020\/03\/f74afb2cd6f5f8de26a0932166933c95.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nF\u00f6r att hantera dem i PostgreSQL (och \u00e4ven i andra system) kan man anv\u00e4nda vissa m\u00f6jligheter f\u00f6r optimeringar som g\u00f6r att allt bearbetas snabbare och med mindre resursf\u00f6rbrukning.<br \/>\n<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<h2>1. Var ska vi ladda?<\/h2>\n<p>\nF\u00f6rst, l\u00e5t oss klarg\u00f6ra vart vi kan ladda data som vi vill \"bearbeta\".<\/p>\n<h3>1.1. Tempor\u00e4ra tabeller (TEMPORARY TABLE)<\/h3>\n<p>\nI princip \u00e4r tempor\u00e4ra tabeller i PostgreSQL precis som vanliga tabeller. D\u00e4rf\u00f6r \u00e4r vidskepligheten om att <i><b>\"de lagras bara i minnet, och det kan ta slut\"<\/b><\/i>felaktig. Men det finns n\u00e5gra betydande skillnader.<\/p>\n<h4>Ett eget \"namnrum\" f\u00f6r varje anslutning till databasen<\/h4>\n<p>\nOm tv\u00e5 anslutningar f\u00f6rs\u00f6ker samtidigt utf\u00f6ra <code>CREATE TABLE x<\/code>, s\u00e5 kommer n\u00e5gon garanterat f\u00e5 <b>en unikhetsfel<\/b> f\u00f6r databaskonton.<\/p>\n<p>Men om b\u00e5da f\u00f6rs\u00f6ker utf\u00f6ra <code>T\u00e4nk p\u00e5 f\u00f6rkortningar. <b>TILLF\u00c4LIG<\/b> TABELL x<\/code>, s\u00e5 kommer b\u00e5da att lyckas, och varje kommer att f\u00e5 <b>sin egen instans<\/b> av tabellen. Och ingenting kommer att vara gemensamt mellan dem.<\/p>\n<h4>\"Sj\u00e4lvf\u00f6rst\u00f6relse\" vid avbrott<\/h4>\n<p>\nVid st\u00e4ngning av anslutningen tas alla tempor\u00e4ra tabeller automatiskt bort, s\u00e5 det finns ingen mening med att \"manuellt\" utf\u00f6ra <code>DROP TABLE x<\/code> utom\u2026<\/p>\n<p>Om du arbetar genom <b>pgbouncer i transaktionsl\u00e4ge<\/b>, s\u00e5 anser databasen att anslutningen fortfarande \u00e4r aktiv, och i denna anslutning existerar fortfarande denna tempor\u00e4ra tabell.<\/p>\n<p>D\u00e4rf\u00f6r kommer ett f\u00f6rs\u00f6k att skapa den igen, redan fr\u00e5n en annan anslutning till pgbouncer, att leda till ett fel. Men detta kan omg\u00e5s genom att anv\u00e4nda <code>SKAPA TILLF\u00c4LLIG TABELL <b>OM DET INTE FINNS<\/b> x<\/code>.<\/p>\n<p>Men det \u00e4r b\u00e4ttre att inte g\u00f6ra s\u00e5, eftersom man d\u00e5 kan \"ov\u00e4ntat\" uppt\u00e4cka data d\u00e4r, som tillh\u00f6rde \"f\u00f6reg\u00e5ende \u00e4gare\". Ist\u00e4llet \u00e4r det mycket b\u00e4ttre att l\u00e4sa manualen och se att n\u00e4r man skapar en tabell finns det m\u00f6jlighet att skriva till. <code>P\u00c5 \u00c5TAGANDE <b>DROP<\/b><\/code> \u2014 det vill s\u00e4ga vid avslutandet av transaktionen kommer tabellen att tas bort automatiskt.<\/p>\n<h4>Icke-replikering<\/h4>\n<p>\nEftersom de endast tillh\u00f6r en viss anslutning, replikeras inte tempor\u00e4ra tabeller. Men <b>detta eliminerar behovet av dubbel skrivning av data<\/b> till heap + WAL, s\u00e5 INSERT\/UPDATE\/DELETE till dem \u00e4r avsev\u00e4rt snabbare.<\/p>\n<p>Men eftersom en tempor\u00e4r tabell fortfarande \u00e4r en \"n\u00e4stan vanlig\" tabell, kan den inte skapas p\u00e5 replikan heller. \u00c5tminstone inte \u00e4nnu, \u00e4ven om en passande patch har funnits l\u00e4nge.<\/p>\n<h3>1.2. Icke-journaliserade tabeller (UNLOGGED TABLE)<\/h3>\n<p>\nMen vad g\u00f6r man om man till exempel har en kr\u00e5nglig ETL-process som inte kan genomf\u00f6ras i en enda transaktion, och ni har faktiskt <b>pgbouncer i transaktionsl\u00e4ge<\/b>?..<\/p>\n<p>Eller datastreamen \u00e4r s\u00e5 stor att <b>en enda anslutning inte har tillr\u00e4cklig kapacitet<\/b> till DB (l\u00e4s, en process per CPU)?..<\/p>\n<p>Eller en del operationer g\u00e5r <b>asynkront<\/b> i olika kopplingar?..<\/p>\n<p>H\u00e4r finns bara ett alternativ \u2014 <b>att tillf\u00e4lligt skapa en icke-tempor\u00e4r tabell<\/b>. En kalambur, visst. Det vill s\u00e4ga:<\/p>\n<ul>\n<li>skapa \"egna\" tabeller med maximalt slumpm\u00e4ssiga namn, s\u00e5 att man inte krockar med n\u00e5gon annan<\/li>\n<li><b>Extrahera<\/b>: ladda upp data fr\u00e5n en extern k\u00e4lla<\/li>\n<li><b>Transformera<\/b>: omvandla, fylla i nyckelkopplingsf\u00e4lt<\/li>\n<li><b>Ladda<\/b>: h\u00e4lla f\u00e4rdiga data i m\u00e5l-tabeller<\/li>\n<li>ta bort \"egna\" tabeller<\/li>\n<\/ul>\n<p>\nOch nu \u2014 en sked av bitterhet. I princip <b>skriver hela tiden data i PostgreSQL tv\u00e5 g\u00e5nger<\/b> \u2014 <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/postgrespro\/blog\/461523\/\">f\u00f6rst till WAL<\/a><\/noindex>, och sedan till tabellernas\/katalogernas kroppar. Allt detta g\u00f6rs f\u00f6r att st\u00f6dja ACID och korrekt datavisning mellan <code>COMMIT<\/code>inkluderade och <code>ROLLBACK<\/code>inkluderade transaktioner.<\/p>\n<p>externa transaktioner. <b>Men vi beh\u00f6ver inte detta! Hela processen<\/b>antingen slutf\u00f6rdes framg\u00e5ngsrikt, eller s\u00e5 gjorde den inte.<\/p>\n<p>Det spelar ingen roll hur m\u00e5nga mellanliggande transaktioner som beh\u00f6vs \u2014 vi \u00e4r inte intresserade av att \"f\u00f6ra processen vidare fr\u00e5n mitten\", s\u00e4rskilt inte n\u00e4r det \u00e4r oklart var den befann sig. <noindex><a rel=\"nofollow\" href=\"https:\/\/postgrespro.ru\/docs\/postgresql\/12\/sql-createtable#SQL-CREATETABLE-UNLOGGED\">F\u00f6r detta inf\u00f6rde PostgreSQL:s utvecklare redan i version 9.1 n\u00e5got som kallas<\/a><\/noindex>:<\/p>\n<blockquote><p>icke-journaliserade (UNLOGGED) tabeller. <b>Med denna angivelse skapas tabellen som en icke-journaliserad. Data som skrivs till icke-journaliserade tabeller g\u00e5r inte igenom f\u00f6rskottsjournalen (se Kapitel 29), vilket g\u00f6r att s\u00e5dana tabeller<\/b>fungerar mycket snabbare \u00e4n vanliga. <b>Men de \u00e4r inte skyddade mot fel; vid ett fel eller ov\u00e4ntad str\u00f6mavbrott sk\u00e4rs den icke-journaliserade tabellen<\/b>automatiskt av. <b>Dessutom replikeras inneh\u00e5llet i en icke-journaliserad tabell inte.<\/b> p\u00e5 skrivna servrar. Alla index som skapas f\u00f6r en icke-journalf\u00f6rd tabell blir automatiskt icke-journalf\u00f6rda.<\/p><\/blockquote>\n<p>Kort sagt, <b>blir det betydligt snabbare<\/b>, men om databasservern \"g\u00e5r ner\" \u2014 blir det obehagligt. Men hur ofta h\u00e4nder det, och kan din ETL-process hantera det korrekt \"fr\u00e5n mitten\" efter \"\u00e5terst\u00e4llningen\" av databasen?..<\/p>\n<p>Om inte, och om ovanst\u00e5ende scenario liknar ditt \u2014 anv\u00e4nd <code>UNLOGGED<\/code>, men aktivera aldrig <b>denna attribut p\u00e5 verkliga tabeller<\/b>, vars data \u00e4r viktiga f\u00f6r dig.<\/p>\n<h3>1.3. ON COMMIT { DELETE ROWS | DROP }<\/h3>\n<p>\nDenna konstruktion g\u00f6r det m\u00f6jligt att definiera automatiskt beteende vid transaktionsavslut n\u00e4r tabellen skapas.<\/p>\n<p>Om <code>P\u00c5 \u00c5TAGANDE <b>DROP<\/b><\/code> Jag har redan n\u00e4mnt det ovan, det genererar <code>DROP TABLE<\/code>, men h\u00e4r med <code>P\u00c5 \u00c5TAGANDE <b>TA BORT RADER<\/b><\/code> \u00e4r situationen intressantare \u2014 h\u00e4r genereras <code>TRUNCATE TABLE<\/code>.<\/p>\n<p>Eftersom hela infrastrukturen f\u00f6r lagring av metadata f\u00f6r tempor\u00e4ra tabeller \u00e4r exakt densamma som f\u00f6r vanliga, s\u00e5 <b>den st\u00e4ndiga skapelsen och borttagningen av tempor\u00e4ra tabeller leder till kraftig \"utvidgning\" av systemtabeller<\/b> pg_class, pg_attribute, pg_attrdef, pg_depend,\u2026<\/p>\n<p>Nu kan du f\u00f6rest\u00e4lla dig att du har en arbetare med direktanslutning till databasen som varje sekund \u00f6ppnar en ny transaktion, skapar, fyller i, bearbetar och tar bort en tempor\u00e4r tabell\u2026 Skr\u00e4pet i systemtabellerna kommer att samlas i \u00f6verfl\u00f6d, och detta ger extra f\u00f6rdr\u00f6jningar vid varje k\u00f6rning.<\/p>\n<p>Sammanfattningsvis, g\u00f6r inte s\u00e5! I det h\u00e4r fallet \u00e4r det mycket effektivare <code>CREATE TEMPORARY TABLE x ... ON COMMIT DELETE ROWS<\/code> att flytta utanf\u00f6r transaktionscykeln \u2014 d\u00e5 kommer tabellerna i b\u00f6rjan av varje ny transaktion redan <b>att existera<\/b> (-spara anropet <code>T\u00e4nk p\u00e5 f\u00f6rkortningar.<\/code>), men <b>kommer att vara tom<\/b>, tack vare <code>TRUNCATE<\/code> (-detta anrop har vi ocks\u00e5 sparat) vid avslutningen av den f\u00f6reg\u00e5ende transaktionen.<\/p>\n<h3>1.4. LIKA... INKLUDERANDE ...<\/h3>\n<p>\nJag n\u00e4mnde i b\u00f6rjan att ett typiskt anv\u00e4ndningsfall f\u00f6r tempor\u00e4ra tabeller \u00e4r olika typer av importer \u2014 och utvecklaren kopierar tr\u00f6tt det m\u00e5ltabellens f\u00e4ltlista till sitt tillk\u00e4nnagivande f\u00f6r den tempor\u00e4ra \u2026<\/p>\n<p>Men lathet \u00e4r framstegens motor! S\u00e5, <b>att skapa en ny tabell \"efter mall\"<\/b> kan g\u00f6ras mycket enklare:<\/p>\n<pre><code class=\"sql\">CREATE TEMPORARY TABLE import_table(\n  LIKE target_table\n);<\/code><\/pre>\n<p>\nEftersom det kommer att genereras en hel del data till denna tabell s\u00e5 kommer s\u00f6kningarna i den att inte vara snabba. Men det finns en traditionell l\u00f6sning mot detta \u2014 index! Och ja, <b>en tempor\u00e4r tabell kan ocks\u00e5 ha index<\/b>.<\/p>\n<p>Eftersom de n\u00f6dv\u00e4ndiga indexerna ofta \u00f6verensst\u00e4mmer med indexerna f\u00f6r m\u00e5l-tabellen kan man helt enkelt skriva <code>LIKE m\u00e5ltavla <b>INKLUDERAR INDEX<\/b><\/code>.<\/p>\n<p>Om du ocks\u00e5 beh\u00f6ver <code>DEFAULT<\/code>-v\u00e4rden (till exempel f\u00f6r att fylla i v\u00e4rdena f\u00f6r prim\u00e4rnyckeln) kan man anv\u00e4nda <code>LIKE m\u00e5ltavla <b>INKLUDERAR STANDARDINST\u00c4LLNINGAR<\/b><\/code>. Eller bara \u2014 <code>LIKE m\u00e5ltavla <b>INNEH\u00c5LLER ALLT<\/b><\/code> \u2014 kopierar standardv\u00e4rdena, index, begr\u00e4nsningar,\u2026<\/p>\n<p>Men h\u00e4r m\u00e5ste man f\u00f6rst\u00e5 att om du skapade <b>import-tabellen direkt med index, s\u00e5 kommer datan att laddas l\u00e4ngre<\/b>, \u00e4n om man f\u00f6rst laddar allt, och sedan l\u00e4gger p\u00e5 index \u2014 se som exempel hur detta g\u00f6rs av <noindex><a rel=\"nofollow\" href=\"https:\/\/postgrespro.ru\/docs\/postgresql\/12\/app-pgdump\">pg_dump<\/a><\/noindex>.<\/p>\n<p>Kort sagt, <noindex><a rel=\"nofollow\" href=\"https:\/\/postgrespro.ru\/docs\/postgresql\/12\/sql-createtable\">RTFM<\/a><\/noindex>!<\/p>\n<h2>2. Hur skriver man?<\/h2>\n<p>\nJag s\u00e4ger helt enkelt \u2014 anv\u00e4nd <code><noindex><a rel=\"nofollow\" href=\"https:\/\/postgrespro.ru\/docs\/postgresql\/12\/sql-copy\">COPY<\/a><\/noindex><\/code>-str\u00f6m ist\u00e4llet f\u00f6r \"batch\" <code>INS\u00c4TT<\/code>, <noindex><a rel=\"nofollow\" href=\"https:\/\/www.citusdata.com\/blog\/2017\/11\/08\/faster-bulk-loading-in-postgresql-with-copy\/\">accelerationen \u00e4r flera g\u00e5nger<\/a><\/noindex>. Man kan till och med direkt fr\u00e5n den f\u00f6rbereda filen.<\/p>\n<h2>3. Hur bearbetar man?<\/h2>\n<p>\nS\u00e5, l\u00e5t oss s\u00e4ga att v\u00e5r indata ser ut ungef\u00e4r s\u00e5 h\u00e4r:<\/p>\n<ul>\n<li>du har en tabell med kunddata i <b>1M poster<\/b><\/li>\n<li>varje dag skickar kunden dig en ny <b>full \"bild\"<\/b><\/li>\n<li>utifr\u00e5n erfarenhet vet du att det fr\u00e5n g\u00e5ng till g\u00e5ng <b>\u00e4ndras inte mer \u00e4n 10K poster<\/b><\/li>\n<\/ul>\n<p>\nEtt klassiskt exempel p\u00e5 en s\u00e5dan situation \u00e4r <noindex><a rel=\"nofollow\" href=\"https:\/\/www.gnivc.ru\/technical_support\/classifiers_reference\/kladr\/\">KLRAD-databasen<\/a><\/noindex> \u2014 det finns m\u00e5nga adresser, men i varje veckolastning av \u00e4ndringar (omd\u00f6pnings av bos\u00e4ttningar, sammanslagningar av gator, nya hus som dyker upp) \u00e4r det v\u00e4ldigt f\u00e5 \u00e4ven i hela landet.<\/p>\n<h3>3.1. Algoritm f\u00f6r fullst\u00e4ndig synkronisering<\/h3>\n<p>\nF\u00f6r enkelhetens skull antar vi att du inte ens beh\u00f6ver omstrukturera datan \u2014 bara anpassa tabellen till r\u00e4tt format, vilket inneb\u00e4r:<\/p>\n<ul>\n<li><b>radera<\/b> allt som inte l\u00e4ngre finns<\/li>\n<li><b>uppgradera<\/b> allt som redan funnits och beh\u00f6ver uppdateras<\/li>\n<li><b>inf\u00f6ra<\/b> allt som \u00e4nnu inte funnits<\/li>\n<\/ul>\n<p>\nVarf\u00f6r exakt i den ordningen b\u00f6r man utf\u00f6ra operationer? F\u00f6r att det \u00e4r s\u00e5 storleken p\u00e5 tabellen v\u00e4xer minimalt (<noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/tensor\/blog\/491366\/\">kom ih\u00e5g MVCC!<\/a><\/noindex>).<\/p>\n<h4>DELETE FROM dst<\/h4>\n<p>\nNej, man kan sj\u00e4lvklart klara sig med bara tv\u00e5 operationer:<\/p>\n<ul>\n<li><b>radera<\/b> (<code>TA BORT<\/code>) hela<\/li>\n<li><b>inf\u00f6ra<\/b> allt fr\u00e5n den nya bilden<\/li>\n<\/ul>\n<p>\nMen samtidigt, tack vare MVCC, <b>kommer storleken p\u00e5 tabellen att \u00f6ka exakt dubbelt s\u00e5 mycket<\/b>! Att f\u00e5 +1M poster i tabellen p\u00e5 grund av att uppdatera 10K \u2014 \u00e4r en s\u00e5d\u00e4r \u00f6verfl\u00f6dighet\u2026<\/p>\n<h4>TRUNCATE dst<\/h4>\n<p>\nEn mer erfaren utvecklare vet att man kan rensa hela tabellen ganska billigt:<\/p>\n<ul>\n<li><b>rensa<\/b> (<code>TRUNCATE<\/code>) hela tabellen<\/li>\n<li><b>inf\u00f6ra<\/b> allt fr\u00e5n den nya bilden<\/li>\n<\/ul>\n<p>\nMetoden \u00e4r effektiv, <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/tensor\/blog\/481866\/\">ibland helt till\u00e4mplig<\/a><\/noindex>, men det finns ett problem\u2026 Att f\u00f6ra in 1M poster kommer att ta l\u00e5ng tid, s\u00e5 vi kan inte till\u00e5ta att l\u00e4mna tabellen tom under hela denna tid (som skulle h\u00e4nda utan att kapsla in i en enda transaktion).<\/p>\n<p>Det betyder att:<\/p>\n<ul>\n<li>vi inleder <b>en l\u00e5ngvarig transaktion<\/b><\/li>\n<li><code>TRUNCATE<\/code> det l\u00e4gger p\u00e5 <b>AccessExclusive<\/b>-l\u00e5sningen<\/li>\n<li>vi g\u00f6r l\u00e5ngsamma ins\u00e4ttningar, och alla andra kan under tiden <b>inte ens <code>V\u00c4LJ<\/code><\/b><\/li>\n<\/ul>\n<p>\nDet k\u00e4nns inte bra\u2026<\/p>\n<h4>ALTER TABLE\u2026 RENAME... \/ DROP TABLE ...<\/h4>\n<p>\nEtt alternativ \u00e4r att ladda upp allt i en ny separat tabell och sedan helt enkelt d\u00f6pa om den till den gamla. N\u00e5gra jobbiga sm\u00e5saker:<\/p>\n<ul>\n<li>ocks\u00e5 det <b>AccessExclusive<\/b>, om \u00e4n betydligt mindre i tid<\/li>\n<li>alla f\u00f6rfr\u00e5gningsplaner\/statistik f\u00f6r denna tabell \u00e5terst\u00e4lls, <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/tensor\/blog\/479656\/\">man m\u00e5ste k\u00f6ra ANALYZE<\/a><\/noindex><\/li>\n<li><b>alla externa nycklar<\/b> (FK) till tabellen bryts<\/li>\n<\/ul>\n<p>\nDet fanns en WIP-patch fr\u00e5n Simon Riggs som f\u00f6reslog att g\u00f6ra <code>ALTER<\/code>-operationen f\u00f6r att ers\u00e4tta tabellens kropp p\u00e5 filniv\u00e5, utan att r\u00f6ra statistiken och FK, men han fick inte ihop tillr\u00e4ckligt med r\u00f6ster.<\/p>\n<h4>DELETE, UPDATE, INSERT<\/h4>\n<p>\nS\u00e5, vi stannar vid den icke-blockerande varianten av de tre operationerna. N\u00e4stan tre\u2026 Hur g\u00f6r vi det mest effektivt?<\/p>\n<pre><code class=\"sql\">-- allt g\u00f6rs inom ramen f\u00f6r en transaktion s\u00e5 att ingen ser \"mellan\" tillst\u00e5nd\nBEGIN;\n\n-- skapar en tempor\u00e4r tabell med importerade data\nCREATE TEMPORARY TABLE tmp(\n  LIKE dst INCLUDING INDEXES -- i likhet med original, inklusive index\n) ON COMMIT DROP; -- utanf\u00f6r transaktionen beh\u00f6vs den inte\n\n-- snabbt injicerar den nya bilden via COPY\nCOPY tmp FROM STDIN;\n-- ...\n-- .\n\n-- tar bort de som saknas\nDELETE FROM\n  dst D\nUSING\n  dst X\nLEFT JOIN\n  tmp Y\n    USING(pk1, pk2) -- prim\u00e4rnyckelns f\u00e4lt\nWHERE\n  (D.pk1, D.pk2) = (X.pk1, X.pk2) AND\n  Y IS NOT DISTINCT FROM NULL; -- \"anti-join\"\n\n-- uppdaterar de kvarvarande\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); -- ingen anledning att uppdatera matchande\n\n-- infogar de som saknas\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. Efterbehandling av importen<\/h3>\n<p>\nI samma KLDRe m\u00e5ste alla \u00e4ndrade poster dessutom k\u00f6ras igenom efterbehandling \u2014 normaliseras, nyckelord identifieras, och struktureranpassas. Men hur vet man \u2014 <b>vad som faktiskt \u00e4ndrades<\/b>, utan att komplicera synkroniseringskoden, helst utan att r\u00f6ra den \u00f6verhuvudtaget?<\/p>\n<p>Om skriv\u00e5tkomst under synkroniseringen endast finns f\u00f6r din process kan du anv\u00e4nda en trigger som sammanst\u00e4ller alla \u00e4ndringar f\u00f6r oss:<\/p>\n<pre><code class=\"sql\">-- m\u00e5ltavlor\nCREATE TABLE kladr(...);\nCREATE TABLE kladr_house(...);\n\n-- tabeller med \u00e4ndringshistorik\nCREATE TABLE kladr$log(\n  ro kladr, -- h\u00e4r ligger hela versioner av gamla\/nya poster\n  rn kladr\n);\n\nCREATE TABLE kladr_house$log(\n  ro kladr_house,\n  rn kladr_house\n);\n\n-- generell Funktion f\u00f6r loggning av \u00e4ndringar\nCREATE OR REPLACE FUNCTION diff$log() RETURNS trigger AS $$\nDECLARE\n  dst varchar = TG_TABLE_NAME || '$log';\n  stmt text = '';\nBEGIN\n  -- kontrollerar om loggning beh\u00f6vs vid uppdatering av post\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  -- skapar en loggpost\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 applicera triggers innan synkroniseringen (eller aktivera 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>\nSedan kan vi enkelt h\u00e4mta alla n\u00f6dv\u00e4ndiga \u00e4ndringar fr\u00e5n loggtabellerna och k\u00f6ra dem genom ytterligare processorer.<\/p>\n<h3>3.3. Import av relaterade upps\u00e4ttningar<\/h3>\n<p>\nOvan har vi diskuterat fall d\u00e4r datak\u00e4llor och mottagare har samma struktur. Men vad ska vi g\u00f6ra om export fr\u00e5n ett externt system har ett format som skiljer sig fr\u00e5n v\u00e5r databasstruktur?<\/p>\n<p>Vi tar som exempel lagring av kunder och deras fakturor, en klassisk \"m\u00e5nga-till-en\"-variant:<\/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>\nOch nu kommer exporten fr\u00e5n en extern k\u00e4lla till oss som \"allt-i-ett\":<\/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 \u00e4r uppenbart att kunddata kan dupliceras i detta fall, och huvudposten \u00e4r \"faktura\":<\/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>\nF\u00f6r modellen kommer vi helt enkelt att infoga v\u00e5ra testdata, men kom ih\u00e5g \u2014 <code>COPY<\/code> effektivare!<\/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\u00f6rst utvalda de \"skivor\" som v\u00e5ra \"fakta\" h\u00e4nvisar till. I v\u00e5rt fall h\u00e4nvisar fakturorna till kunder:<\/p>\n<pre><code class=\"sql\">CREATE TEMPORARY TABLE client_import AS\nSELECT DISTINCT ON(client_inn)\n-- kan helt enkelt vara SELECT DISTINCT om uppgifterna \u00e4r uppenbart konsekventa\n  client_inn inn\n, client_name \"name\"\nFROM\n  invoice_import;<\/code><\/pre>\n<p>\nF\u00f6r att korrekt koppla fakturor till kundernas ID m\u00e5ste vi f\u00f6rst f\u00e5 reda p\u00e5 eller generera dessa identifierare. Vi l\u00e4gger till f\u00e4lt f\u00f6r dem:<\/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>\nVi anv\u00e4nder den beskrivna metoden f\u00f6r att synkronisera tabeller med en liten justering \u2014 vi kommer inte att uppdatera eller ta bort n\u00e5got i m\u00e5ltabellen, eftersom importen av kunder \u00e4r \"endast till\u00e4gg\":<\/p>\n<pre><code class=\"sql\">-- vi s\u00e4tter ID:n f\u00f6r redan befintliga poster i importtabellen\nUPDATE\n  client_import T\nSET\n  client_id = D.client_id\nFR\u00c5N\n  client D\nVARF\u00d6R\n  T.inn = D.inn; -- unik nyckel\n\n-- vi l\u00e4gger in saknade poster och s\u00e4tter deras ID:n\nMED ins SOM (\n  INSERT INTO client(\n    inn\n  , name\n  )\n  SELECT\n    inn\n  , name\n  FR\u00c5N\n    client_import\n  VARF\u00d6R\n    client_id \u00c4R NULL -- om ID:n inte har satts\n  RETURNING *\n)\nUPDATE\n  client_import T\nSET\n  client_id = D.client_id\nFR\u00c5N\n  ins D\nVARF\u00d6R\n  T.inn = D.inn; -- unik nyckel\n\n-- vi s\u00e4tter kundernas ID:n p\u00e5 fakturarekorden\nUPDATE\n  invoice_import T\nSET\n  client_id = D.client_id\nFR\u00c5N\n  client_import D\nVARF\u00d6R\n  T.client_inn = D.inn; -- till\u00e4mpad nyckel\n<\/code><\/pre>\n<p>\nDet \u00e4r faktiskt allt \u2014 i <code>invoice_import<\/code> \u00e4r nu f\u00e4ltet f\u00f6r kopplingen ifyllt <code>client_id<\/code>, som vi ska anv\u00e4nda f\u00f6r att inf\u00f6ra fakturan.<br \/>\n<br \/>K\u00e4lla: <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.4 - 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\/sv\/blog\/administrirovanie\/dba-gramotno-organizovyvaem-sinhronizaczii-i-importy\" \/>\n\t\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.4\" \/>\n\t\t<meta property=\"og:locale\" content=\"sv_SE\" \/>\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\/sv\/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: organisation av synkroniseringar och importer p\u00e5 ett korrekt s\u00e4tt | ProHoster","description":"Vid komplex bearbetning av stora datam\u00e4ngder (olika ETL-processer: importer, konverteringar och synkronisering med externa k\u00e4llor) ofta.","canonical_url":"https:\/\/prohoster.info\/sv\/blog\/administrirovanie\/dba-gramotno-organizovyvaem-sinhronizaczii-i-importy","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"sv_SE","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\/sv\/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\/sv\/wp-json\/wp\/v2\/posts\/74953","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/sv\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/sv\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/sv\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/sv\/wp-json\/wp\/v2\/comments?post=74953"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/sv\/wp-json\/wp\/v2\/posts\/74953\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/sv\/wp-json\/wp\/v2\/media\/74954"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/sv\/wp-json\/wp\/v2\/media?parent=74953"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/sv\/wp-json\/wp\/v2\/categories?post=74953"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/sv\/wp-json\/wp\/v2\/tags?post=74953"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}