I am using MongoDB library https://github.com/jenssegers/laravel-mongodb version 3.1.0-alpha in Laravel 5.3.28 I have two collections in MongoDB and I want to make a hasMany relation b/w them. Means each Employee performs many tasks. I have used reference and added employee_ids in the task collection.
Below are my code:
MongoDB:
1st Collection: Employee
{
"_id" : ObjectId("586ca8c71a72cb07a681566d"),
"employee_name" : "John",
"employee_description" : "test description",
"employee_email" : "john#email.com",
"updated_at" : "2017-01-04 11:45:20",
"created_at" : "2017-01-04 11:45:20"
},
{
"_id" : ObjectId("586ca8d31a72cb07a6815671"),
"employee_name" : "Carlos",
"employee_description" : "test description",
"employee_email" : "carlos#email.com",
"updated_at" : "2017-01-04 11:45:20",
"created_at" : "2017-01-04 11:45:20"
}
2nd Collection: Task
{
"_id" : ObjectId("586ccbcf1a72cb07a6815b04"),
"task_name" : "New Task",
"task_description" : "test description",
"task_status" : 1,
"task_start" : "2017-04-01 12:00:00",
"task_end" : "2017-04-01 02:00:00",
"task_created_at" : "2017-04-01 02:17:00",
"task_updated_at" : "2017-04-01 02:17:00",
"employee_id" : [
ObjectId("586ca8c71a72cb07a681566d"),
ObjectId("586ca8d31a72cb07a6815671")
]
},
{
"_id" : ObjectId("586cd3261a72cb07a6815c69"),
"task_name" : "2nd Task",
"task_description" : "test description",
"task_status" : 1,
"task_start" : "2017-04-01 12:00:00",
"task_end" : "2017-04-01 02:00:00",
"task_created_at" : "2017-04-01 02:17:00",
"task_updated_at" : "2017-04-01 02:17:00",
"employee_id" : ObjectId("586ca8c71a72cb07a681566d")
}
Laravel:
Model:
Employee:
<?php
namespace App\Models;
use Jenssegers\Mongodb\Eloquent\Model as Eloquent;
class Employee extends Eloquent {
protected $collection = 'employee';
protected $primaryKey = '_id';
public function tasks()
{
return $this->hasMany('App\Models\Task');
}
}
Laravel:
Model:
Task:
<?php
namespace App\Models;
use Jenssegers\Mongodb\Eloquent\Model as Eloquent;
class Task extends Eloquent {
protected $collection = 'task';
protected $primaryKey = '_id';
public function employees()
{
return $this->belongsTo('App\Models\Employee');
}
}
I want to get tasks assigned to the specific employee.
Controller:
public function EmployeeData($data)
{
$employees = Employee::with('tasks')->where('_id', new \MongoDB\BSON\ObjectID('586ca8d31a72cb07a6815671'))->get();
echo "<pre>";
print_r($employees);exit;
}
Output:
Illuminate\Database\Eloquent\Collection Object
(
[items:protected] => Array
(
[0] => App\Models\Employee Object
(
[connection:protected] => mongodb
[collection:protected] => lt_employees
[primaryKey:protected] => _id
[employee_id:App\Models\Employee:private] =>
[employee_name:App\Models\Employee:private] =>
[employee_description:App\Models\Employee:private] =>
[employee_email:App\Models\Employee:private] =>
[employee_created_at:App\Models\Employee:private] =>
[employee_updated_at:App\Models\Employee:private] =>
[parentRelation:protected] =>
[table:protected] =>
[keyType:protected] => int
[perPage:protected] => 15
[incrementing] => 1
[timestamps] => 1
[attributes:protected] => Array
(
[_id] => MongoDB\BSON\ObjectID Object
(
[oid] => 586ca8d31a72cb07a6815671
)
[employee_name] => Carlos
[employee_description] => test description
[employee_email] => carlos#email.com
[updated_at] => 2017-01-04 11:45:20
[created_at] => 2017-01-04 11:45:20
)
[original:protected] => Array
(
[_id] => MongoDB\BSON\ObjectID Object
(
[oid] => 586ca8d31a72cb07a6815671
)
[employee_name] => Carlos
[employee_description] => test description
[employee_email] => carlos#email.com
[updated_at] => 2017-01-04 11:45:20
[created_at] => 2017-01-04 11:45:20
)
[relations:protected] => Array
(
[tasks] => Illuminate\Database\Eloquent\Collection Object
(
[items:protected] => Array
(
)
)
)
[hidden:protected] => Array
(
)
[visible:protected] => Array
(
)
[appends:protected] => Array
(
)
[fillable:protected] => Array
(
)
[guarded:protected] => Array
(
[0] => *
)
[dates:protected] => Array
(
)
[dateFormat:protected] =>
[casts:protected] => Array
(
)
[touches:protected] => Array
(
)
[observables:protected] => Array
(
)
[with:protected] => Array
(
)
[exists] => 1
[wasRecentlyCreated] =>
)
)
)
In the output, relation tasks items are empty.
Can anyone suggest me that the relation b/w collections are correct?
Update
I have used belongsToManyin the relation. Now my models are:
In the Employee Model:
public function tasks()
{
return $this->belongsToMany('App\Models\Task');
}
In the Task Model:
public function employees()
{
return $this->belongsToMany('App\Models\Employee');
}
These are the documents:
Employee collection
{
"_id" : ObjectId("586ca8c71a72cb07a681566d"),
"employee_name" : "Carlos",
"employee_description" : "test description",
"employee_email" : "carlos#email.com",
"updated_at" : "2017-01-04 11:45:20",
"created_at" : "2017-01-04 11:45:20",
"task_ids" : [
ObjectId("586ccbcf1a72cb07a6815b04"),
ObjectId("586cd3261a72cb07a6815c69")
]
},
{
"_id" : ObjectId("586ca8d31a72cb07a6815671"),
"employee_name" : "John",
"employee_description" : "test description",
"employee_email" : "john#email.com",
"updated_at" : "2017-01-04 11:45:20",
"created_at" : "2017-01-04 11:45:20"
}
Task collection
{
"_id" : ObjectId("586ccbcf1a72cb07a6815b04"),
"task_name" : "New Task",
"task_description" : "test description",
"task_status" : 1,
"task_start" : "2017-04-01 12:00:00",
"task_end" : "2017-04-01 02:00:00",
"task_created_at" : "2017-04-01 02:17:00",
"task_updated_at" : "2017-04-01 02:17:00",
"employee_ids" : [
ObjectId("586ca8c71a72cb07a681566d"),
ObjectId("586ca8d31a72cb07a6815671")
]
},
{
"_id" : ObjectId("586cd3261a72cb07a6815c69"),
"task_name" : "2nd Task",
"task_description" : "test description",
"task_status" : 1,
"task_start" : "2017-04-01 12:00:00",
"task_end" : "2017-04-01 02:00:00",
"task_created_at" : "2017-04-01 02:17:00",
"task_updated_at" : "2017-04-01 02:17:00",
"employee_ids" : ObjectId("586ca8c71a72cb07a681566d")
}
I get the first employee with these documents:
$employee = Employee::with('tasks')->first();
dd($employee);
And I gotthe output with empty relation:
Employee {#176
#connection: "mongodb"
#collection: "employee"
#primaryKey: "_id"
-employee_id: null
-employee_name: null
-employee_description: null
-employee_email: null
-employee_created_at: null
-employee_updated_at: null
#parentRelation: null
#table: null
#keyType: "int"
#perPage: 15
+incrementing: true
+timestamps: true
#attributes: array:10 [
"_id" => ObjectID {#170}
"employee_name" => "Carlos"
"employee_description" => "test description"
"employee_email" => "carlos#email.com"
"updated_at" => "2017-01-04 11:45:20"
"created_at" => "2017-01-04 11:45:20"
"task_ids" => array:2 [
0 => ObjectID {#174}
1 => ObjectID {#175}
]
]
#original: array:10 [
"_id" => ObjectID {#170}
"employee_name" => "Carlos"
"employee_description" => "test description"
"employee_email" => "carlos#email.com"
"updated_at" => "2017-01-04 11:45:20"
"created_at" => "2017-01-04 11:45:20"
"task_ids" => array:2 [
0 => ObjectID {#174}
1 => ObjectID {#175}
]
]
#relations: array:1 [
"tasks" => Collection {#173
#items: []
}
]
#hidden: []
#visible: []
#appends: []
#fillable: []
#guarded: array:1 [
0 => "*"
]
#dates: []
#dateFormat: null
#casts: []
#touches: []
#observables: []
#with: []
+exists: true
+wasRecentlyCreated: false
}
I understood by your other question, that a task can belong to many employees, right? So you should be using belongsToMany relationship in your Task model. Also your example "task" collection shows that in one document employee_id is an array and in the other document it is an ObjectId, when both should be arrays.
Anyway, I've had a hard time trying to figure this out, but I've seen that you can't use hasMany as the inverse of belongsToMany, because belongsToMany creates an array of ids, and hasMany doesn't work well with arrays. I would say that we would need something like hasManyInArray, but when I associate a belongsToMany relationship, the "parent" document gets created an array of ids, which leads me to think that the parent should also use belongsToMany even though it doesn't "belong to" but actually "has". So when you would associate an employee to a task like this:
$task->employees()->save($employee);
The "employee" document will end up having a "task_ids" attribute with the only task id it should have. So that seems to be the way to go with Jenssegers: to use belongsToMany in both models:
Laravel: Model: Employee:
<?php
namespace App\Models;
use Jenssegers\Mongodb\Eloquent\Model as Eloquent;
class Employee extends Eloquent
{
protected $collection = 'employee';
public function tasks()
{
return $this->belongsToMany(Task::class);
}
}
Laravel: Model: Task:
<?php
namespace App\Models;
use Jenssegers\Mongodb\Eloquent\Model as Eloquent;
class Task extends Eloquent
{
protected $collection = 'task';
public function employees()
{
return $this->belongsToMany(Employee::class);
}
}
And you would use this like:
// Give a task a new employee
$task->employees()->save($employee);
// Or give an employee a new task
$employee->tasks()->save($task);
The only thing about this is that when you look at the database, you will see that your employee documents have an array called "task_ids", and inside it, the id of the only task each employee have. I hope this helped.
Just some side notes, you know that you don't have to define the name of the primary key on each model, right? You don't need this:
protected $primaryKey = '_id';
Also you don't have to define the name of the collection (i.e. protected $collection = 'employee';), unless you really want them to be in singular (by default they are in plural).
I got up in the middle of the night (it's 3:52AM here) and checked something on the computer and then checked SO an saw your question, I hope this time I answered soon enough for you, we seem to be in different timezones.
Update
These are the documents I created for testing:
employee collection
{
"_id" : ObjectId("5870ba1973b55b03d913ba54"),
"name" : "Jon",
"updated_at" : ISODate("2017-01-07T09:51:21.316Z"),
"created_at" : ISODate("2017-01-07T09:51:21.316Z"),
"task_ids" : [
"5870ba1973b55b03d913ba56"
]
},
{
"_id" : ObjectId("5870ba1973b55b03d913ba55"),
"name" : "Doe",
"updated_at" : ISODate("2017-01-07T09:51:21.317Z"),
"created_at" : ISODate("2017-01-07T09:51:21.317Z"),
"task_ids" : [
"5870ba1973b55b03d913ba56"
]
}
task collection
{
"_id" : ObjectId("5870ba1973b55b03d913ba56"),
"name" : "New Task",
"updated_at" : ISODate("2017-01-07T09:51:21.317Z"),
"created_at" : ISODate("2017-01-07T09:51:21.317Z"),
"employee_ids" : [
"5870ba1973b55b03d913ba54",
"5870ba1973b55b03d913ba55"
]
}
With these documents I get the first employee like this:
$employee = Employee::with('tasks')->first();
dd($employee);
And in the output we can see the relations attribute is an array:
Employee {#186 ▼
#collection: "employee"
#primaryKey: "_id"
// Etc.....
#relations: array:1 [▼
"tasks" => Collection {#199 ▼
#items: array:1 [▼
0 => Task {#198 ▼
#collection: "task"
#primaryKey: "_id"
// Etc....
#attributes: array:5 [▼
"_id" => ObjectID {#193}
"name" => "New Task"
"updated_at" => UTCDateTime {#195}
"created_at" => UTCDateTime {#197}
"employee_ids" => array:2 [▶]
]
}
]
}
]
}
Update 2
The belongsToMany method isn't in the file you mention because that class (i.e. Jenssegers\Mongodb\Eloquent\Model) extends Laravel's Eloquent Model class, and that's where the belongsToMany method is.
Ok so that must be why it's not working for you, because the arrays have to be strings instead of ObjectIds. Why is this? Because that's how the Jenssegers library work, it saves the Ids as strings. I've also found this behaviour strange, but that's how it works. Remember that you are supposed to relate objects using the Jenssegers library, not by creating the data manually in the database.
How can you index the ids? Just create a normal index in MongoDB, like tasks.createIndex({task_ids: 1}). Here's the documentation on how to create indexes: https://docs.mongodb.com/manual/reference/method/db.collection.createIndex/. You can also create indexes on migrations, here are the docs on migrations, make sure to read Jenssegers notes on migrations too.
You can access the tasks realtion like this: $employee->tasks;. You access relations by getting a property with the same name of the method you declared your relation with, so if you have:
class Post
{
public function owner()
{
return $this->belongsTo(User::class);
}
}
You get the relation as $post->owner;. Here's the documentation on relations: https://laravel.com/docs/5.3/eloquent-relationships
Related
Hellow folks, I am new to MongoDB and looking for some answer
Is there any way to update nested array without looping it.
foreach ($post->comments as $key => $comment) {
if ($comment['posted_by'] == $authUser['id']) {
$data = $post->update([
"comments.$key.description" => $dataArray['description'],
"comments.$key.updated_at" => $dataArray['updated_at'],
]);
}}
I want to to do something like below.
$post = Post::where('_id', $id)->where('comments.*.id', $commentId)->update(array('description' => $desc));
Or I have to write raw MongoDB query for that.
I have 1 level nested comment also under main comments so if I want to update nested comment than I have to loop comment array than the nested comment array.
if ($subCommentId) {
foreach ($comment as $nestedkey => $nestedComments) {
if ($nestedComments['id'] === $subCommentId && $nestedComments['posted_by'] == $authUser['id']) {
$data = $post->update([
"comments.$key.$nestedkey.description" => $dataArray['description'],
"comments.$key.$nestedkey.updated_at" => $dataArray['updated_at'],
]);
}
}
}
Something like this :
$post = Post::where('_id', $id)->where('comments.*.id', $commentId)->where('comments.*.*.id', $subCommentId)->update(array('description' => $desc));
Is it good to store comment in the same collection as an array or should I create a new collection for that as maximum BSON document size is 16 megabytes and how much comments it can store like 10K or more?
Below is my sample comment array format under one Collection.
"comments" : [
{
"description" : "description some",
"channel" : "swachhata-citizen-android",
"user_role" : "Citizen",
"id" : "5b4dc367d282f",
"user_role_id" : ObjectId("5accd7f8309a203be03b6441"),
"created_at" : "2018-07-17 15:52:31",
"updated_at" : "2018-07-17 15:52:31",
"ip_address" : "127.0.0.1",
"user_agent" : "PostmanRuntime/6.4.1",
"deleted" : false,
"channel_id" : "5acccfe4309a2038347a5c47",
"posted_by" : NumberInt(1),
"comments" : [
{
"description" : "some description nested",
"channel" : "swachhata-citizen-android",
"user_role" : "Citizen",
"id" : "5b4dcfc7022db",
"user_role_id" : ObjectId("5accd7f8309a203be03b6441"),
"created_at" : "2018-07-17 16:45:19",
"updated_at" : "2018-07-17 16:45:19",
"ip_address" : "127.0.0.1",
"user_agent" : "PostmanRuntime/6.4.1",
"deleted" : false,
"channel_id" : "5acccfe4309a2038347a5c47",
"posted_by" : NumberInt(1)
}
]
}
]
Thanks. :)
To update nested document, you should use arrayFilters:
Post::raw()->updateMany(
[],
[ '$set' => ["comments.$[i].comments.$[j].description" => $desc] ],
[ '$arrayFilters' => [
[
[ "i.id" => "5b4dc367d282f" ],
[ "j.id" => "5b4dcfc7022db" ]
]
]
]
)
Hope it helps :)
I understand GroupBy and Include more or less, but can I group together the records that are included?
I have this working in my controller:
public async Task<IActionResult> Index()
{
return View(await _context.Organizations.Include(x => x.Members).ToListAsync());
}
Which gives me my Organizations and their Members... but I want the members grouped in to their teams. I thought something like:
public async Task<IActionResult> Index()
{
return View(await _context.Organizations.Include(
x => x.Members.GroupBy(m => m.Team)).ToListAsync());
}
But that is incorrect.
I'd like the returned data to be something around (but not necessarily exactly):
[
{
"ID" : "some-guid-string"
"Name" : "MyOrganization",
"Members" : {
"Team1" : [
"MemberName" : "John",
"MemberName" : "Jess",
"MemberName" : "Joe",
],
"Team2" : [
"MemberName" : "Jake",
"MemberName" : "Jeff"
]
}
}
]
I don't think you need to explicitly include fields which are coded in your query. But If I'm wrong, then you just need to replace _context.Organizations with _context.Organizations.Include(o => o.Members).Include(o => o.Members.Select(m => m.Team)) and the reset is the same.
To get a JSON output like this:
[
{
"Id": "some-guid-string",
"Name": "MyOrganization",
"Members":
{
"Team1":
[
"John",
"Jess",
"Joe"
],
"Team2":
[
"Jake",
"Jeff"
]
}
}
]
Could be done with:
_context.Organizations.Select(o => new
{
Id = o.Id,
Name = o.Name,
Members =
o.Members.GroupBy(m => m.Team)
.ToDictionary(kvp => kvp.Key.Name, kvp => kvp.Select(p => p.Name))
});
We have the following Testsnippet in Ruby
def self.course_overview(course_member=nil)
course_member = CourseMember.last if course_member == nil
group_global = {"$group" =>
{"_id" => { "course_id" => "$course_id",
"title" => "$title",
"place" => "$place",
"description" => "$description",
"choosen_id" => "$choosen_id",
"year" => {"$year" => "$created_at"},
"course_member_ids" => "$course_member_ids"}}
}
match_global = {"$match" => {"_id.course_member_ids" => {"$in" => "#{course_member.id}"} }}
test = CoursePlan.collection.aggregate([group_global, match_global])
return test
end
The problem is the "match_global" statement. We would like to match all Documents where the course_member ID is appearing in the course_member_ids array.
The above statement fails with the error: "...must be an array". This make sense to me but according to other comments on the web this should be possible this way.
Any advice? How is it possible to return the docs where the course_member id is in the array of the course_member ids?
Sample CoursePlan Object:
{
"_id" : ObjectId("5371e70651a53ed5ad000055"),
"course_id" : ObjectId("5371e2e051a53ed5ad000039"),
"course_member_ids" : [
ObjectId("5371e2a751a53ed5ad00002d"),
ObjectId("5371e2b251a53ed5ad000030"),
ObjectId("5371e2bb51a53ed5ad000033")
],
"created_at" : ISODate("2014-05-13T09:33:58.042Z"),
"current_user" : "51b473bf6986aee9c0000002",
"description" : "Schulung 1 / Elektro",
"fill_out" : ISODate("2014-04-30T22:00:00.000Z"),
"place" : "TEST",
"title" : "Schulung 1",
"updated_at" : ISODate("2014-05-13T09:33:58.811Z"),
"user_ids" : [
ObjectId("51b473bf6986aee9c0000002"),
ObjectId("521d7f606986ae4826000002"),
ObjectId("521d8b3f6986aed678000007")
]
}
Since course_member_ids is an array of course members you should test for equality. In shell syntax:
{$match:{"_id.course_member_ids":<valueYouWantToTest>}}
You don't need $in as this query is analogous to a find when you want to select documents that have a particular single value you are looking for.
This is the first of 7 test/example documents, in collection "SoManySins."
{
"_id" : ObjectId("51671bb6a6a02d7812000018"),
"Treats" : "Sin1 = Gluttony",
"Sin1" : "Gluttony",
"Favourited" : "YES",
"RecentActivity" : "YES",
"GoAgain?" : "YeaSure."
}
I would like to be able to query to retrieve any info in any position,
just by referring to the position. The following document,
{
"_id" : ObjectId("51671bb6a6a02d7812000018"),
"Sin1" : "Gluttony",
"?????????" : "??????",
"RecentActivity" : "YES",
"GoAgain?" : "YeaSure."
}
One could retrieve whatever might be in the 3rd key~value
pair. Why should one have to know ahead of time what the
data is, in the key? If one has the same structure for the
collection, who needs to know? This way, you can get
double the efficiency? Like having a whole lot of mailboxes,
and your app's users supply the key and the value; your app
just queries the dbs' documents' arrays' positions.
Clara? finally? I hope?
The sample document you've provided is not saved as an array in BSON:
{
"_id" : ObjectId("51671bb6a6a02d7812000018"),
"Sin1" : "Gluttony",
"?????????" : "??????",
"RecentActivity" : "YES",
"GoAgain?" : "YeaSure."
}
Depending on the MongoDB driver you are using, the fields here are typically represented in your application code as an associative array or hash. These data structures are not order-preserving so you cannot assume that the 3rd field in a given document will correspond to the same field in another document (or even that the same field ordering will be consistent on multiple fetches). You need to reference the field by name.
If you instead use an array for your fields, you can refer by position or select a subset of the array using the $slice projection.
Example document with an array of fields:
{
"_id" : ObjectId("51671bb6a6a02d7812000018"),
"fields": [
{ "Sin1" : "Gluttony" },
{ "?????????" : "??????" },
{ "RecentActivity" : "YES" },
{ "GoAgain?" : "YeaSure." }
]
}
.. and query to find the second element of the fields array (a $slice with skip 1, limit 1):
db.SoManySins.find({}, { fields: { $slice: [1,1]} })
{
"_id" : ObjectId("51671bb6a6a02d7812000018"),
"fields" : [
{
"?????????" : "??????"
}
]
}
This is one way to Query and get back data when you may not
know what the data is, but you know the structure of the data:
examples in Mongo Shell, and in PHP
// the basics, setup:
$dbhost = 'localhost'; $dbname = 'test';
$m = new Mongo("mongodb://$dbhost");
$db = $m->$dbname;
$CursorFerWrites = $db->NEWthang;
// defining a set of data, creating a document with PHP:
$TheFieldGenerator = array( 'FieldxExp' => array(
array('Doc1 K1'=>'Val A1','Doc1 K2'=>'ValA2','Doc1 K3'=>'Val A3'),
array('Doc2 K1'=>'V1','Doc2 K2'=>'V2','Doc2 K3'=>'V3' ) ) ) ;
// then write it to MongoDB:
$CursorFerWrites->save($TheFieldGenerator);
NOTE : In the Shell : This produces the same Document:
> db.NEWthang.insert({"FieldxExp" : [
{"Doc1 K1":"Val A1","Doc1 K2":"Val A2","Doc1 K3":"Val A3"},
{"Doc2 K1":"V1", "Doc2 K2":"V2","Doc2 K3":"V3"}
]
})
#
Now, some mongodb Shell syntax:
> db.NEWthang.find().pretty()
{
"_id" : ObjectId("516c4053baa133464d36e836"),
"FieldxExp" : [
{
"Doc1 K1" : "Val A1",
"Doc1 K2" : "Val A2",
"Doc1 K3" : "Val A3"
},
{
"Doc2 K1" : "V1",
"Doc2 K2" : "V2",
"Doc2 K3" : "V3"
}
]
}
> db.NEWthang.find({}, { "FieldxExp" : { $slice: [1,1]} } ).pretty()
{
"_id" : ObjectId("516c4053baa133464d36e836"),
"FieldxExp" : [
{
"Doc2 K1" : "V1",
"Doc2 K2" : "V2",
"Doc2 K3" : "V3"
}
]
}
> db.NEWthang.find({}, { "FieldxExp" : { $slice: [0,1]} } ).pretty()
{
"_id" : ObjectId("516c4053baa133464d36e836"),
"FieldxExp" : [
{
"Doc1 K1" : "Val A1",
"Doc1 K2" : "Val A2",
"Doc1 K3" : "Val A3"
}
]
}
Finally, how about write the Query in some PHP ::
// these will be for building the MongoCursor:
$myEmptyArray = array();
$TheProjectionCriteria = array('FieldxExp'=> array('$slice' => array(1,1)));
// which gets set up here:
$CursorNEWthang1 = new MongoCollection($db, 'NEWthang');
// and now ready to make the Query/read:
$ReadomgomgPls=$CursorNEWthang1->find($myEmptyArray,$TheProjectionCriteria);
and the second document will be printed out:
foreach ($ReadomgomgPls as $somekey=>$AxMongoDBxDocFromCollection) {
var_dump($AxMongoDBxDocFromCollection);echo '<br />';
}
Hope this is helpful for a few folks.
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;
}