How do I find multiple items in a field with mongodb? - php

I have the following type of occurrence in my Mongo documents.
"tags" : [ [ "narnia" ], [ "aslan" ], [ "winter" ], [ "heaven" ] ]
I need to know how to find this document by matching all of any number of the tags. I.e. Narnia AND Aslan (but not Narnia OR Aslan).
The query needs to be made with in PHP.
So far I only have it working for a single tag. I.e.
$filter['tags'] = array('$in' => array(array('Narnia')));

As Hussain mentioned in comments - you might want to revise that document structure as seems like you're storing unnecessary arrays.
Otherwise, what you're trying to do could be done with an $and statement (example without the nested arrays):
PRIMARY> db.wardrobe.find({ $and: [ { tags: "narnia" }, { tags: "tugboat" } ] })
//returns nothing
PRIMARY> db.wardrobe.find({ $and: [ { tags: "narnia" }, { tags: "winter" } ] })
//returns { "_id" : ObjectId("521067a48202463b88c2a0c9"), "tags" : [ "narnia", "aslan", "winter", "heaven" ] }
In PHP:
//With your nested arrays:
$filter = array(
'$and' => array(
array('tags' => array('narnia') ),
array('tags' => array('aslan') )
)
);
//With a simple array:
$filter = array(
'$and' => array(
array('tags' => 'narnia'),
array('tags' => 'aslan')
)
);
$mongoClient->selectCollection('cwlewis', 'wardrobe')->find( $filter );

Related

Query document's array fields in PHP MongoDB using filter and options

I am using the PHP MongoDB\Driver\Manager and I want to query by creating a MongoDB\Driver\Query.
So I have the following collection design:
{
"_comment": "Board",
"_id": "3",
"player": "42",
"moves": [{
"_id": "1",
"piece": "b3rw4",
"from_pos": "E2",
"to_pos": "E4"
}]
}
How can i query this collection to receive, for all boards of a specific player all moves with min(id)? This means I first want to filter all boards, to get only boards with player ID. Then I want to search all those board's "moves" fields, where I want the min(_id) of that "moves" field.
I currently have this query:
$filter = ['player' => '93'];
$options = [
'projection' => ['_id' => 0,
'moves' => 1]
];
$query = new MongoDB\Driver\Query($filter, $options);
This results in finding all "moves" arrays by Player 93.
How can I then filter all those "moves" fields by only getting the moves with min(_id)?
Ok, so I figured it out. I simply had to use an aggregation pipeline.
Here is the shell command which gives the expected output:
db.boards.aggregate( [
{
$match: {'player': '93'}
},
{
$unwind: {path: '$moves'}
},
{
$group:
{
_id: '$_id',
first_move: { $min: '$moves._id' },
from_pos : { $first: '$moves.from_pos' },
to_pos: { $first: '$moves.to_pos' }
}
}
])
Here is the corresponding PHP MongoDB code using Command and aggregate:
$command = new MongoDB\Driver\Command([
'aggregate' => 'boards',
'pipeline' => [
['$match' => ['player' => '93']],
['$unwind' => '$moves'],
['$group' => ['_id' => '$_id',
'firstMove' => ['$min' => '$moves._id'],
'from_pos' => ['$first' => '$moves.from_pos'],
'to_pos' => ['$first' => '$moves.to_pos']
]
]
],
'cursor' => new stdClass,
]);
$manager = new MongoDB\Driver\Manager($url);
$cursor = $manager->executeCommand('db', $command);

Parse Server Aggregation using PHP SDK

