Denne nettsiden inneholder forslag til skript/kode for å flytte/migrere eksempeldatabasen Hobbyhuset fra MySQL til Neo4j. Ikke alt er testet like godt – noe kan gi feil og mye kan sikkert gjøres på andre måter. Tips til forbedringer mottas!
En relasjonsdatabase kan importeres i Neo4j på flere måter. Her er en innføring som forklarer mer om denne prosessen.
Dette steget gjøres fra MySQL.
(SELECT 'PostNr','Poststed')
UNION
SELECT * FROM Poststed
INTO OUTFILE 'C:/tmp/Poststed.csv'
FIELDS ENCLOSED BY ''
TERMINATED BY ';'
ESCAPED BY '"'
LINES TERMINATED BY '\r\n';
(SELECT 'AnsNr','Fornavn', 'Etternavn', 'Adresse', 'PostNr', 'Fødselsdato', 'Kjønn', 'Stilling', 'Årslønn')
UNION
SELECT * FROM Ansatt
INTO OUTFILE 'C:/tmp/Ansatt.csv'
FIELDS ENCLOSED BY ''
TERMINATED BY ';'
ESCAPED BY '"'
LINES TERMINATED BY '\r\n';
(SELECT 'KNr','Fornavn', 'Etternavn', 'Adresse', 'PostNr')
UNION
SELECT * FROM Kunde
INTO OUTFILE 'C:/tmp/Kunde.csv'
FIELDS ENCLOSED BY ''
TERMINATED BY ';'
ESCAPED BY '"'
LINES TERMINATED BY '\r\n';
(SELECT 'KatNr','Navn')
UNION
SELECT * FROM Kategori
INTO OUTFILE 'C:/tmp/Kategori.csv'
FIELDS ENCLOSED BY ''
TERMINATED BY ';'
ESCAPED BY '"'
LINES TERMINATED BY '\r\n';
(SELECT 'VNr', 'Betegnelse', 'Pris', 'KatNr', 'Antall', 'Hylle')
UNION
SELECT * FROM Vare
INTO OUTFILE 'C:/tmp/Vare.csv'
FIELDS ENCLOSED BY ''
TERMINATED BY ';'
ESCAPED BY '"'
LINES TERMINATED BY '\r\n';
(SELECT 'VNr', 'Dato', 'GammelPris')
UNION
SELECT * FROM Prishistorikk
INTO OUTFILE 'C:/tmp/Prishistorikk.csv'
FIELDS ENCLOSED BY ''
TERMINATED BY ';'
ESCAPED BY '"'
LINES TERMINATED BY '\r\n';
(SELECT 'OrdreNr', 'OrdreDato', 'SendtDato', 'BetaltDato', 'KNr')
UNION
SELECT * FROM Ordre
INTO OUTFILE 'C:/tmp/Ordre.csv'
FIELDS ENCLOSED BY ''
TERMINATED BY ';'
ESCAPED BY '"'
LINES TERMINATED BY '\r\n';
(SELECT 'OrdreNr', 'VNr', 'PrisPrEnhet', 'Antall')
UNION
SELECT * FROM Ordrelinje
INTO OUTFILE 'C:/tmp/Ordrelinje.csv'
FIELDS ENCLOSED BY ''
TERMINATED BY ';'
ESCAPED BY '"'
LINES TERMINATED BY '\r\n';
Kommandoene herfra og ut kjøres fra Neo4j.
Hver rad blir til en node med et attributt for hver kolonne, og hver tabell blir til en samling av noder som tilhører samme nodetype. Vi oppretter noder og attributter også for koblingstabeller og fremmednøkler i første omgang.
Starter med å flytte CSV-filene fra C:/tmp (se forrige steg) til en web-tjener. Erstatt URL-en www.xyz.no med URL-en til CSV-filene i følgende LOAD-kommandoer:
USING PERIODIC COMMIT
LOAD CSV WITH HEADERS FROM "https://www.xyz.no/Poststed.csv" AS row
FIELDTERMINATOR ';'
CREATE (:Poststed {postNr: row.PostNr, poststed: row.Poststed});
USING PERIODIC COMMIT
LOAD CSV WITH HEADERS FROM "https://www.xyz.no/Ansatt.csv" AS row
FIELDTERMINATOR ';'
CREATE (:Ansatt {ansNr: row.AnsNr, fornavn: row.Fornavn, etternavn: row.Etternavn, adresse: row.Adresse, postNr: row.PostNr, fødselsdato: row.Fødselsdato, kjønn: row.Kjønn, stilling: row.Stilling, årslønn: row.Årslønn});
USING PERIODIC COMMIT
LOAD CSV WITH HEADERS FROM "https://www.xyz.no/Kunde.csv" AS row
FIELDTERMINATOR ';'
CREATE (:Kunde {kNr: row.KNr, fornavn: row.Fornavn, etternavn: row.Etternavn, adresse: row.Adresse, postNr: row.PostNr});
USING PERIODIC COMMIT
LOAD CSV WITH HEADERS FROM "https://www.xyz.no/Kategori.csv" AS row
FIELDTERMINATOR ';'
CREATE (:Kategori {katNr: row.KatNr, navn: row.Navn});
USING PERIODIC COMMIT
LOAD CSV WITH HEADERS FROM "https://www.xyz.no/Vare.csv" AS row
FIELDTERMINATOR ';'
CREATE (:Vare {vNr: row.VNr, betegnelse: row.Betegnelse, pris: row.Pris, katNr: row.KatNr, antall: row.Antall, hylle: row.Hylle});
USING PERIODIC COMMIT
LOAD CSV WITH HEADERS FROM "https://www.xyz.no/Prishistorikk.csv" AS row
FIELDTERMINATOR ';'
CREATE (:Prishistorikk {vNr: row.VNr, dato: row.Dato, gammelPris: row.GammelPris});
USING PERIODIC COMMIT
LOAD CSV WITH HEADERS FROM "https://www.xyz.no/Ordre.csv" AS row
FIELDTERMINATOR ';'
CREATE (:Ordre {ordreNr: row.OrdreNr, ordreDato: row.OrdreDato, sendtDato: row.SendtDato, betaltDato: row.BetaltDato, kNr: row.KNr});
USING PERIODIC COMMIT
LOAD CSV WITH HEADERS FROM "https://www.xyz.no/Ordrelinje.csv" AS row
FIELDTERMINATOR ';'
CREATE (:Ordrelinje {ordreNr: row.OrdreNr, vNr: row.VNr, prisPrEnhet: row.PrisPrEnhet, antall: row.Antall});
CREATE CONSTRAINT ON (p:Poststed) ASSERT (p.postNr) IS NODE KEY;
CREATE CONSTRAINT ON (a:Ansatt) ASSERT (a.ansNr) IS NODE KEY;
CREATE CONSTRAINT ON (k:Kunde) ASSERT (k.kNr) IS NODE KEY;
CREATE CONSTRAINT ON (k:Kategori) ASSERT (k.katNr) IS NODE KEY;
CREATE CONSTRAINT ON (v:Vare) ASSERT (v.vNr) IS NODE KEY;
CREATE CONSTRAINT ON (o:Ordre) ASSERT (o.ordreNr) IS NODE KEY;
Prishistorikk og Ordrelinje er svake, med sammensatte og (delvis) arvede primærnøkler – som også er fremmednøkler i en relasjonsdatabase. Vi kunne laget sammensatte nodenøkler også for disse, slik:
CREATE CONSTRAINT ON (ph:Prishistorikk) ASSERT (ph.vNr, ph.dato) IS NODE KEY;
CREATE CONSTRAINT ON (ol:Ordrelinje) ASSERT (ol.ordreNr, ol.vNr) IS NODE KEY;
Men de arvede "fremmednøklene" vil etter hvert bli erstattet av en kant – og det ser ikke ut til at man kan opprette sammensatte nodenøkler der en av "komponentene" er en kant.
CREATE INDEX ON :Ansatt(postNr);
CREATE INDEX ON :Kunde(postNr);
CREATE INDEX ON :Vare(katNr);
CREATE INDEX ON :Prishistorikk(vNr);
CREATE INDEX ON :Ordre(kNr);
CREATE INDEX ON :Ordrelinje(ordreNr);
CREATE INDEX ON :Ordrelinje(vNr);
MATCH (a:Ansatt),(p:Poststed)
WHERE a.postNr = p.postNr
CREATE (a)-[:BOSTED]->(p);
MATCH (k:Kunde),(p:Poststed)
WHERE k.kNr = p.postNr
CREATE (k)-[:BOSTED]->(p);
MATCH (v:Vare),(k:Kategori)
WHERE v.katNr = k.katNr
CREATE (v)-[:TILHØRER]->(k);
MATCH (p:Prishistorikk),(v:Vare)
WHERE p.vNr = v.vNr
CREATE (p)-[:FOR]->(v);
MATCH (o:Ordre),(k:Kunde)
WHERE o.kNr = k.kNr
CREATE (o)-[:TILHØRER]->(k);
MATCH (ol:Ordrelinje),(o:Ordre)
WHERE ol.ordreNr = o.ordreNr
CREATE (ol)-[:TILHØRER]->(o);
MATCH (ol:Ordrelinje),(v:Vare)
WHERE ol.vNr = v.vNr
CREATE (ol)-[:GJELDER]->(v);
Oppretter først mange-til-mange kanter med egenskaper:
MATCH (v:Vare)<-[r1:GJELDER]-(ol:Ordrelinje)-[r2:TILHØRER]->(o:Ordre)
CREATE (o)-[:DETALJER {prisPrEnhet: ol.prisPrEnhet, antall: ol.antall}]->(v);
Fjerner indeksene på fremmednøklene:
DROP INDEX ON :Ordrelinje(ordreNr);
DROP INDEX ON :Ordrelinje(vNr);
Fjerner "koblingstabellen" med tilhørende kanter:
MATCH (ol:Ordrelinje)
DETACH DELETE ol;
DROP INDEX ON :Ansatt(postNr);
DROP INDEX ON :Kunde(postNr);
DROP INDEX ON :Vare(katNr);
DROP INDEX ON :Prishistorikk(vNr);
DROP INDEX ON :Ordre(kNr);
Så selve "fremmednøklene" (dette er altså attributter som er erstattet av kanter):
MATCH (a:Ansatt)
REMOVE a.postNr;
MATCH (k:Kunde)
REMOVE k.postNr;
MATCH (v:Vare)
REMOVE v.katNr;
MATCH (o:Ordre)
REMOVE o.kNr;
I relasjonsdatabasen har Prishistorikk et identifiserende forhold mot Vare med en naturlig (delvis) arvet primærnøkkel – vNr+dato. Hvis vi har opprettet en sammensatt nodenøkkel (se steg 3), kunne vi ikke fjernet egenskapen vNr fordi den ville vært med i den sammensatte nodenøkkelen. Men hvis vi ikke har opprettet en slik nodenøkkel, kan den fjernes slik:
MATCH (p:Prishistorikk)
REMOVE p.vNr;
Hele tabellen Ordrelinje er fjernet i steg 6, men vi kunne alternativt ha fjernet "fremmednøklene" på denne måten:
MATCH (ol:Ordrelinje)
REMOVE ol.ordreNr;
MATCH (ol:Ordrelinje)
REMOVE ol.vNr;
Vis databaseskjemaet:
CALL db.schema();
Noen eksempelspørringer som sjekker at vi har fått med oss data:
MATCH (m)-[r]->(n)
RETURN *;
MATCH (a:Ansatt)-[:BOSTED]->(p:Poststed)
RETURN a.ansNr, a.etternavn, p.poststed;
MATCH (a:Ansatt)-[r:BOSTED]->(p:Poststed)
RETURN *;
Neste spørring er ikke testet, men skal eksportere (dumpe) de 100 første nodene (radene) av hver nodetype (tabell) til en fil på mappe c:\tmp.
call apoc.export.cypher.query("match (n)-[r]->(n2) return * limit 100",
"c:/tmp/hobbyhuset.cypher",
{format:'plain',separateFiles:true});