MongoDB (PHP) - MapReduce - Connect Documents in different collections with the same ID - php

I have a collection named requests with documents following this prototype:
{
"_id": {
"$id": "56295368ef2b8e5458000029"
},
"title": "Hardcoded Name",
"requestorId": "dve34gegrgeefdsfewe"
}
The requestorId of those documents correlates with the id (not the _id Mongo Object ID) of the users collection, whose documents follow this prototype:
{
"_id": {
"$id": "86235288ef2gif5458000027"
},
"id": "dve34gegrgeefdsfewe",
"name": "John Doe"
}
I need to be able to run a find() on the requests collection and obtain the name associated with the requestorId in each request document. I would normally just do the obvious: run a find() on requests, then while looping through each document, do a subsequent query on users where id = [requestorId (really "id" in users collection)], BUT I need to be able to sort the entire requests collection by the name associated with each requestorId, then use skip() & limit(). This should be pretty simple but I can't seem to figure it out... Here's my code so far, inspired by https://www.noppanit.com/merge-documents-two-collections-together-mongodb/
$m = new MongoClient(); // connect
$db = $m->{"database"};
// construct map and reduce functions
$eventRequest_map = new MongoCode("function() {
emit(this.requestorId, {\"requestorId\" : this.requestorId})
}");
$user_map = new MongoCode("function() {
emit(this.id, {\"name\" : this.name})
}");
$reductionFunction = new MongoCode("function(key, values) {
var result = {
\"requestorId\" : \"\",
\"name\" : \"\"
};
return result;
}");
$sales = $db->command(array(
'mapreduce' => "requests",
'map' => $eventRequest_map,
'reduce' => $reductionFunction,
'query' => array(),
'out' => 'joined'));
$sales = $db->command(array(
'mapreduce' => "users",
'map' => $user_map,
'reduce' => $reductionFunction,
'query' => array(),
'out' => 'joined'));
$collection = $db->joined;
$cursor = $collection->find();
$outputJoined = array();
foreach($cursor as $doc){
$outputJoined[] = $doc;
}
echo json_encode($outputJoined);

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);
}

getting relational results from three tables into one nested array

i have googled for solution to my problem but nun helped me.
here i have three tables items, feeds and images. each item has one feed and one or more images.
i have 3 functions. one is to return records from items table the second one receives feeds_id (foreign key in items table) then return records from feeds table. the third function is to return all images related to items_id.
those functions are :
* To get all items in database:
function get_items(){
return $query = Database::getInstance('db')
->table('items')
->columns(
'id',
'items.rowid',
'items.feed_id as feed_id',
'title' )
->findAll();
}
* To get feed data from feeds table :
function get_feeds($id){
return $query = Database::getInstance('db')
->table('feeds')
->eq('id',$id)
->findAll();
}
* To get image data from images table :
function get_images($id){
return $query = Database::getInstance('db')
->table('images')
->columns('items_id','src as image_url',
'title as image_title',
'alt')
->eq('items_id',$id)
->findAll();
}
Then i have the following code to call those function and display the result in jsonformat:
$response['items'] = array();
$response['feeds'] = array();
$response['images'] = array();
foreach ($items = get_items() as $item) {
$response['items'][] = array(
'id' => (int)$item['rowid'],
'feed_id' => (int)$item['feed_id'],
'title' => $item['title'],
);
foreach ($feeds = get_feeds((int)$item['feed_id']) as $feed) {
$response['feeds'][] = array(
'title' => $feed['title'],
'logo_url' => $feed['logo_url'],
'site_url' => $feed['site_url'],
);
}
foreach ($images = get_images($item['id']) as $image) {
$response['images'][] = array(
'id' => $image['items_id'],
'url' => $image['image_url'],
'thumb' => $_SERVER['SERVER_NAME'] . /myServer/images/thumbs/'. 'thumb_'.basename($image['image_url']),
'title' => $image['image_title'],
'alt' => $image['alt']
);
}
}
echo json_encode($response, JSON_PRETTY_PRINT);
so, my expectation is to get json output like:
"items": [
{
"id": ,
"feed_id":
"title":
"feeds": [
{
"title": ,
"logo_url": ,
"site_url": "
}
]
"images": [
{
"id": ,
"url": ",
"thumb":
"title": "",
"alt": ""
},
{
....
}
]
}]
i mean each item array should include nested arrays of its related data coming from get_feeds and get_images functions.
instead of that, i get response like :
//here i select two items from my db
"items": [
{ //first_item
"id": ,
"feed_id":
"title":
},
{ //second_item
"id": ,
"feed_id":
"title":
}
],
"feeds": [
{ // feed data for first item
"title": ,
"logo_url": ,
"site_url": "
},
{ // feed data for second item
"title": ,
"logo_url": ,
"site_url": "
}
],
"images": [
{ // image data for first item
"id": ,
"url": ",
"thumb":
"title": "",
"alt": ""
},
{ // other images data
....
}
]
}]
as you see i am getting output without keeping relation between items, feeds and images, all of them are shown independently.
my queries are fine but i am suspecting error in my foreach statements.
i could fix this issue by joining those tree tables in one query, but i don't want to do that because i need to do validation and other operations to output comes from each table.
i appreciate your help
i found the solution. it is very easy :)
it is just like:
$response['items'][] = array(
'id' => (int)$item['rowid'],
'feed_id' => (int)$item['feed_id'],
'title' => $item['title'],
'feeds' => array(
)
'images' => array(
)
);

