I have written mongodb aggregation query in php like below lines of code.
$orrollno= array('$or' => array(array("student.roll_no" => new MongoRegex("/$arg/i"))));
$orlastname= array('$or' => array(array("student.last_name" => new MongoRegex("/$arg/i"))));
$oremail= array('$or' => array(array("student.email" => new MongoRegex("/$arg/i"))));
$orguardian= array('$or' => array(array("student.guardian_name" => new MongoRegex("/$arg/i"))));
$orphone= array('$or' => array(array("student.phone1" => new MongoRegex("/$arg/i"))));
$orfullname= array('$or' => array(array("fullname" => new MongoRegex("/$arg/i"))));
$orfirstmiddle= array('$or' => array(array("firstmiddle" => new MongoRegex("/$arg/i"))));
$orfirstlast= array('$or' => array(array("firstlast" => new MongoRegex("/$arg/i"))));
$query = array( '$or' => array($orrollno,$orlastname,$oremail,$orguardian,$orphone,$orfullname,$orfirstmiddle,$orfirstlast));
$outputTotalResults= $this->db->studentTbl->aggregate(
array(
array(
'$project' => array(
'fullname' => array('$concat' => array('$first_name', ' ', '$middle_name', ' ', '$last_name')),
'firstmiddle' => array('$concat' => array('$first_name', ' ', '$middle_name')),
'firstlast' => array('$concat' => array('$first_name', ' ', '$last_name')),
'student' => '$$ROOT'
)
),
array(
'$match' => $query
),
)
);
I am trying to sort the results which comes from $match => $query.
For e.g $arg contains "William David" then results should first contain the records with names Willian David and then the rest of the results.
Any help shall be greatly appreciated!!!
Based on ur suggestion I have now tried the below
$outputTotalResults= $this->db->studentTbl->aggregate(
array(
array(
'$project' => array(
'fullname' => array('$concat' => array('$first_name', ' ', '$middle_name', ' ', '$last_name')),
'firstmiddle' => array('$concat' => array('$first_name', ' ', '$middle_name')),
'firstlast' => array('$concat' => array('$first_name', ' ', '$last_name')),
'student' => '$$ROOT',
'weight' => array(
'$cond' => array(
array(
'$or' => array(
array('$eq' => array('$fullname' => $arg )),
array('$eq' => array('$firstmiddle' => $arg)),
array('$eq' => array('$firstlast' => $arg)),
)
),
10,
0
)
),
array(
'$sort' => array( 'weight'=> -1 )
),
array(
'$match' => $query
),
)
)
)
);
What you want to achieve here is a "weighted sort", where you essentially want to calculate a field based on conditions and then apply a $sort pipeline stage to that result.
The general case is to apply $cond with a logical condition and either return a value or not, possibly for more than one condition in a cascading way.
Ideally with MongoDB 3.4 and above, use $addFields:
array(
array(
'$addFields' => array(
'weight' => array(
'$cond => array(
array(
'$and' => array(
array( '$eq' => array( '$first_name', 'Willam' ) )
array( '$eq' => array( '$last_name', 'David' ) )
)
),
10,
0
)
)
)
),
array(
'$sort' => array( 'weight'=> -1 )
)
)
Or in prior versions where you cannot simply "append" a new field to the existing document structure you use $project, either specifying each field you want or altering the structure returning under one property via $$ROOT:
array(
array(
'$project' => array(
'first_name' => 1,
'last_name' => 1,
'weight' => array(
'$cond => array(
array(
'$and' => array(
array( '$eq' => array( '$first_name', 'Willam' ) )
array( '$eq' => array( '$last_name', 'David' ) )
)
),
10,
0
)
)
)
),
array(
'$sort' => array( 'weight'=> -1 )
)
)
So in that simple case, whenever the "both" conditions (via $and) are met, the weight property is assigned a value of 10, otherwise it gets a 0. The subsequent sort on the weight property is in "descending" order, so all the 10 values where the condition matched will be on "top", whilst all other results will come after all the matches.
This is how you would structure for your exact implementation. First you $match your query conditions as this reduces the overall documents to process and this is the only time an aggregation pipeline can actually use an index.
Then you $project the field with the comparisons for whether the match phrase was in the preferred fields, and finally $sort on that calculated field.
array(
array( '$match' => $query ),
array(
'$addFields' => array(
'weight' => array(
'$cond => array(
array(
'$or' => array(
array(
'$eq' => array(
array('$concat' => array('$first_name', ' ', '$middle_name', ' ', '$last_name')),
$arg
)
),
array(
'$eq' => array(
array('$concat' => array('$first_name', ' ', '$middle_name')),
$arg
)
),
array(
'$eq' => array(
array('$concat' => array('$first_name', ' ', '$last_name')),
$arg
)
)
)
),
10,
0
)
)
)
),
array(
'$sort' => array( 'weight'=> -1 )
)
)
So always $match first or otherwise use a pipeline stage that is going to use an index and "optimize" your result. Then manipulate and remember that you cannot use calculated fields for comparison in a "single" $project phase. If you really need it then you either duplicate the calculations or do the calculations in one stage and then compare on the values in the next stage.
Honestly though, once you go to these lengths you are basically reproducing what a text search, in which you can:
Spread the index across all the fields you want to search in. This eliminates the massive $or condition into a simple query operation.
Specify a weighting on the particular fields where the match would be more important.
The only case where a "text search" would not be the best solution is if the fields you want "more weight on" change on a regular basis. Since text indexes have "set" values for the weighting and you can have only one per collection, then you cannot easily change the combination of fields to assign more weight to. With the aggregation process shown, its fairly trivial to change around the fields and assignment of weight.
Related
I have written mongo db query like
$outputTotalResults= $this->db->studentTbl->aggregate(
array(
array(
'$addFields' => array(
'weight' => array(
'$cond' => array(
array(
'$or' => array(
array(
'$eq' => array(
array('$concat' => array('$first_name', ' ', '$middle_name', ' ', '$last_name')),
$arg
)
),
array(
'$eq' => array(
array('$concat' => array('$first_name', ' ', '$middle_name')),
$arg
)
),
array(
'$eq' => array(
array('$concat' => array('$first_name', ' ', '$last_name')),
$arg
)
)
)
),
10,
0
)
)
)
),
array(
'$sort' => array( 'weight'=> -1 )
)
)
);
I am trying to sort the students. The above code is throwing error "Unrecognized pipeline stage name: '$addFields''"
Please help me !!!
Please check your mongodb version. This feature is not available on 3.2, it's been added in v3.4. Upgrade the db then.
$all = array(
'post_type' => array("pwpd_poll","pwpd_infog"),
'posts_per_page' => wp_is_mobile() ? 3 : 7,
'orderby' => 'post_date',
'order' => 'DESC'
);
$news = array(
'post_type' =>array('pwpd_news'),
'meta_query' => $meta_query_par,
'date_query'=>array(
array(
'year' => $today['year'],
'month' => $today['mon'],
'day' => $today['mday'],
)
)
);
the problem is the $news output is always in the end of the loop the "orderby" doesnt seem to be working and also the "posts_per_page" is not exact in the output.
Using ichikaway-cakephp I am trying to convert following query (running fine in php) to cakephp
In cakephp it returns empty array
Core PHP
$out = $collection->aggregate(array(
array('$unwind' => '$as'),
array(
'$group' => array(
'_id' => array('as'=>'$as'),
'count' => array('$sum' => 1)
)
)
));
Cakephp
$conditions=array('aggregate'=>array(
array('$unwind' => '$as'),
array(
'$group' => array(
'_id' => array('as'=>'$as'),
'count' => array('$sum' => 1)
)
)
));
$results = $this->Post->find('all',array('conditions'=>$conditions));
I am unable to find aggrgation framework function in test cases
So far only this commit talks about aggregation.
$params = array(
array('$unwind' => '$as'),
array(
'$group' => array(
'_id' => array('as'=>'$as'),
'count' => array('$sum' => 1)
)
)
));
$mongo = $this->Post->getDataSource();
$mongoCollectionObject = $mongo->getMongoCollection($this->Post);
$results = $mongoCollectionObject->aggregate($params);
I have some difficulty in getting Mongodb aggregate to work. It keeps giving me null. Please help. Below are the codes written in php. Thanks.
What I want to do is to sum up the values of 2 fields, Requests and Responses, between 2 particular dates
try {
$mongodb = new MongoClient("mongodb://ad:pass2word1#localhost");
$database = $mongodb->selectDB('backend');
$collection = new MongoCollection($database, 'RequestSummary');
$pipeline = array(
array(
'$group' => array(
'_id' => array(
'request' => array('$sum' => '$Requests'),
'response' => array('$sum' => '$Responses')
)
)
),
array(
'$match' => array(
'RequestDate' => array(
'$gte' => intval($_SESSION['range_from']),
'$lte' => intval($_SESSION['range_to'])
)
)
)
);
$collection->aggregate($pipeline);
var_dump($g);
} catch (MongoConnectionException $exc) {
echo $exc->getTraceAsString();
}
The _id of your $group can't contain aggregation operators like $sum. Those sums need to be defined as fields at the same level as _id. If you don't want to group on a specific field you can use NULL for the _id like this:
array(
'$group' => array(
'_id' => NULL,
'request' => array('$sum' => '$Requests'),
'response' => array('$sum' => '$Responses')
)
),
I am trying to use the aggregation function to display info in a chart. For this example, a document in the collection looks like this (excluding unnecessary fields for this query):
{
'locid' : <someid>, #Reference to a city & state collection
'collat' : <dateobj>, #a date object when this entry was saved
'pid' : <someid>, #Reference to a person collection
'pos' : <int> #Value I am interested in matching with location & date
}
So I basically start with a pid. I use this as my first $match parameter to limit the amount of data that gets thrown into the pipeline.
array(
'$match' => array(
'pid' => new \MongoId($pid)
)
),
So now that I have selected the correct pid, I tell it I only want/need certain fields:
array(
'$project' => array(
'pos' => 1,
'collat' => 1,
'locid' => 1
)
),
The second match is to say I only care about these locations right now ($ids contains an array of locid):
array(
'$match' => array(
'locid' => array('$in' => $ids)
)
),
And finally, I am saying group all the returned documents by collat and locid
array(
'$group' => array(
'_id' => array(
'locid' => '$locid',
'collat' => '$collat'
)
)
)
While the query completes OK and returns data, I am not getting the pos field back, it is only returning the locid and collat.
Questions
Isn't that what $project is for? I use it to tell the driver what fields I want returned?
Once I get the pos field returning as well, how can I tell the driver I only want the lowest value for each locid & collat combo pair? So say there are two entries for that date, location, and person: 4 & 8. I only care about pos=4
My end goal is to create a line chart with the X-Axis as the dates (from collat) and the Y-Axis will be the pos field, and each line will plot individual locid data.
Here is the entire parameters being sent to the aggregation driver.
$ops = array(
array(
'$match' => array(
'pid' => new \MongoId($pid)
)
),
array(
'$project' => array(
'pos' => 1,
'collat' => 1,
'locid' => 1
)
),
array(
'$match' => array(
'locid' => array('$in' => $ids)
)
),
array(
'$group' => array(
'_id' => array(
'locid' => '$locid',
'collat' => '$collat'
)
)
)
);
$out = $myCollection->aggregate($ops);
Update This is the way I got it to group & return pos without throwing an error. I need to spot check it though to make sure it's actually returning the correct values though.
array(
'$group' => array(
'_id' => array(
'locid' => '$locid',
'collat' => '$collat'
),
array('$min' => '$pos')
)
)
Aggregation query is like an SQL statement group by. You are telling {$group} what field(s) you want to 'GROUP BY' but you are not telling it how you want to aggregate the grouped information.
The {$group} you want is probably something like:
{$group : { _id : { locid: "$locid", collat: "$collat"},
pos : {$min : "$pos"}
}
}