How to query more fields on mongo DB aggregation query? - ruby-on-rails

I would like to know how to add an extra field to the response of collection.aggregate?
The query below groups activities by user_id. And I would like to know how to also include the user_name in the response.
DB Models
class Activity
include Mongoid::Document
field :hd_race_id, type: Float
field :metric_elapsed_time, type: Float
field :metric_distance, type: Float
field :user_name, type: String
belongs_to :user
...
class User
include Mongoid::Document
field :user_name, type: String
has_many :activities
...
Query
Activity.collection.aggregate([
{
"$group" => {
"_id" => "$user_id",
"distance" => { "$sum" => "$metric_distance" },
"time" => { "$sum" => "$metric_elapsed_time" },
},
},
{ "$sort" => { "distance" => -1 } },
])
Thank you in advance

Use the operator $first (aggregation accumulator) inside the $group stage.
For example:
"user_name": {"$first": "$user_name"}
or for the programming language you are using (not sure what it is), try something like:
"user_name" => {"$first" => "$user_name"},
For an example, see the "Group & Total" chapter in my Practical MongoDB Aggregations book

Related

How to order by field of embedded model in embeds_many mongoid rails application?

I have model Transactions that embeds many Events
class Transaction
embeds_many :events
end
Model Events has fields :name and :execute_at
class Event
field :name, type: String
field :execute_at, type: Date
embedded_in :transaction, inverse_of: :events
end
What I need to do is to sort Transactions by execute_at field of Events with specific name (lets say 'Name1'). Events are unique within each Transaction so there is no issue here.
Example:
{
amount: '123',
events: [
{
name: 'Name1',
execute_at: someday
},
{
name: 'Name5',
execute_at: someotherday
}
}
Transaction2
{
amount: '124',
events: [
{
name: 'Name1',
execute_at: someotherday
},
{
name: 'Name11',
execute_at: somerday
}
}
Basically sort these 2 transactions only taking data for sorting from events with name: 'Name1'
sorted_trans = Transaction.where('events.name' => 'Name1').order_by(:'events.execute_at'.asc)
or
sorted_trans = Transaction.where('events.name' => 'Name1').order_by(:'events.execute_at'.desc)

Mongoid - two query conditions to both be met in an embedded doc?

I have two models
class User
include Mongoid::Document
include Mongoid::Timestamps
field :username
embeds_many :user_tags
end
class UserTag
include Mongoid::Document
field :name
field :like_count, :type => Integer, :default => 0
embedded_in :user
end
I want to query all the users that have the user_tag named "nyc" and where the user_tag "nyc" has a like_count > 10. I've tried the following:
users = User.where('user_tags.name' => "nyc").and('user_tags.like_count' => {'$gte' => 10 })
Logically this does what it's supposed to do, but not what I need it to do. It returns users that have the user_tag "nyc" and have any user_tag with a like_count >= 10. I need users that have the user_tag "nyc" and where the user_tag "nyc"'s like_count is >= 10.
How do I do that? I'm running mongoid 4.0.2.
Actually your query is not correct for the purpose you are trying to achieve. It translates to the following MongoDB query:
db.users.find({'user_tags.name': 'nyc' }, {'user_tags.like_count': {$gte: 10}})
It means that MongoDB will find all documents with both criteria. Mongoid is returning you the same data, as MongoDB.
What you need instead is the following MongoDB query:
db.users.find({ user_tags: {
$elemMatch: {
name: 'nyc',
like_count: { $gte: 10 }
}
}})
With Mongoid you can write:
User.where(user_tags: {
'$elemMatch' => {
name: 'nyc',
like_count: { '$gte' => 10 }
}
}).count
Maybe you should write something like this:
users = User.where('user_tags.name' => "nyc", 'user_tags.like_count' => {'$gte' => 10 })
Mongoid will try to find Documents which satisfies both conditions.
You can try this
users = User.where('user_tags.name' => "nyc").where('user_tags.like_count' => {'$gte' => 10 }).all
or
users = User.where('user_tags.name' => "nyc", 'user_tags.like_count' => {'$gte' => 10 }).all

How to query embedded document in rails & mongoid

I have a class model, a student model and an attendance model. Attendance is embedded in Student to improve the performance.
I want to show number of all students in Class, number of present students, number of absent student & percentage of attendance. I am a newbie in Mongodb and i would appreciate any help. Thanks you for your time.
class Klass
include Mongoid::Document
include Mongoid::Timestamps
has_and_belongs_to_many :students
field :name, type: String
end
class Student
include Mongoid::Document
include Mongoid::Timestamps
has_and_belongs_to_many :klasses
embeds_many :attendances
field :name, type: String
end
class Attendance
include Mongoid::Document
include Mongoid::Timestamps
embedded_in :student
field :status, type: Integer # 1 = Present, 2 = Absent
field :klass_id, type: BSON::ObjectId
end
I have solved my problem by following technique.
#students_present_today = #class.students.where({ attendances: { '$elemMatch' => {status: 1, :created_at.gte => Date.today} } }).count
#students_absent_today = #class.students.where({ attendances: { '$elemMatch' => {status: 2, :created_at.gte => Date.today} } }).count
You can try these:
#class = Klass.where(name: 'something').first
#total_students = #class.students.count
#present_students = #class.students.where('attendances.status' => '1').count
#absent_students = #class.students.where('attendances.status' => '2').count
#p_s_today = #class.students.where('attendances.status' => '1', 'attendances.created_at' => {'$gte' => Date.today} ).count
#a_s_today = #class.students.where('attendances.status' => '2', 'attendances.created_at' => {'$gte' => Date.today} ).count

ElasticSearch with Tire doesn't include custom analyzer with STI model

I have an STI model which I want to be searchable with ElasticSearch and Tire. The issue I am having is when Tire creates the mappings it seems to ignore my custom analyzers for the second model. Below is an example of my models.
class Account < ActiveRecord::Base
attr_accessible :name, :type
include Tire::Model::Search
include Tire::Model::Callbacks
tire.settings :analysis => {
:analyzer => {
"custom_search_analyzer" => {
"tokenizer" => "keyword",
"filter" => "lowercase"
},
"custom_index_analyzer" => {
"tokenizer" => "keyword",
"filter" => ["lowercase","substring"]
}
},
:filter => {
:substring => {
"type" => "nGram",
"min_gram" => 1,
"max_gram" => 20
}
}
} do
mapping do
indexes :id, :type => 'integer', :include_in_all => false
indexes :name, :type => 'string', :search_analyzer => :custom_search_analyzer, :index_analyzer=>:custom_index_analyzer
end
end
def to_indexed_json
hash = {}
hash[:id] = id
hash[:name] = name
hash.to_json
end
end
class StandardAccount < Account
tire.index_name 'accounts'
end
class SuperAccount < Account
tire.index_name 'accounts'
end
When I create the index through tire, either with the rake task or through creating a model it creates the mappings but for the inherited models it doesn't apply the custom analyzers to them. If I look at the mappings using
curl -XGET 'http://127.0.0.1:9200/accounts/_mapping?pretty=1'
I get:
{
"accounts" : {
"account" : {
"properties" : {
"id" : {
"type" : "integer",
"include_in_all" : false
},
"name" : {
"type" : "string",
"index_analyzer" : "custom_index_analyzer",
"search_analyzer" : "custom_search_analyzer"
}
}
},
"standard_account" : {
"properties" : {
"id" : {
"type" : "long"
}
"name" : {
"type" : "string"
}
}
},
"super_account" : {
"properties" : {
"id" : {
"type" : "long"
}
"name" : {
"type" : "string"
}
}
}
}
}
Even if I move the mapping declarations to the inherited classes it seems to be only the first model created that picks up the extra options. I can manually create the indexes through ElasticSearch but was wondering if there was a way to do it with Tire? Or do I have something set up incorrectly
You might have figured out the answer already, but I think this might help you, or others having the same problem:
https://stackoverflow.com/a/13660263/1401343
-Vlad

Mongoid Group By or MongoDb group by in rails

I have a mongo table that has statistical data like the following....
course_id
status which is a string, played or completed
and timestamp information using Mongoid's Timestamping feature
so my class is as follows...
class Statistic
include Mongoid::Document
include Mongoid::Timestamps
include Mongoid::Paranoia
field :course_id, type: Integer
field :status, type: String # currently this is either play or complete
I want to get a daily count of total # of plays for a course. So for example...
8/1/12 had 2 plays, 8/2/12 had 6 plays. Etc. I would therefore be using the created_at timestamp field, with course_id and action. The issue is I don't see a group by method in Mongoid. I believe mongodb has one now, but I'm unsure of how that would be done in rails 3.
I could run through the table using each, and hack together some map or hash in rails with incrementation, but what if the course has 1 million views, retrieving and iterating over a million records could be messy. Is there a clean way to do this?
As mentioned in comments you can use map/reduce for this purpose. So you could define the following method in your model ( http://mongoid.org/en/mongoid/docs/querying.html#map_reduce )
def self.today
map = %Q{
function() {
emit(this.course_id, {count: 1})
}
}
reduce = %Q{
function(key, values) {
var result = {count: 0};
values.forEach(function(value) {
result.count += value.count;
});
return result;
}
}
self.where(:created_at.gt => Date.today, status: "played").
map_reduce(map, reduce).out(inline: true)
end
which would result in following result:
[{"_id"=>1.0, "value"=>{"count"=>2.0}}, {"_id"=>2.0, "value"=>{"count"=>1.0}}]
where _id is the course_id and count is the number of plays.
There is also dedicated group method in MongoDB but I am not sure how to get to the bare mongodb collection in Mongoid 3. I did not have a chance to dive into code that much yet.
You may wonder why I emit a document {count: 1} as it does not matter that much and I could have just emitted empty document or anything and then always add 1 to the result.count for every value. The thing is that reduce is not called if only one emit has been done for particular key (in my example course_id has been played only once) so it is better to emit documents in the same format as result.
Using Mongoid
stages = [{
"$group" => { "_id" => { "date_column_name"=>"$created_at" }},
"plays_count" => { "$sum" => 1 }
}]
#array_of_objects = ModelName.collection.aggregate(stages, {:allow_disk_use => true})
OR
stages = [{
"$group" => {
"_id" => {
"year" => { "$year" => "$created_at" },
"month" => { "$month" => "$created_at" },
"day" => { "$dayOfMonth" => "$created_at" }
}
},
"plays_count" => { "$sum" => 1 }
}]
#array_of_objects = ModelName.collection.aggregate(stages, {:allow_disk_use => true})
Follow the links below to group by using mongoid
https://taimoorchangaizpucitian.wordpress.com/2016/01/08/mongoid-group-by-query/
https://docs.mongodb.org/v3.0/reference/operator/aggregation/group/

Resources