I'm trying to build an aggregation query in Parse's PHP SDK, and I'm stuck in the "lookup" area, I saw a JS example regarding this but it doesn't work in my case.
I have a table of users, which contains a "Tags" field of type Array, the array is actually an array of pointers, that point to a separate Tag class.
What I'm trying to achieve is to list most popular Tags based on their usage, so basically I need to query the users class and group the Tags that exist in the array, I already achieved this, but I'm stuck with the lookup part, the query currently returns an array of Tags pointers, what I want is to pull the object of those pointers.
Here's what I have currently:
$query = new ParseQuery('_User');
$pipeline = [
'project' => ['tags' => 1],
'unwind' => '$tags',
'group' => [
'objectId' => '$tags.objectId',
'count' => ['$sum' => 1]
],
'sort' => [ 'count' => -1],
'limit' => 10,
];
try {
return $query->aggregate($pipeline);
} catch (ParseException $ex) {
return $ex->getMessage();
}
And here's a snippet of what the _User collection looks like:
{
"_id" : "5BuBVo2GD0",
"email" : "test#test.com",
"username" : "test#test.com",
"lastname" : "Doe",
"firstname" : "John",
"_created_at" : ISODate("2017-01-23T09:20:11.483+0000"),
"_updated_at" : ISODate("2019-02-15T02:48:30.684+0000"),
"tags" : [
{
"__type" : "Pointer",
"className" : "Tag",
"objectId" : "St2gzaFnTr"
},
{
"__type" : "Pointer",
"className" : "Tag",
"objectId" : "LSVxAy2o74"
}
],
"_p_country" : "Country$4SE8J4HRBi",
}
And the Tag collection looks like this:
{
"_id" : "St2gzaFnTr",
"name" : "Music",
"_created_at" : ISODate("2018-10-22T20:00:10.481+0000"),
"_updated_at" : ISODate("2018-10-22T20:00:10.481+0000")
}
Any help would be appreciated!
Thanks in advance
Not sure if this is a direct answer, but here's a working aggregation on tags sorting for freq...
public function tagHistogram(Request $request, Response $response, array $args): Response {
$pipeline = [
'unwind' => '$tags' ,
'sortByCount' => '$tags',
'limit' => 1000,
];
$query = new ParseQuery('Product');
$result = $query->aggregate($pipeline);
$result = array_map(
function ($e) {
$e['name'] = $e['objectId'];
unset($e['objectId']);
return $e;
},
$result
);
return $response->withJson($result);
}

Return a subset of array where a given field is present

