union two collections in mongodb with one query - php

I have one collection named USER
{"_id" => "id1", ...}
and another collection named CONTACT
{"_id" => "id2", ...}
now i have an array
[id1, id2]
Can I get two data with one query?

You can use the Aggregation Framework $setUnion operator for that.
db.collection.aggregate(
[
{ $project: { id1:1, id2: 1, allValues: { $setUnion: [ "$id1", "$id2" ] } } }
]
)

Related

MongoDB Aggregation "group" with "max" field within a sub-array

Using Compass initially, I then need to convert it into the PHP library.
So far, I have a 1st stage that filters the documents on 2 fields using $match:
comp.id (sub-document / array)
playerId
Code is:
$match (from drop-down)
{
"comp.id" : ObjectId('607019361c071256e4f0d0d5'),
"playerId" : "609d0993906429612483cea0"
}
This returns 2 documents.
The document has a sub-array holes, for the holes played in a round of golf. This sub-array has fields (among others):
holes.no
holes.par
holes.grossScore
holes.nettPoints
So each round has 1 document, with a holes sub-array of (typically) 18 array elements (holes), or 9 for half-round. A player will play each round multiple times - hence multiple documents.
I would like to find the highest holes.nettPoints across the documents. I think I need to $group with $max on the holes.nettPoints field, so I would find the highest score for each hole across all rounds.
I have tried this, but in Compass its says its not properly formatted:
$group drop-down
{
_id: holes.no,
"highest":
{ $max: "$holes.nettPoints" }
}
"highest" can be any name I want?
EDIT FOLLOWING PROVIDED ANSWER
The answer marked as the solution was enough of a pointer for how the Aggregation Framework operates (multi-stage documents, i.e. documents as input to 1 stage become new documents as the output of that stage. And so on.
For the purposes of posterity, I ended up using the following aggregation:
[{$match: {
"comp.id" : ObjectId('607019361c071256e4f0d0d5'),
"playerId" : "609d0993906429612483cea0",
"comp.courseId" : "608955aaabebbd503ba6e116"
}
}, {$unwind: {
path : "$holes"
}}, {$group: {
_id: "$holes.no",
hole: {
$max: "$holes"
}
}}, {$sort: {
"hole": 1
}}]
In PHP speak, it looks like:
$match = [
'$match' => [
'comp.id' => new MongoDB\BSON\ObjectID( $compId ),
'playerId' => $playerId,
'comp.courseId' => $courseId
]
];
$unwind = [
'$unwind' => [
'path' => '$holes'
]
];
$group = [
'$group' => [
'_id' => '$holes.no',
'hole' => [
'$max' => '$holes'
]
]
];
$sort = [
'$sort' => [
'hole.no' => 1
]
];
$cursor = $collection->aggregate([$match, $unwind, $group, $sort]);
It is not complete (looking at adding a $sum accumulator across the courseId, not individual documents), but answers the question posted.
$match your conditions
$unwind deconstruct holes array
$sort by nettPoints in descending order
$group by no and select first holes object
[
{
$match: {
"comp.id": ObjectId("607019361c071256e4f0d0d5"),
"playerId": "609d0993906429612483cea0"
}
},
{ $unwind: "$holes" },
{ $sort: { "holes.nettPoints": -1 } },
{
$group: {
_id: "$holes.no",
highest: { $first: "$holes" }
}
}
]

Can i add tags to a "deeper" key in an Elastic Search document?

i have products with tags, and tags are inside tagtypes.
this is a sample document that i added to the index
{
"_index" : "products",
"_type" : "_doc",
"_id" : "1219",
"_score" : 1.0,
"_source" : {
"id" : "1219",
"product_no" : "26426492261",
"merchant_id" : 11,
"name" : "Apple »Magic Keyboard für das 12,9\" iPad Pro (4. Generation)« iPad-Tastatur",
"category" : "Technik>Multimedia>Zubehör>Tastatur>iPad Tastatur",
"deep_link" : "https://foo",
"short_description" : null,
"long_description" : "Apple:",
"brand" : "Apple",
"merchant_image_url" : "http://something",
"tagtypes" : [
[
{
"Memory" : [ ]
}
]
]
}
},
That tagtype "Memory" is dynamically created while indexing the products.
I tried to add tags to that key
//attach tags also to ES
$params = [
'index' => 'products',
'id' => $product['_id'],
'body' => [
'script' => [
'source' => 'if (!ctx._source.tagtypes.'.$tagType->name.'.contains(params.tag)) { ctx._source.tagtypes.'.$tagType->name.'.add(params.tag) }',
'lang' => 'painless',
'params' => [
'tag' => $tag->value
]
]
]
];
But i receive an error like
{"error":{"root_cause":[{"type":"illegal_argument_exception","reason":"failed to execute script"}],"type":"illegal_argument_exception","reason":"failed to execute script","caused_by":{"type":"script_exception","reason":"runtime error","script_stack":["if (!ctx._source.tagtypes[\"Memory\"].contains(params.tag)) { "," ^---- HERE"],"script":"if (!ctx._source.tagtypes[\"Memory\"].contains(params.tag)) { ctx._source.tagtypes[\"Memory\"].add(params.tag) }","lang":"painless","position":{"offset":16,"start":0,"end":60},"caused_by":{"type":"wrong_method_type_exception","reason":"cannot convert MethodHandle(List,int)int to (Object,String)String"}}},"status":400}
Could anyone help me with that. I couldnt find any documentation about it, as the examples are often too basic.
Is it generally possible to save to "deeper keys" like this ?
Or can i just create "tags" as simple list (without any depth)
Thanks in advance
Adrian!
Your field tagtypes is an array of arrays of objects which themselves contain one-key arrays.
When you're dealing with such "deep" structures, you'll need some form of iteration to update them.
For loops are a good place start but they often lead to java.util.ConcurrentModificationExceptions. So it's easier to work with temporary copies of data and then replace the corresponding _source attribute when done with the iterations:
{
"query": {
"match_all": {}
},
"script": {
"source": """
if (ctx._source.tagtypes == null) { return; }
def originalTagtypes = ctx._source.tagtypes;
def newTagtypes = [];
for (outerGroup in originalTagtypes) {
// keep what we've got
newTagtypes.addAll(outerGroup);
// group already present?
def atLeastOneGroupContainsTag = outerGroup.stream().anyMatch(tagGroup -> tagGroup.containsKey(params.tag));
// if not, add it as a hashmap of one single empty list
if (!atLeastOneGroupContainsTag) {
Map m = new HashMap();
m.put(params.tag, []);
newTagtypes.add(m);
}
}
ctx._source.tagtypes = [newTagtypes];
""",
"lang": "painless",
"params": {
"tag": "CPU"
}
}
}
which'll end up updating the tagtypes like so:
{
...
"tagtypes" : [
[
{
"Memory" : [ ]
},
{
"CPU" : [ ] <---
}
]
],
...
}
You're right when you say that the documentation examples are too basic. Shameless plug: I recently published a handbook that aims to address exactly that. You'll find lots non-trivial scripting examples to gain a better understanding of the Painless scripting language.

PHP MongoDB Driver and Aggregations

I am at my first steps with mongoDB and php, trying to figure out how aggregations works. I have an approximate idea on how to use them from the command line but I am trying to translate this for the php driver. I am using the restaurants dexample DB, a list of records like this
{
"_id" : ObjectId("59a5211e107765480896f3f8"),
"address" : {
"building" : "284",
"coord" : [
-73.9829239,
40.6580753
],
"street" : "Prospect Park West",
"zipcode" : "11215"
},
"borough" : "Brooklyn",
"cuisine" : "American",
"grades" : [
{
"date" : ISODate("2014-11-19T00:00:00Z"),
"grade" : "A",
"score" : 11
},
{
"date" : ISODate("2013-11-14T00:00:00Z"),
"grade" : "A",
"score" : 2
},
{
"date" : ISODate("2012-12-05T00:00:00Z"),
"grade" : "A",
"score" : 13
},
{
"date" : ISODate("2012-05-17T00:00:00Z"),
"grade" : "A",
"score" : 11
}
],
"name" : "The Movable Feast",
"restaurant_id" : "40361606"
}
I just want to count how many restaurants for location, what I am doing is
$client = new MongoDB\Client("mongodb://localhost:27017");
$collection = $client->myNewDb->restaurants;
$results = $collection->aggregate(
[
'name' => '$name'
],
[
'$group' => [
'cuisine' => ['sum' => '$sum']
]
]
);
and I am getting this error
Fatal error: Uncaught exception 'MongoDB\Exception\InvalidArgumentException'
with message '$pipeline is not a list (unexpected index: "name")'
any idea? I can't find any good documentation on php.net.
thanks
M
Just take a look into documentation, and you will see, that the pipelines must be passed as an array.
The aggregate method accepts two parameters $pipelines and $options (public function aggregate(array $pipeline, array $options = [])).
Also as was mentioned before, the $group must have the _id element.
Groups documents by some specified expression and outputs to the next
stage a document for each distinct grouping. The output documents
contain an _id field which contains the distinct group by key. The
output documents can also contain computed fields that hold the values
of some accumulator expression grouped by the $group‘s _id field.
$group does not order its output documents.
https://docs.mongodb.com/manual/reference/operator/aggregation/group/
So your code must look like this:
$results = $collection->aggregate([
[
'$group' => [
'_id' => '$cuisine',
'sum' => ['$sum' => 1],
'names' => ['$push' => '$name']
]
]
]);
This code groups documents by cuisine element, counts the items and collects all name values into array.

select sum column and group by with Mongodb and Laravel

I have a issue with my query:
I want to
SELECT "user_info" with SUM "amount" and GROUP BY "user_id"
I am using Laravel 5 and jenssegers/laravel-mongodb
Thank you so much.
http://laravel.io/forum/10-05-2014-raw-select-using-jenssegers-laravel-mongodb
check link above or use this as i write.
$total = DB::table('user_info')->sum('amount')->groupBy('user_id')->get();
For better performance use the underlying MongoDB driver's aggregation framework methods as this uses the native code on the MongoDB server rather than the .groupBy() methods which basically wraps mapReduce methods.
Consider the following aggregation operation which uses the $group pipeline and the $sum operator to do the sum aggregation:
db.collectionName.aggregate([
{
"$group": {
"_id": "$user_id",
"user_info": { "$first": "$user_info" },
"total": { "$sum": "$amount" }
}
}
]);
The equivalent Laravel example implementation:
$postedJobs = DB::collection('collectionName')->raw(function($collection) {
return $collection->aggregate(array(
array(
"$group" => array(
"_id" => "$user_id",
"user_info" => array("$first" => "$user_info")
"total" => array("$sum" => "$amount")
)
)
));
});

laravel mongodb push element to existing array in document_

In my mongodb collection I want to push some elements to an existing array. I use jenssegers/Laravel-MongoDB - Eloquent model and Query builder to work with lavavel and mongodb.
How can I use the $push operator in jenssegers/Laravel-MongoDB?
MongoDB entry which shall be updated (RockMongo representation):
{
"_id": ObjectId("5328bc2627784fdb1a6cd398"),
"comments": {
"0": {
"id": 3,
"score": 8
}
},
"created_at": ISODate("2014-03-18T21:35:34.0Z"),
"file": {
"file_id": NumberLong(1175),
"timestamp": NumberLong(1395178534)
}
}
Hint about array representation in rockmongo and mongo shell
RockMongo and mongo shell array representation of the documents are a little bit different. Have a look at the comments-array. The above RockMongo representation appears in the mongo shell as:
{
"_id" : ObjectId("5328c33a27784f3b096cd39b"),
"comments" : [
{
"id" : 3,
"score" : 8
}
],
"created_at" : ISODate("2014-03-18T22:05:46Z"),
"file" : {
"file_id" : NumberLong(1176),
"timestamp" : NumberLong(1395180346)
}
}
As the documentation states the $push operater to push elements to an array. This works fine in the mongo-shell:
Mongo shell
db.Images.update({'file.file_id': 1175},
{ $push: { comments: { id: 3, score: 8} }
})
But in the query-builder I struggle to incorporate the $push operator. I get the error:
localhost:27017: Modified field name may not start with $
I did not find any documentation or example that showed me how to do it..
My jenssegers/Laravel-MongoDB code, that returns the error
// $file_id = 1175
public static function addComment( $file_id ) {
$image = Images::where( 'file.file_id', '=', floatval( $file_id ) )
->update( array('$push' => array( 'comments' => array( 'id' => 4, 'score' => 9 ) ) ) );
return $image;
}
Assuming everything is okay as it works in the shell then use the provided method to push instead:
Images::where('file.file_id', '=', floatval( $file_id ))
->push('comments', array( 'id' => 4, 'score' => 9 ));

Categories