0

I have the following doc definition (it's ruby)

class Block
  include Mongoid::Document

  field :index, type: Integer  # index 0,1,..  
  field :codes, type: Array    #[A, B, C,... ]

  embedded_in :Video
end

class Video
  include Mongoid::Document

  field :name, type: String
  embeds_many :blocks, :order => :index.asc
end

I want to query matching the property video.blocks.codes, but it is an array property of an embedded doc. I mainly want to do two types of queries:

  • How many blocks exist with a non-null/non-empty codes array?
  • How many blocks exist where the codes array matches a certain string in a given position?

Here's an example of the data I'm trying to match:

video#1
blocks: [{index: 1, codes:["a","g","c"]}, {index: 2, codes: [] }]

video#2
blocks: [{index: 1, codes:["x","b","d", "e"]}, {index: 2, codes: ["x","b"] }]

For example, I want to know how many blocks are there without non-empty codes array (answer is three blocks), and how many blocks are there with a b in the second position(index 1) (answer is there are two).

I'm using the mongoid driver so ideally the query would use the driver, but plain mongo is fine. Thanks!

2 Answers 2

1

I think what you are looking for is dot notation: http://docs.mongodb.org/manual/reference/glossary/#term-dot-notation

Question 1:

  • How many blocks exist with a non-null/non-empty codes array?
db.videos.find( { 'video.blocks.codes.0' : { $exists : true } } )

Effectively does the zero-th element of the array exist. For speed you create an index on video.blocks.codes. Also note that you will get back all of the video documents with at least 1 non-empty codes array within a block. To count the blocks you will have to do client side processing to remove the extra blocks.

Question 2:

  • How many blocks exist where the codes array matches a certain string in a given position?

Very similar answer. For a position 3:

db.videos.find( { 'video.blocks.codes.3' : 'the magic code' } )

Sorry I don't know Mongoid but hopefully you can translate the above.

HTH - Rob.

Edit:

This doesn't work, because blocks is embedded and codes is an array within blocks.

I don't think I understand the question then. The shell returns What I expect.

Example from the shell (reformatted) - First the data:

> db.test.find()
{ "_id" : ObjectId("51b7cfff0ccc6eb8b11c82b1"), 
  "blocks" : [    
     { "index" : 1, "codes" : [  "a", "g", "c" ] },
     { "index" : 2, "codes" : [ ] } 
  ] 
}
{ "_id" : ObjectId("51b7d0300ccc6eb8b11c82b2"), 
  "blocks" : [
     { "index" : 1, "codes" : [ "x", "b", "d", "e" ] },        
     { "index" : 2, "codes" : [ "x", "b" ] } 
  ] 
}
{ "_id" : ObjectId("51b7d0a50ccc6eb8b11c82b3"), 
  "blocks" : [ 
     { "index" : 1, "codes" : [ ] } 
  ] 
}

First Query: Find all documents with a block with at least 1 code:

> db.test.find( { 'blocks.codes.0' : { $exists : true } } )
{ "_id" : ObjectId("51b7cfff0ccc6eb8b11c82b1"), 
  "blocks" : [
     { "index" : 1, "codes" : [ "a", "g", "c" ] },
     { "index" : 2, "codes" : [ ] } 
   ] 
}
{ "_id" : ObjectId("51b7d0300ccc6eb8b11c82b2"), 
  "blocks" : [ 
     { "index" : 1, "codes" : [ "x", "b", "d", "e" ] },
     { "index" : 2, "codes" : [ "x", "b" ] } 
   ] 
}

Second Query: Find all documents where the n'th code is a specific value. In this case I chose the second (index 1) is 'g'.

> db.test.find( { 'blocks.codes.1' : "g" } )
{ "_id" : ObjectId("51b7cfff0ccc6eb8b11c82b1"), 
  "blocks" : [ 
     { "index" : 1, "codes" : [ "a", "g", "c" ] },
     { "index" : 2, "codes" : [ ] } 
  ] 
}
Sign up to request clarification or add additional context in comments.

2 Comments

This doesn't work, because blocks is embedded and codes is an array within blocks.
I updated to show the queries working as I expected from the shell. Can you update the question with what you expect the results of the queries to be?
0

IMHO Block should be a separate collection with an additional attribute num_codes (and not embedded, code untested).

class Video
    include Mongoid::Document
    has_many :blocks
end

class Block
    include Mongoid::Document
    belongs_to :video
    field :index
    field :num_codes
    field :codes

    # warning pseudo code ahead:
    before_save :update_codes
    def update_codes
       # set num_codes to length of codes
       # delete all codes belonging to this block and recreate them
    end
end

To query for empty blocks: Blocks.where(num_codes : 0). This solves requirement 1.

Regarding requirement 2: As far as I know, MongoDB does not allow you to query for values at a specific index within an array (altough I might be wrong on this one). Again my suggestion would be to make a separate collection (code untested):

class Code
   include Mongoid::Document
   belongs_to :block
   field :position
   field :value
end

Code.where(position : 3, value : 'x')

Saving a video will thus take about 2-n insertions depending on the size of codes. But the collections are indexable ([:num_codes] for Blocks and [:position, :value] for Code) and should give you reasonable query performance - even for large collections. Hope that helps.

3 Comments

I know I should have it in a separate collection, but I don't; I can't really change the schema right now. That's why I'm asking SO :). But thanks for your answer.
Are you unable to change the way you store things in MongoDB or only the schema of your input data? (or both?)
Well, then you're somewhat out of luck, I guess. :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.