This is the third post in a small series about RavenDB – a document database in .NET. I will try to touch the same areas as I did in my series on MongoDB, possibly comparing the two where I see fit.
Now, this time we will take a look at some more querying…
Introduction
After having chewed a bit on the concept of map/reduce queries from doing my MongoDB series, I am actually beginning to see the beauty of this kind of query – and of course RavenDB supports them as well, because it is currently the only sane way to structure aggregate queries in distributed databases.
All of RavenDB’s queries are actually map/reduce queries, but if you don’t supply a reduction function, the reduction is trivial, as every document is emitted in its entirety.
Now, let’s try specifying our own reduction function….
Simple map/reduce query
But first we’ll start out by stuffing some data in the DB:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
using (var session = documentStore.OpenSession()) { session.Store(new Order { Items = { new Item {Name = "beer", Amount = 12}, new Item {Name = "peanuts", Amount = 3}, new Item {Name = "cashew nuts", Amount = 4}, } }); session.Store(new Order { Items = { new Item {Name = "beer", Amount = 6}, new Item {Name = "just nuts", Amount = 8}, new Item {Name = "peanuts", Amount = 6}, new Item {Name = "cashew nuts", Amount = 2}, } }); session.Store(new Order { Items = { new Item {Name = "beer", Amount = 5}, } }); session.SaveChanges(); } |
Now, in order to aggregate each Item by name, summing up the amounts, let’s construct the following index:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public class AggregateAmountsPerItem : AbstractIndexCreationTask { public override IndexDefinition CreateIndexDefinition() { return new IndexDefinition<Order, ItemAggregate> { Map = orders => from order in orders from item in order.Items select new {item.Name, item.Amount}, Reduce = items => from item in items group item by item.Name into i select new {Name = i.Key, Amount = i.Sum(x => x.Amount)} }.ToIndexDefinition(DocumentStore.Conventions); } } |
Note how this way of structuring the map/reduce differs from MongoDB and CouchDB where the map operation decides which value to aggregate on by emitting it as the key – in RavenDB, this decision is made in the reduce function.
Note also that I need to create a type, ItemAggregate, that is used to tell which fields the reduce function should expect as input. It is important that this type’s fields correspond to those emitted from the map function, or else the serialization will fail silently, yielding no results.
Now, let’s execute the index creation like so:
1 |
IndexCreation.CreateIndexes(typeof (AggregateAmountsPerItem).Assembly, documentStore); |
and now I am ready to query the index:
1 2 3 4 5 6 7 8 9 |
using(var session = documentStore.OpenSession()) { var amountsPerItem = session.Query<ItemAggregate>(typeof(AggregateAmountsPerItem).Name); foreach(var amountPerItem in amountsPerItem) { Console.WriteLine("{0}: {1} pcs", amountPerItem.Name, amountPerItem.Amount); } } |
which yields the following results:
1 2 3 4 5 6 |
Executing query '' on index 'AggregateAmountsPerItem' in 'http://localhost:8080' Query returned 4/4 results beer: 23 pcs peanuts: 9 pcs cashew nuts: 6 pcs just nuts: 8 pcs |
and that was pretty much what I expected.
That was a quick look at map/reduce queries in RavenDB. I’m sure there’s no end to the fun you can have with this kind of stuff, but I have yet to use map/reduce for anything in a real project, so I can’t really comment further on those.
“All of RavenDB’s queries are actually map/reduce queries, but if you don’t supply a reduction function, the reduction is trivial, as every document is emitted in its entirety.”
That’s not actually true, if you define a map function, only the fields you map are emitted.
Of course if you then query that map index you can only query the fields you have emitted, but RavenDB will return the whole document once it has matched it, unless you specify a projection of some sort.
Yeah, I guess I wasn’t entirely clear on that one either – I’m having a hard time explaining myself 🙂
What I actually meant to point out, was that – contrary to other map/reduce implementations – the map function is not responsible for projecting… So if no reduce function is specified, the result is documents.