Merge related entities using Multi Map/Reduce
A question came up in the mailing list regarding searching across related entities. In particular, the scenario is the notion of a player and characters in MMPROG game.
Here is what a Player document looks like:
{ "Id": "players/bella@dona.self", "Name": "Bella Dona", "Billing": [ { ... }, { ... }], "Adult": false, "LastLogin": "2015-03-11" }
And a player have multiple character documents:
{ "Id": "characters/1234", "Name": "Black Dona", "Player": "players/bella@dona.self", "Race": "DarkElf", "Level": 24, "XP": 283831, "HP": 438, "Skills": [ { ... } , { ... } ] } |
{ "Id": "characters/1321", "Name": "Blue Bell", "Player": "players/bella@dona.self", "Race": "Halfling", "Level": 2, "XP": 2831, "HP": 18, "Skills": [ { ... } , { ... } ] } |
{ "Id": "characters/1143", "Name": "Brown Barber", "Player": "players/bella@dona.self", "Race": "WoodElf", "Level": 44, "XP": 983831, "HP": 718, "Skills": [ { ... } , { ... } ] } |
And what we want is an output like this:
{ "Id" : "players/bella@dona.self", "Adult": false, "Characters" : [ { "Id": "characters/1234", "Name": "Black Dona" }, { "Id": "characters/1321", "Name": "Blue Bell" }, { "Id": "characters/1143", "Name": "Brown Barberl" }, ] }
Now, a really easy way to do that would be to issue two queries. One to find the player, and another to find its characters. That is actually the much preferred method to do this. But let us say that we need to do something that uses both documents types.
Give me all the players who aren’t adults that have a character over 40, for example. In order to do that, we are going to use a multi map reduce index to merge the two together. Here is how it is going to look like:
// map - Players from player in docs.Players select new { Player = player.Id, Adult = player.Adult, Characters = new object[0] } // map - Characters from character in docs.Characters select new { character.Player, Adult = false, Characters = new [] { new { character.Id, character.Name } } } // reduce from result in results group result by result.Player into g select new { Player = g.Key, Adult = g.Any(x=>x.Adult), Characters = g.SelectMany(x=>x.Characters) }
This gives you all the details, in a single place. And you can start working on queries from there.
Comments
Glad to see you're back posting, Thanks!
Ayende,
Can you explain why issuing two queries is the preferred approach?
Thks
Carlos, You make one query to find the relevant player, then you query its characters. The reason this is usually better is that your model specified that those are independent documents, so they are likely separate concepts. In the same way that searching for a person named Joe that graduated ivy league college is strange. You either know the specific college details, or you know the specific person. Searching over both details is strange, because it can find you unrelated details (two characters belong to different players) which is likely not what you wanted.
Comment preview