Best way to group by date with Mongoid
Here's an answer that uses the aggregation framework to group by date. I hope that you like it.
app/model/patient.rb
class Patient
include Mongoid::Document
field :name, type: String
def self.group_by(field, format = 'day')
key_op = [['year', '$year'], ['month', '$month'], ['day', '$dayOfMonth']]
key_op = key_op.take(1 + key_op.find_index { |key, op| format == key })
project_date_fields = Hash[*key_op.collect { |key, op| [key, {op => "$#{field}"}] }.flatten]
group_id_fields = Hash[*key_op.collect { |key, op| [key, "$#{key}"] }.flatten]
pipeline = [
{"$project" => {"name" => 1, field => 1}.merge(project_date_fields)},
{"$group" => {"_id" => group_id_fields, "count" => {"$sum" => 1}}},
{"$sort" => {"count" => -1}}
]
collection.aggregate(pipeline)
end
end
test/unit/patient_test.rb
require 'test_helper'
require 'pp'
class PatientTest < ActiveSupport::TestCase
def setup
Patient.delete_all
end
test "group by date" do
[
{"name" => "John", "created_at" => Date.new(2012, 10, 10).mongoize},
{"name" => "Jane", "created_at" => Date.new(2012, 10, 31).mongoize},
{"name" => "Mary", "created_at" => Date.new(2012, 10, 31).mongoize},
{"name" => "Mark", "created_at" => Date.new(2012, 12, 12).mongoize},
{"name" => "Alex", "created_at" => Date.new(2013, 11, 10).mongoize},
{"name" => "Andy", "created_at" => Date.new(2013, 10, 31).mongoize},
{"name" => "Toni", "created_at" => Date.new(2013, 10, 31).mongoize},
{"name" => "Cori", "created_at" => Date.new(2013, 11, 10).mongoize}
].each do |patient|
Patient.create(patient)
end
puts "\nMongoid::VERSION:#{Mongoid::VERSION}\nMoped::VERSION:#{Moped::VERSION}"
pp Patient.group_by('created_at', 'month')
end
end
$ rake test
Run options:
# Running tests:
[1/1] PatientTest#test_group_by_date
Mongoid::VERSION:3.1.5
Moped::VERSION:1.5.1
[{"_id"=>{"year"=>2012, "month"=>10}, "count"=>3},
{"_id"=>{"year"=>2013, "month"=>10}, "count"=>2},
{"_id"=>{"year"=>2013, "month"=>11}, "count"=>2},
{"_id"=>{"year"=>2012, "month"=>12}, "count"=>1}]
Finished tests in 0.042561s, 23.4957 tests/s, 0.0000 assertions/s.
1 tests, 0 assertions, 0 failures, 0 errors, 0 skips
Group by date in mongoDB while counting other fields
You can do like following, what have you done is excellent. After that,
- In second grouping, We just get total value and value of each recipe.
$map
is used to go through/modify each objects$arrayToObject
is used to covert the array what we have done via map (key : value pair) to object$ifNull
is used for, sometimes your data might not have "A" or "B" or "C". But you need the value should be 0 if there is no name as expected output.
Here is the code
[
{
"$project": {
"createdAt": {
"$dateToString": {
"format": "%Y-%m-%d",
"date": "$timestamp"
}
},
recipe: 1,
"progressivo": 1,
"temperatura_fusione": 1
}
},
{
"$group": {
"_id": {
"createdAt": "$createdAt",
"recipeName": "$recipe",
},
"products": {
$sum: 1
}
}
},
{
"$group": {
"_id": "$_id.createdAt",
value: {
$sum: "$products"
},
recipes: {
$push: {
name: "$_id.recipeName",
val: "$products"
}
}
}
},
{
$project: {
"content": {
"$arrayToObject": {
"$map": {
"input": "$recipes",
"as": "el",
"in": {
"k": "$$el.name",
"v": "$$el.val"
}
}
}
},
value: 1
}
},
{
$project: {
_id: 1,
value: 1,
A: {
$ifNull: [
"$content.A",
0
]
},
B: {
$ifNull: [
"$content.B",
0
]
},
C: {
$ifNull: [
"$content.C",
0
]
}
}
}
]
Working Mongo playground
MongoDB: Group by date and other value
You can change your query,
$group
byfloor_price
field and get max date by$max
$sort
by date in ascending order
let history = await History.aggregate([
{
$match: {
address: "123456",
token_to_index: "1"
}
},
{
$group: {
_id: "$stats.floor_price",
date: { $max: "$date" },
count: { $sum: 1 }
}
},
{ $sort: { "date": 1 } }
])
Playground
How to get and then group by date range -weekly- in MongoDB?
It seems you can use $week
and $group
. There is a simple solution without the group names:
db.collection.aggregate([
{
$match: {
createdBy: mongoose.Types.ObjectId(req.user.userId),
dateCreated: {
$gte: moment().startOf('week').week(-6).toDate(),
$lt: moment().startOf('week').toDate()
},
}
},
{
"$addFields": {
"week": {$week: "$dateCreated"}
}
},
{
$project: {data: "$$ROOT"}
},
{
$group: {
_id: "$data.week",
week: {$first:"$data.week"},
data: {$push: "$data"}
}
}
])
In order to get something similar to your expected results.
You can see how it works on the playground.
If it is important to name the groups 'weekOne', 'weekTwo', you can do it by adding a step, finding the week group name from a given list of group names:
db.collection.aggregate([
{
$match: {
createdBy: mongoose.Types.ObjectId(req.user.userId),
dateCreated: {
$gte: moment().startOf('week').startOf('week').toDate(),
$lt: moment().startOf('week').toDate()
},
}
},
{
"$addFields": {
"weekNum": {$week: "$dateCreated"},
"currentWeek": {$week: ISODate("2022-04-07T21:24:36.000Z")},
}
},
{
$project: {
data: "$$ROOT",
weeksOrder: [
"WeekOne",
"WeekTwo",
"WeekTree",
"WeekFour",
"WeekFive",
"WeekSix"
],
weekIndex: {$subtract: ["$currentWeek", "$weekNum"]
}
}
},
{
$project: {
data: 1,
weekGroup: {"$arrayElemAt": ["$weeksOrder", "$weekIndex"]}
}
},
{
$group: {
_id: "$weekGroup",
week: {$first: "$weekGroup"},
data: {$push: "$data"}
}
}
])
As you can see on the playground as well
I want to group by date, but how can I do it in mongodb?
$group
db.collection.aggregate([
{
"$group": {
"_id": {
year: {
$year: "$createdAt"
},
month: {
$month: "$createdAt"
},
day: {
$dayOfMonth: "$createdAt"
}
},
"Sales": {
"$sum": 1
},
"Own Sales Volume": {
"$sum": {
$cond: [
{
$and: [
{
$isArray: "$details"
},
{
$gt: [
{
"$size": "$details"
},
0
]
}
]
},
1,
0
]
}
}
}
}
])
mongoplayground
How to group by date with interval in Mongo
Your conditions are not fully clear, have a look at this outline:
connection disconnection
————————————|————————————————|——————————————> t
start + end
x x | | | NO
x | x | | YES/NO?
| x x | | YES
| x | x | YES/NO?
x | | x | YES/NO?
| | x x | NO
One approach is to generate intervals of 15 Minutes and then filter on these intervals. Could be this one:
db.collection.aggregate([
{
$group: {
_id: null,
data: { $push: "$$ROOT" },
// Determine total min. and max. time. Might be replaced by static values
min: { $min: "$connection" },
max: { $max: "$disconnection" }
}
},
{
$set: {
// Round min/max time to 15 Minute interval
min: { $dateTrunc: { date: "$min", unit: "minute", binSize: 15 } },
max: { $dateTrunc: { date: "$max", unit: "minute", binSize: 15 } }
}
},
{
$set: {
// Get number of 15-Minute intervals between min and max
steps: {
$dateDiff: {
startDate: "$min",
endDate: "$max",
unit: "minute"
}
}
}
},
{
$set: {
// Generate the 15-Minute intervals
intervals: {
$map: {
input: { $range: [0, "$steps", 15] },
as: "t",
in: {
start: {
$dateAdd: {
startDate: "$min",
unit: "minute",
amount: "$$t"
}
},
end: {
$dateAdd: {
startDate: "$min",
unit: "minute",
amount: { $add: ["$$t", 15] }
}
}
}
}
}
}
},
// Transpose array to documents
{ $unwind: "$intervals" },
// Just some cosmetic
{ $project: { data: 1, start: "$intervals.start", end: "$intervals.end" } },
{
$set: {
data: {
// Filter matching connections (based on outline above)
$filter: {
input: "$data",
cond: { $and: [{ $lte: ["$$this.connection", "$start"] }, { $gte: ["$$this.disconnection", "$end"] }] }
}
}
}
},
// Skip all 15-Minute intervals without any connection
{ $match: { data: { $ne: [] } } },
// Count users and some cosmetic
{
$project: {
start:1,
end: 1,
count: { $size: "$data" },
user: { $setUnion: "$data.user" }
}
}
])
Another approach could be $bucket
but would be difficult, because it works only with numeric values, i.e. you have to twist around Date
values.
Just a tip: Create a helper function
function between(ts, start, end) {
let ret = {};
if (typeof ts == "string") {
ret["$expr"] = { $and: [{ $lte: ["$" + start, "$" + ts] }, { $gte: ["$" + end, "$" + ts] }] };
} else {
ret[start] = { $lte: ts };
ret[end] = { $gte: ts };
}
return ret;
}
It corresponds to SQL BETWEEN Operator, i.e. WHERE ts BETWEEN start AND end
Then you can compose conditions like this
{ $match: between("start", "connection", "disconnection") }
{ $match: between(new Date(), "connection", "disconnection") }
which makes it much easier to write your query.
Related Topics
Mongoid: Find Through Array of Ids
What Does the Operator ||= Stand for in Ruby
How to Load Some Activerecord Models from a Yaml File and Save Them to the Db
How to Run Ruby 2.0 with Jruby 1.7
What Happens When Modifying Gemfile.Lock Directly
How to Check If There's a Nil Set or Not in an Array
Ruby $Stdin.Gets Without Showing Chars on Screen
Active Admin Scopes for Each Instance of a Related Model
How to Remove Repeated Spaces in a String
How to Ignore Multiline Comments in SASS
How to Reference a Lambda from Within Itself Using Ruby
Start or Ensure That Delayed Job Runs When an Application/Server Restarts
Rails Can't Login to Postgresql - Pg::Error - Password - Correct Info
Get Title, Content via Link in Rails