MongoDB using only map without reduce in PHP

In mongoDB i have two collection users and posts following this structure:
Posts
{
_id: ObjectId(""),
subject: "some post",
content: "here is the content",
user_id: "4351"
}
Users
{
user_id: "4351",
name: "John Marks",
picURL: "http://...",
aboutme: "historian of the future"
}
needing to get the posts in array with name.
db.posts.find().map(function(newPost){
newPost.name = db.users.findOne({user_id: newPost.user_id}).name;
return (newPost);
})
I wrote this code and it's work in mongoshell well returning this result:
{
_id: ObjectId(""),
subject: "some post",
content: "here is the content",
user_id: "4351",
name: "John Marks"
}
but i could not apply in php. You can't just simple get the output of the map function. It requires reduce function and output collection for the returning value.
Edit:
$map = new MongoCode('
function(newPost) {
newPost.username = db.users.findOne({user_id: newPost.user_id}).name;
return newPost;
}
');
post = $app->mongo->command(array(
"mapreduce" => "posts",
"map" => $map,
"reduce" => '',
"out" => array("inline" => 1)
));
var_dump($post);
This code must be work but accessing another collection in map function via 'db' is forbidden after mongo 2.4 release. That's why i changed my approach. Instead of using map/reduce, handled with php. Added posts user_ids to array and get the users information with following code.
$userInf = $app->mongo->selectCollection("users")->find(
array('user_id' => array('$in' => $user_ids)),
array("_id" => 0, "user_id" => 1, "name" => 1, "picURL" => 1)
);

Expire Documents at a Certain Clock Time in mongodb with php

I have this code inside my php:
$m = new MongoClient();
$db = $m->selectDB('authentication');
$collection = new MongoCollection($db, 'digits');
$document = array(
"username" => $_POST['username'],
"digits" => $_POST['digits']
);
$collection->insert($document);
I want these documents to be deleted after 2 hours automatically using ttl feature of mongodb.
there may be thousands of documents inserting every minute, so I don't want them to get messy or buggy, I want them to be deleted independently in the same collection.
If you can put the code in php I'd appreciate it. because everywhere else they just explained mongodb commands directly, which I couldn't understand how to use it in php. thanks.
Edit 1:
with the help of "Christian P", I created 30 documents for test:
for($i=0;$i<30;$i++){
$m = new MongoClient();
$db = $m->selectDB('authentication');
$collection = new MongoCollection($db, 'teeeest');
$collection->ensureIndex(array('createdAt' => 1, 'expireAfterSeconds' => 60));
$document = array(
"username" => "4563678678",
"digits" => "5958974",
"createdAt" => new MongoDate()
);
$collection->insert($document);
sleep(1);
}
but they are not being removed.
an example of created documents:
{
"_id": {
"$oid": "53ac7c237fae31100e000109"
},
"username": "4563678678",
"digits": "5958974",
"createdAt": {
"$date": "2014-06-26T20:01:39.000Z"
}
}
{
"_id": {
"$oid": "53ac7c247fae31100e00010a"
},
"username": "4563678678",
"digits": "5958974",
"createdAt": {
"$date": "2014-06-26T20:01:40.000Z"
}
}
{
"_id": {
"$oid": "53ac7c257fae31100e00010b"
},
"username": "4563678678",
"digits": "5958974",
"createdAt": {
"$date": "2014-06-26T20:01:41.000Z"
}
}
Edit 2:
as "Christian P" said in his edit, "expireAfterSeconds" should be passed as an array.
To automatically expire data from collection by setting TTL you must do two things:
Create a Date field.
Create a TTL index on that field.
To create a Date field in PHP you need to use MongoDate object:
$document = [
"username" => $_POST['username'],
"digits" => $_POST['digits'],
"createdAt" => new MongoDate()
];
You can add a TTL index using ensureIndex command, as other regular indexes.
$collection->ensureIndex(
['createdAt' => 1], ['expireAfterSeconds' => 7200]
);
The above command will add an index TTL index on the createdAt that will delete documents after 2 hours.

Categories