How to apply correctly $limit and $skip in subfields? - mongodb

I'm starting with mongodb and I'm finding many difficulties with the following scheme.
{
"_id" : "AAA",
"events" : [
{
"event" : "001",
"time" : 1456823333
},
{
"event" : "002",
"time" : 1456828888
},
{
"event" : "003",
"time" : 1456825555
},...
]
}
I want to get the events sorted by date and apply limit and skip.
I'm using the following query:
$op = array(
array('$match' => array('_id' => $userId)),
array('$unwind' => '$events'),
array('$sort' => array('events.time' => -1)),
array('$group' => array('_id' => '$_id',
'events' => array('$push' => '$events')))
//,array('$project' => array('_id' => 1, 'events' => array('$events', 0, 3)))
//,array('$limit' => 4)
//,array('$skip' => 3)
);
$result= Mongo->aggregate('mycollection', $op);
I have tried everything to filter $project or $limit and $skip but none of it works.
How should I apply the limit and skyp conditions in events?
If I do not apply the conditions of "limit" above the result is ordered correctly.
Result:
{ "waitedMS":0,
"result":[
{
"_id":"AAA",
"events":[
{
"event":"002",
"time":1456828888,
},
{
"event":"003",
"time":1456825555,
},
{
"event":"001",
"time":1456823333,
},...
}
],
"ok":1
}
Order correctly but I can not limit the number of results for paging.

Related

MongoDb v4.2.1 aggregation - How to use join in php 7 code

I am working on PHP7 with MongoDB 4.2.
I am trying to Join tables using aggregation.
I am able to do on mongo console.
db.orders.aggregate([
{
$lookup: {
from: "items",
localField: "item", // field in the orders collection
foreignField: "item", // field in the items collection
as: "fromItems"
}
},
{
$replaceRoot: { newRoot: { $mergeObjects: [ { $arrayElemAt: [ "$fromItems", 0 ] }, "$$ROOT" ] } }
},
{ $project: { fromItems: 0 } }
])
and getting result as:
{ "_id" : 1, "item" : "almonds", "description" : "almond clusters", "instock" : 120, "price" : 12, "quantity" : 2 }
{ "_id" : 2, "item" : "pecans", "description" : "candied pecans", "instock" : 60, "price" : 20, "quantity" : 1 }
Same thing I want do in the PHP but not getting result:
My PHP code is:
<?php
public function demo_join() {
global $mng; // $mng = new MongoDB\Driver\Manager("mongodb://localhost:27017");
global $dbname;
$command = new MongoDB\Driver\Command([
'aggregate' => 'orders',
'pipeline' => [
['$lookup' => ["from" => "items","localField" => "items","foreignField" => "items","as" => "fromItems"]],
],
]);
//$cursor = $mng->executeCommand($dbname, $command);
try {
$cursor = $manager->executeCommand($dbname, $command);
} catch(MongoDB\Driver\Exception $e) {
echo $e->getMessage(), "\n";
exit;
}
return $cursor;
}
?>
and getting this error:
Uncaught Error: Call to a member function executeCommand() on null in $cursor = $manager->executeCommand($dbname, $command);
and some time
Uncaught MongoDB\Driver\Exception\CommandException: The 'cursor' option is required, except for aggregate with the explain argument in
Ok, I got the answer:
I added this line : 'cursor' => new stdClass
<?php
//mongo database join example
public function test_join() {
global $mng;
global $dbname;
$pipeline = [['$lookup' => ["from" => "items","localField" => "items","foreignField" => "items","as" => "fromItems"]]];
$aggregate = new \MongoDB\Driver\Command([
'aggregate' => 'orders',
'cursor' => new stdClass,
'pipeline' => $pipeline
]);
$cursor = $mng->executeCommand($dbname, $aggregate);
return $cursor;
}
?>
Now, I am able to get the result.

Fetch a value from an array in a MongoDB collection

Below is my MongoDB collection
{
"_id" : ObjectId("5b2a2ee19d332d0118b26dfe"),
"Name" : "Rock",
"Job" : [
{
"Id" : ObjectId("5b2b93c63629d0271ce366ae"),
"JobName" : "abc",
"JobTrack" : [
"123"
]
}
]
}
I want to fetch both ObjectId values
my $cursor = $custColl->find(
{ 'Job.JobName' => "abc", 'Job.JobTrack' => "123" },
{ '_id' => 1, 'Job.Id' => 1 }
);
while ( my $next = $cursor->next ) {
my $CustomerId = "$next->{_id}";
my $JobId = "$next->{'Job.Id'}";
say "$CustomerId => $JobId\n";
}
The result I got from above code as follows
5b2a2ee19d332d0118b26dfe =>
With this code I'm not able to get $JobId.
Assuming the query finds a document, $next is a Perl data structure that resembles your original JSON:
{
'Name' => 'Rock',
'_id' => bless( {
'value' => '5b2a2ee19d332d0118b26dfe'
}, 'MongoDB::OID' ),
'Job' => [
{
'JobName' => 'abc',
'JobTrack' => [
'123'
],
'Id' => bless( {
'value' => '5b2b93c63629d0271ce366ae'
}, 'MongoDB::OID' )
}
]
}
To get the job ID, you need to dereference that structure using Perl syntax, not MongoDB syntax:
my $JobId = "$next->{Job}[0]{Id}";
You need to access a specific element of the Job array. Because there is only one element in your example it must have an index of zero. Accordingly you need
my $JobId = "$next->{'Job.0.Id'}";

how to check two fields having same values in same table in laravel 5 with mongodb

I have to check the two fields having same values in same table in laravel 5. I am using Mongodb.
{
"id": "565d23ef5c2a4c9454355679",
"title": "Event1",
"summary": "test",
"total": NumberInt(87),
"remaining": NumberInt(87),
"status": "1"
}
I need to check "total" and "remaining" fields are same. How to write query in laravel 5.1. Please help.
One approach you could take would be using the aggregation framework methods from the raw MongoDB collection object provided from the underlying driver. In the mongo shell, you would essentially run the following aggregation pipeline operation to compare the two fields and return the documents which satisfy that criteria:
db.collection.aggregate([
{
"$project": {
"isMatch": { "$eq" : ["$total", "$remaining"] }, // similar to "valueof(total) == valueof(remaining)"
"id" : 1,
"title" : 1,
"summary" : 1,
"total" : 1,
"remaining" : 1,
"status" : 1
}
},
{
"$match": { "isMatch": true } // filter to get documents that only satisfy "valueof(total) == valueof(remaining)"
}
]);
Or using the $where operator in the find() query:
db.collection.find({ "$where" : "this.total == this.remaining" })
Thus in laravel, you can get the documents using raw expressions as follows
$result = DB::collection("collectionName") -> raw(function ($collection)
{
return $collection->aggregate(array(
array(
"$project" => array(
"id" => 1,
"title" => 1,
"summary" => 1,
"total" => 1,
"remaining" => 1,
"status" => 1,
"isMatch" => array(
"$eq" => array( "$total", "$remaining" )
)
)
),
array(
"$match" => array(
"isMatch" => true
)
)
));
});
In the case of $where, you can inject the expressions directly into the query:
Model::whereRaw(array("$where" => "this.total == this.remaining"))->get();
Or using the raw expression on the internal MongoCollection object executed on the query builder. Note that using the raw() method requires using a cursor because it is a low-level call:
$result = Model::raw()->find(array("$where" => "this.total == this.remaining"));
Collectionname::whereRaw(array('$where' => "this.filed1 > this.field2"))

MongoDB Aggregation Framework

I have a document that's structured as follows:
{
'_id' => 'Star Wars',
'count' => 1234,
'spelling' => [ ( 'Star wars' => 10, 'Star Wars' => 15, 'sTaR WaRs' => 5) ]
}
I would like to get the top N documents (by descending count), but with only one one spelling per document (the one with the highest value). It there a way to do this with the aggregation framework?
I can easily get the top 10 results (using $sort and $limit). But how do I get only one spelling per each?
So for example, if I have the following three records:
{
'_id' => 'star_wars',
'count' => 1234,
'spelling' => [ ( 'Star wars' => 10, 'Star Wars' => 15, 'sTaR WaRs' => 5) ]
}
{
'_id' => 'willow',
'count' => 2211,
'spelling' => [ ( 'willow' => 300, 'Willow' => 550) ]
}
{
'_id' => 'indiana_jones',
'count' => 12,
'spelling' => [ ( 'indiana Jones' => 10, 'Indiana Jones' => 25, 'indiana jones' => 5) ]
}
And I ask for the top 2 results, I'll get:
{
'_id' => 'willow',
'count' => 2211,
'spelling' => 'Willow'
}
{
'_id' => 'star_wars',
'count' => 1234,
'spelling' => 'Star Wars'
}
(or something to this effect)
Thanks!
Your schema as designed would make using anything but a MapReduce difficult as you've used the keys of the object as values. So, I adjusted your schema to better match with MongoDB's capabilities (in JSON format as well for this example):
{
'_id' : 'star_wars',
'count' : 1234,
'spellings' : [
{ spelling: 'Star wars', total: 10},
{ spelling: 'Star Wars', total : 15},
{ spelling: 'sTaR WaRs', total : 5} ]
}
Note that it's now an array of objects with a specific key name, spelling, and a value for the total (I didn't know what that number actually represented, so I've called it total in my examples).
On to the aggregation:
db.so.aggregate([
{ $unwind: '$spellings' },
{ $project: {
'spelling' : '$spellings.spelling',
'total': '$spellings.total',
'count': '$count'
}
},
{ $sort : { total : -1 } },
{ $group : { _id : '$_id',
count: { $first: '$count' },
largest : { $first : '$total' },
spelling : { $first: '$spelling' }
}
}
])
Unwind all of the data so the aggregation pipeline can access the various values of the array
Flatten the data to include the key aspects needed by the pipeline. In this case, the specific spelling, the total, and the count.
Sort on the total, so that the last grouping can use $first
Then, group so that only the $first value for each _id is returned, and then also return the count which because of the way it was flattened for the pipeline, each temporary document will contain the count field.
Results:
[
{
"_id" : "star_wars",
"count" : 1234,
"largest" : 15,
"spelling" : "Star Wars"
},
{
"_id" : "indiana_jones",
"count" : 12,
"largest" : 25,
"spelling" : "Indiana Jones"
},
{
"_id" : "willow",
"count" : 2211,
"largest" : 550,
"spelling" : "Willow"
}
]

MongoDB MapReduce Emit Strangeness

I'm very much a noob when it comes to MapReduce and I have been pulling my hair out with this issue. Hopefully someone can give me a hand.
My Goal: Get the product revenue and a count of the units sold.
Transactions Collection sample document where i'm querying from:
{ "_id" : ObjectId( "xxxxxxxxxx" ),
"MerchantID" : { "$ref" : "merchants",
"$id" : ObjectId( "xxxxxxxx" ) },
"TransactionSocialKey" : "xxxxxxxx",
"PurchaseComplete: true,
"Products" : [
{ "ProductID" : { "$ref" : "products",
"$id" : ObjectId( "4ecae2b9cf72ab1f6900xxx1" ) },
"ProductPrice" : 14.99,
"ProductQuantity" : "1" },
{ "ProductID" : { "$ref" : "products",
"$id" : ObjectId( "4ecae2b9cf72ab1f690xxx2" ) },
"ProductPrice" : 14.99,
"ProductQuantity" : "1" } ],
"DateTimeCreated" : Date( 1321919161000 ) }
As you can see I have an embedded array called Products with the ProductID, Product Price, and Product Quantity.
My Map Function
map = function(){
if(this.PurchaseComplete === true){
this.Products.forEach(function(Product){
if(Product.ProductID.$id.toString() == Product_ID.toString()){
emit(Product_ID, {
"ProductQuantity" : Product.ProductQuantity,
"ProductPrice" : Product.ProductPrice,
"ProductID" : Product.ProductID.$id.toString()
});
}
});
}
}
So with this i'm only going to emit Transactions that were completed. If the Transaction was completed I'm looping through the Products array and if the Product.ProductID.$id is equal to the Product_ID that I set in the MapReduce Scope then I'm going to emit the Product from the set.
For testing sake I've set up my Reduce function as:
reduce = function(key, Product_Transactions){
return {"Transactions" : Product_Transactions};
}
For some odd reason i'm getting this sort of result:
[results] => Array
(
[0] => Array
(
[_id] => MongoId Object
(
[$id] => 4ecae2b9cf72ab1f6900xxx1
)
[value] => Array
(
[Transactions] => Array
(
[0] => Array
(
[Transactions] => Array
(
[0] => Array
(
[ProductQuantity] => 1
[ProductPrice] => 14.99
[ProductID] => 4ecae2b9cf72ab1f6900xxx1
)
[1] => Array
(
[ProductQuantity] => 1
[ProductPrice] => 14.99
[ProductID] => 4ecae2b9cf72ab1f6900xxx1
)
It Continues…
)
)
[1] => Array
(
[ProductQuantity] => 1
[ProductPrice] => 12.74
[ProductID] => 4ecae2b9cf72ab1f6900xxx1
)
[2] => Array
(
[ProductQuantity] => 1
[ProductPrice] => 12.74
[ProductID] => 4ecae2b9cf72ab1f6900xxx1
)
)
)
)
)
I'm not sure why I'm getting this odd embedded array. The emit key is always the same and never changes. I'm really lost for ideas on where to start trouble shooting. Any help or guidance would be appreciated.
Output of map should be in the same format that reduce consumes and produces. The idea is that reduce may run in parallel and/or against partially reduced results.
Here's how your code should look like (pseudo-code)
var map = function() {
if(some condition) {
emit(product_id, {Transactions: [{ // <= note the array here!
"ProductQuantity" : Product.ProductQuantity,
"ProductPrice" : Product.ProductPrice,
"ProductID" : ID
}]})
}
};
var reduce = function(key, vals) {
var result = {Transactions: []};
vals.forEach(function(v) {
v.Transactions.forEach(t) {
result.Transactions.push(t);
}
});
return result;
}