loading...

Read-Only Views in MongoDB 3.4

damcosset profile image Damien Cosset Originally published at damiencosset.com ・3 min read

Introduction

MongoDB introduced read-only views in version 3.4. This article will briefly explore what they are, how you can use them and why you would want to use them.

What?

Read-only views are synthetic collections. They act a lot like a real collection in terms of reading from them, outside from the fact that they do not contain any documents. A view's state depends on a real collection, the source collection. Everytime you query a read-only view, you essentially aggregate the source collection. You can't write to a view (that's the read-only part!). If you update the source collection, it will propagate to your view (depending on how you define your view). Outside from creating a new document, (which we will see shortly after), views do not take up any space.

How?

An example might be more useful at this point. I will use the mongo shell to demonstrate a read-only view. Here is my database right now:

> use team
switched to db team

> db.players.find().pretty()
{
        "_id" : ObjectId("596b44d46879c5e83035f228"),
        "firstName" : "Joe",
        "lastName" : "Johnson",
        "position" : "forward",
        "number" : 9,
        "isMarried" : true
}
{
        "_id" : ObjectId("596b44d46879c5e83035f229"),
        "firstName" : "Alex",
        "lastName" : "Jones",
        "position" : "goalkeeper",
        "number" : 16,
        "isMarried" : false
}
{
        "_id" : ObjectId("596b44d46879c5e83035f22a"),
        "firstName" : "Ethan",
        "lastName" : "Hawk",
        "position" : "defender",
        "number" : 4,
        "isMarried" : false
}
{
        "_id" : ObjectId("596b44d46879c5e83035f22b"),
        "firstName" : "Richard",
        "lastName" : "Dawkins",
        "position" : "midfielder",
        "number" : 8,
        "isMarried" : true
}

I have a bunch of documents in my players collection. In each document is defined firstName, lastName, position, number and isMarried. Next, I will create a read-only view with db.createView(). It takes 3 arguments: the first is your view's name, the second is the source collection and the third is an array containing your aggregation pipeline. This is where you define how your view will manipulate the source collection.

> db.createView("playersInfos", "players", [ {$project : { "firstName": 1, "lastName": 1, "position": 1} } ] )
{ "ok" : 1 }

> show collections
players
playersInfos
system.views

> db.system.views.find()
{ "_id" : "team.playersInfos", "viewOn" : "players", "pipeline" : [ { "$project" : { "firstName" : 1, "lastName" : 1, "position" : 1 } } ] }

I named my view playersInfos. The source collection is players. In the third argument, I only use $project to return the firstName, lastName and position fields. When I look for collections in my database, I can see my playersInfos and system.views have been added. system.views keeps track of your views. Now, let's query our view. You do this like you would any other collection:

> db.playersInfos.find()
{ "_id" : ObjectId("596b44d46879c5e83035f228"), "firstName" : "Joe", "lastName" : "Johnson", "position" : "forward" }
{ "_id" : ObjectId("596b44d46879c5e83035f229"), "firstName" : "Alex", "lastName" : "Jones", "position" : "goalkeeper" }
{ "_id" : ObjectId("596b44d46879c5e83035f22a"), "firstName" : "Ethan", "lastName" : "Hawk", "position" : "defender" }
{ "_id" : ObjectId("596b44d46879c5e83035f22b"), "firstName" : "Richard", "lastName" : "Dawkins", "position" : "midfielder" }

I only get back what was defined in my aggregation pipeline when I created my view.

Now, I forgot one player in my team. I will add a new document to my players collection and query my view again. Remember that you can't write to a read-only view.

> db.players.insertOne({"firstName": "Rigoberto", "lastName": "Song", "position": "wingback", "number": 2, "isMarried": false})
{
        "acknowledged" : true,
        "insertedId" : ObjectId("596b47506879c5e83035f22c")
}
> db.playersInfos.find()
{ "_id" : ObjectId("596b44d46879c5e83035f228"), "firstName" : "Joe", "lastName" : "Johnson", "position" : "forward" }
{ "_id" : ObjectId("596b44d46879c5e83035f229"), "firstName" : "Alex", "lastName" : "Jones", "position" : "goalkeeper" }
{ "_id" : ObjectId("596b44d46879c5e83035f22a"), "firstName" : "Ethan", "lastName" : "Hawk", "position" : "defender" }
{ "_id" : ObjectId("596b44d46879c5e83035f22b"), "firstName" : "Richard", "lastName" : "Dawkins", "position" : "midfielder" }
{ "_id" : ObjectId("596b47506879c5e83035f22c"), "firstName" : "Rigoberto", "lastName" : "Song", "position" : "wingback" }

The document has been inserted in the source collection, and my view is able to query it right away.

Great! Now let's complicate things a little bit. I will delete my view and create it again. I will modify the aggregator pipeline.

> db.playersInfos.drop()
true

> db.createView("playersInfos", "players", [{$project : { "fullName": {$concat: ["$firstName", " ", "$lastName"]}, "position": 1}}])
{ "ok" : 1 }

> db.playersInfos.find()
{ "_id" : ObjectId("596b44d46879c5e83035f228"), "position" : "forward", "fullName" : "Joe Johnson" }
{ "_id" : ObjectId("596b44d46879c5e83035f229"), "position" : "goalkeeper", "fullName" : "Alex Jones" }
{ "_id" : ObjectId("596b44d46879c5e83035f22a"), "position" : "defender", "fullName" : "Ethan Hawk" }
{ "_id" : ObjectId("596b44d46879c5e83035f22b"), "position" : "midfielder", "fullName" : "Richard Dawkins" }
{ "_id" : ObjectId("596b47506879c5e83035f22c"), "position" : "wingback", "fullName" : "Rigoberto Song" }

This time, I don't want firstName and lastName in separate fields. I want only one field fullName. I used $concat in my third argument to create this.

Why?

Why would you want to use views? After all, we can already do all of this with regular collections. Two reasons:

  • Security

You can allow an application to query a view without exposing certain fields. If you store sensitive information in your collection, those informations can't be accessed from a view if it is defined correctly, even if those informations are in the source collection.

  • Simplicity

If you find yourself querying for the same informations over and over, you can define a view that will allow you to not specify the query criterias every single time you need to query a collection.

Warnings

You can't use MapReduce, $text, $, $slice, $elemMatch, $meta with a view.

Posted on by:

damcosset profile

Damien Cosset

@damcosset

French web developer mostly interested in Javascript and JAVA

Discussion

markdown guide
 

Is it faster to read data this way ?

 

I'm not sure but I think it doesn't impact the speed of the queries.