I would like to filter the Categories embedded Array to get only those which have a parent key.
{
"_id": ObjectId("5737283639533c000978ae71"),
"name": "Swiss",
"Categories": [
{
"name": "Management",
"_id": ObjectId("5738982e39533c00070f6a53")
},
{
"name": "Relations",
"_id": ObjectId("5738984a39533c000978ae72"),
"parent": ObjectId("5738982e39533c00070f6a53")
},
{
"name": "Ambiance",
"_id": ObjectId("57389bed39533c000b148164")
}
]
}
I've tried with the find but without success.
After some research it seems that it can be done via the aggregation command but I don't like the way it works, I would prefer to use only the find command.
Also, I'm asking myself if in term of performances it wouldn't be better to store each Categories in a new collection, would it be ?
Edit, I would like to get something like this as find output :
[
{
"name": "Relations",
"_id": ObjectId("5738984a39533c000978ae72"),
"parent": ObjectId("5738982e39533c00070f6a53")
}
]
The optimal way to do is in MongoDB 3.2 using the aggregation framework. All you need is project your documents and use the $filter operator to return a subset of the "Categories" array that match your criteria, but to do this you will need to use $ifNull operator give a "default" value to the "parent" field in all those sub-documents where that field is missing then use the $ne in your cond expression which determine where a give element should be included in the subset.
db.collection.aggregate([
{ "$project" : {
"_id": 0,
"Categories": {
"$filter": {
"input": "$Categories",
"as": "catg",
"cond": {
"$ne": [
{ "$ifNull": [ "$$catg.parent", false ] },
false
]
}
}
}
}}
])
From version 3.0 backwards, you need a different approach. Instead you need to use the $map operator to return a give element if it matches your criteria or false then use the $setDifference operator to filter out all those element in the returned array which are equal to false. Of course $setDifference is fine as long as the data being filtered is "unique".
db.collection.aggregate([
{ "$project" : {
"_id": 0,
"Categories": {
"$setDifference": [
{ "$map": {
"input": "$Categories",
"as": "catg",
"in": {
"$cond": [
{ "$ne": [
{ "$ifNull": [ "$$catg.parent", false ] },
false
]},
"$$catg",
false
]}
}
},
[ false ]
]
}
}}
])
Translation in PHP gives:
db.collection.aggregate(
array(
array("$project" => array(
"_id" => 0,
"Categories" => array(
"$filter" => array(
"input" => "$Categories",
"as" => "catg",
"cond" => array(
"$ne" => array(
array("$ifNull" => array("$$catg.parent", false),
false
)
)
)
)
))
)
)
And something this:
db.collection.aggregate(
array(
array("$project" => array(
"_id" => 0,
"Categories" => array(
"$setDifference" => array(
"$map" => array(
"input" => "$Categories",
"as" => "catg",
"in" => array(
"$cond" => array(
"$ne" => array(
array("$ifNull" => array( "$$catg.parent", false ) ),
false
),
"$$catg",
false
)
),
array(false)
)
)
))
)
)
As a solution according to above mentioned description please try executing following query
db.mycoll.find({Categories:{$elemMatch:{parent:{$exists:true}}}},
{Categories:{$elemMatch:{parent:{$exists:true}}}})
The above example uses $elemMatch operator to filter elements in an embedded document.

how to aggregate mongodb collection data in laravel

i have collection like this
{
"wl_total" : 380,
"player_id" : 1241,
"username" : "Robin",
"hand_id" : 292656,
"time" : 1429871584
}
{
"wl_total" : -400,
"player_id" : 1243,
"username" : "a",
"hand_id" : 292656,
"time" : 1429871584
}
as both collection have same hand_id i want to aggregate both these collection on the basis of hand_id
i want result as combine of
data=array(
'hand_id'=>292656,
'wl_total'=>
{
0=>380,
1=>-400
},
'username'=>
{
0=>"Robin",
1=>"a"
},
"time"=>1429871584
)
You basically want a $group by the "hand_id" common to all players, and then $push to different arrays in the document and then also do something with "time", I took $max. Nees to be an accumulator of some sort at any rate.
Also not sure what your underlying collection name is, but you can call this in laravel with a construct like this:
$result = DB::collection('collection_name')->raw(function($collection)
{
return $collection->aggregate(array(
array(
'$group' => array(
'_id' => '$hand_id',
'wl_total' => array(
'$push' => '$wl_total'
),
'username' => array(
'$push' => '$username'
),
'time' => array(
'$max' => '$time'
)
)
)
));
});
Which returns output ( shown in json ) like this:
{
"_id" : 292656,
"wl_total" : [
380,
-400
],
"username" : [
"Robin",
"a"
],
"time" : 1429871584
}
Personally I would have gone for a single array with all the infomation in it for the grouped "hand", but I supose you have your reasons why you want it this way.

Nested PHP arrays to json

So my code here:
$featurecollection = ("FeatureCollection");
$test[] = array (
"type" => $featurecollection,
$features[] = array($images)
);
file_put_contents($cache,json_encode($test));
results in the following json:
[
{
"type":"feature",
"0":[
[
{
"title":"some title",
"src":"value",
"lat":"value",
"lon":"value"
},
{
"title":"some title",
...
But I need to nest things differently and I'm perplexed on how the php array should be constructed in order to get a result like:
{
"type":"FeatureCollection",
"features":[
{
"type":"Feature",
"geometry":{
"coordinates":[
-94.34885,
39.35757
],
"type":"Point"
},
"properties":{
"latitude":39.35757,
"title":"Kearney",
"id":919,
"description":"I REALLY need new #converse, lol. I've had these for three years. So #destroyed ! :( Oh well. Can't wait to get a new pair and put my #rainbow laces through. #gay #gaypride #bi #proud #pride #colors #shoes #allstar #supporting ",
"longitude":-94.34885,
"user":"trena1echo5",
"image":"http://images.instagram.com/media/2011/09/09/ddeb9bb508c94f2b8ff848a2d2cd3ece_7.jpg",
"instagram_id":211443415
}
},
What would the php array look like for that? I'm thrown off by the way everything is nested but still has a key value.
Here's how I'd represent that in PHP:
array(
'type' => 'FeatureCollection',
'features' => array(
array(
'type' => 'Feature',
'geometry' => array(
'coordinates' => array(-94.34885, 39.35757),
'type' => 'Point'
), // geometry
'properties' => array(
// latitude, longitude, id etc.
) // properties
), // end of first feature
array( ... ), // etc.
) // features
)
So to get that structure, each feature has to be an associative array of:
type,
geometry - an associative array of:
coordinates - an indexed array of values,
type
properties - an associative array of values like latitude, longitude, id etc.
It's times like these when I prefer languages that distinguish between lists (array(1, 2, 3)) and dictionaries or maps (array('a' => 1, 'b' => 2)).
With PHP 5.4 and above:
$array = [
'type' => 'FeatureCollection',
'features' => [
[
'type' => 'Feature',
'geometry' => [
'coordinates' => [-94.34885, 39.35757],
'type' => 'Point'
], // geometry
'properties' => [
// latitude, longitude, id etc.
] // properties
], // end of first feature
[] // another feature, and so on
] // end of features
];
For the PHP script below:
<?php
header('Content-type=> application/json');
echo json_encode($array);
This is the JSON output;
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"coordinates": [
-94.34885,
39.35757
],
"type": "Point"
},
"properties": []
},
[]
]
}

Categories