Head's Up! These forums are read-only. All users and content have migrated. Please join us at community.neo4j.com.
08-30-2020 02:53 AM
I'm totally new to neo4j, and I have data very similar to the example movie data, but I'm not sure if the same model works for my case.
I have Shows nodes and People nodes, with relationships such as acted in, directed in etc., but my issue is that there are many more roles in my data than the obvious ones. It's very likely that at some point more will be added beyond the scope of what I can think of. For example, one Person may be credited as assistant director and flower arranger.
To further complicate matters, all these roles have English and Chinese translations, which I do not want to duplicate. So I can create a relationship :DIRECTED_IN and give it properties
english: "director",
chinese: "導演"
But when I create another relationship of the type :DIRECTED_IN, it will not have these properties, so it will need to be translated again. If a translation is changed or corrected, it would have to be done for each :DIRECTED_IN relationship. Perhaps this is how it should be, but it seemed awkward to me.
My first thought was to do this:
(a: Person)-[:WORKED_AS]->(b:Role)-[:ON]->(c:Show)
But then I found myself having to make the roles separately for each person anyway so it wasn't really any better. Should I be dealing with translations in a different way?
Solved! Go to Solution.
09-08-2020 11:19 AM
You can't have objects stored in node-properties, so this isn't ok:
(a:Person {name:{zh:"李小龍"}})
...but you could do this:
(a:Person {name_zh:"李小龍"})
While that would make the data a little messy, with everything you've told me it's probably the best way to go. You could then keep the data and graph simple, while still supporting nodes without translations.
(p:Person {name_en:"Bob", name_zh:"鲍勃"})
(c:Credit {name_en:"Director", name_zh:"導演"})
(p)-[:CREDITED]->(c:Credit)-[:ON]->(s:Show)
(c)-[:ROLE]->(r:Role)
...
The problem you're going to run into is duplication, and MATCH
/MERGE
.
// Does the movie "Matrix" already exist in the DB?
MERGE (m:Movie {name_en: "Matrix"}) // what about zh? 矩阵?
MATCH (m:Movie) WHERE m.name_en = "Matrix" OR m.name_zh = "矩阵" // And if it hasn't been added yet?
If you can come up with an identity-scheme, or automatic pseudo-translation scheme, or maybe just use Google Translation API ($20/million characters), before you send stuff into the graph? Somewhere, at some point, you need fields that can be used as an identity, so you don't have to know if a node has been added or not. "How will you write your data-import into MERGE
commands, and have no CREATE
commands?" If you can manage that, you've got a solution.
08-30-2020 05:24 AM
I read some other posts about translations, and I am now considering this, with each person having unique Role nodes as described above.
(b:Role)-[:HAS_TRANSLATION]->(d:Translation {en: "Director", zh: "導演"})
Does this make more sense? Still a little unsure if I'm overcomplicating things.
08-31-2020 02:58 PM
There isn't really a "right" or "best" way to model your data. How to structure your data should only be informed by what that data represents, and how you are going to use it. One of the biggest things you should be thinking about, is what kind of queries you'll be running. Maybe even create a few ideal queries, that you'd want to be able to make, when the data is there. With those, consider which data patterns will be easier/faster to work with, to get the data you want.
IMO, the direction you're going is the right one, but there's a few semantic clarifications I would make.
(:Person)-[:WORKED_AS]->(:Role)
Role here isn't a singular role. In fact, you'll wind up with one of these, for every credit in a movie.Some possibilities:
(:Person)-[:CREDIT {role: 'director'}]->(:Show)
# You could then have *one big node* which maps `en` roles to other languages:
(:Lang { code: "zh", director: "導演", ... })
(:Person)-[:CREDITED_AS]->(c:Credit)-[:ON]->(c:Show),
(c)-[:ROLE]->(:Role)-[:TRANS]->(:Translation {en: 'Director'})
(:Person)-[:CREDITED_AS]->(c:Credit)-[:ON]->(c:Show),
(c)-[:ROLE]->(:Role)-[:TRANS {code: 'en']->(:Translation {code: 'en', value: 'Director'})->(:Lang {code: 'en', name: 'English'})
The real questions you need to carefully analyze:
Answers to those questions could help us better help you.
09-05-2020 10:24 PM
Thank you for all your help.
The second option you suggest about clarifying the semantics seems like a good choice for me. The first one would be difficult as data can come in either in Chinese or English only at first, so the role property of :CREDIT would be inconsistent.
You make a very good point about how the data will be used in the end.
Languages won't be mixed, so it should be possible to query for, let's say, all the shows an actor worked on - either by the actor's English or Chinese name, and return all the show names in that same language. I would like to be able to form queries using either English or Chinese to search by, without it being overly complicated to search for either. So being able to do something like MATCH (a:Person{name:{zh:"李小龍"}})-[:CREDIT {role: {zh:"演員"}]-(s:Show) RETURN s.title.zh
would be most convenient in terms of getting the data back, but like I said, that would mean the :CREDIT relationships will have to duplicate translations. I'm not even sure how I'd form the query if translations are done as in your third example, but I will do my best to work it out.
09-05-2020 10:50 PM
Sorry, I'll try and answer your questions more properly too:
Hope that's enough info - I realise perhaps there is not a 'best' way but I'm grateful for any advice because I really don't have any experience with this kind of database. Thanks.
09-07-2020 07:58 AM
So obviously I wrote this before actually testing any of my proposed ideas and ran into "Property values can only be of primitive types or arrays thereof". I realise I need some time to reevaluate what I'm doing and try things out with the actual data. I still think this is a good case for using a graph database, but I'm going to have to get a bit more familiar with it before importing this massive amount of data into it.
09-08-2020 11:19 AM
You can't have objects stored in node-properties, so this isn't ok:
(a:Person {name:{zh:"李小龍"}})
...but you could do this:
(a:Person {name_zh:"李小龍"})
While that would make the data a little messy, with everything you've told me it's probably the best way to go. You could then keep the data and graph simple, while still supporting nodes without translations.
(p:Person {name_en:"Bob", name_zh:"鲍勃"})
(c:Credit {name_en:"Director", name_zh:"導演"})
(p)-[:CREDITED]->(c:Credit)-[:ON]->(s:Show)
(c)-[:ROLE]->(r:Role)
...
The problem you're going to run into is duplication, and MATCH
/MERGE
.
// Does the movie "Matrix" already exist in the DB?
MERGE (m:Movie {name_en: "Matrix"}) // what about zh? 矩阵?
MATCH (m:Movie) WHERE m.name_en = "Matrix" OR m.name_zh = "矩阵" // And if it hasn't been added yet?
If you can come up with an identity-scheme, or automatic pseudo-translation scheme, or maybe just use Google Translation API ($20/million characters), before you send stuff into the graph? Somewhere, at some point, you need fields that can be used as an identity, so you don't have to know if a node has been added or not. "How will you write your data-import into MERGE
commands, and have no CREATE
commands?" If you can manage that, you've got a solution.
09-08-2020 11:50 PM
This is very helpful, thank you!
I did consider some kind of pseudo-translation to run on data before it goes into the database, and this would probably be possible with roles, but when it comes to people's names, or even show names, it will not be so easy.
At the moment I am working on the MERGE commands to import the data, and struggling a bit with if/else and dealing with all the possible cases in cypher. I do think it will be possible to make it work like this, but I will start a separate thread if I have any questions, since it's no longer about data modelling.
09-15-2020 04:41 PM
When modelling the airports in our airline network and routes between them, with localized properties on each of them, I found it easiest to have a Destination node that had all of the relationships with other Destinations, etc and then have one DestinationLabels node for each locale, with the relationship labeled by the locale.
This graph drives several of our guest-facing APIs. If I need localized labels for an airport, I can traverse the relationship based on the negotiated locale and the name is always name, etc. I can also use COALESCE() to get the localized value if it exists, but default to English if it hasn't been translated yet.
MERGE (d:Destination {code: "YYC"})
SET
d.timezone = "America/Edmonton",
d.cityCode = "YYC",
d.latitude = 51.131394,
d.longitude = -114.010551,
d.countryCode = "CA"
MERGE (d)-[:enCA])->(en:DestinationLabels)
SET
en.provinceStateName = "Alberta",
en.airportName = "Calgary International Airport",
en.name = "Calgary",
en.countryName = "Canada"
MERGE (d)-[:zhCN]->(zh:DestinationLabels)
SET
zh.provinceStateName = "艾伯塔省",
zh.airportName = "卡尔加里国际机场",
zh.name = "卡尔加里",
zh.countryName = "加拿大";
09-17-2020 10:26 PM
I like that. It's clean and clever. Only works if Ivor can define a PK (like your :Dest code).
@ivor, if you can find a way to define a language-agnostic identity for your nodes, use @dave.pirie's method. It'll save you headaches, with only one drawback:
Where you only have one translation, you'll see that one in the other lang. I.E.: if you only have ZH for a specific Director, you'll get the ZH value when you try to get EN.
09-17-2020 10:44 PM
Thanks a lot for this suggestion - it seems like a great way of working with language data. I'm not sure I can make it work for my data in this project, but I will definitely try it out.
09-22-2020 01:58 PM
@ivor, if you don't figure out a way to define an identity field for entries in your data, you're gonna have a bad time. (In the form of fragmentation and duplication)
09-22-2020 08:02 PM
Thank you for continuing to follow up and help me. I implemented your suggestion of using name_en and name_zh, and wrote the necessary logic to enter data with either/both, merging when necessary, and avoided any problems or duplicates with people. I've found that I can (and will have to due to the nature of the data) rely on the Chinese name string being unique for people. Unfortunately I still have some data that exists only in English at the moment. Of course I'm generating uuids when I enter data, but for importing the data initially, I accept that it's going to be more than a bit fiddly.
09-23-2020 08:46 AM
Honestly, that sounds like the best way to go about it! Nicely done.
It also means you should be able to combine the Dave's airport system with the one you have, to get clean and simple language/translation in the data.
You're almost there! Congrats!
All the sessions of the conference are now available online