Sorry if this might be a typical "RTM"-question, I am new to MongoDB and did some manual-reading but sadly I didn't find an attempt to solve that.
I have two collections, one collection is "articles" containing an array of "categories" which has one or more MongoID objects with IDs of my categories-collection.
I would like to display all categories with the number of articles refering to the category. Below my solution I found atfer some time of researching:
my collection of categories:
Array
(
[_id] => MongoId Object
(
[$id] => 54eb1510974f5590179702aa
)
[name] => Test
[multiplier] => 2
)
My collection of articles:
Array
(
[_id] => MongoId Object
(
[$id] => 54e5e39f974f5535248b4bdf
)
[productnumber] => 63483
[categories] => Array
(
//... other categories...
[1] => MongoId Object
(
[$id] => 54eb1510974f5590179702aa
)
)
[image] => /var/www/mongodbtest/Files/FTP/images/63483.jpg
)
My current PHP code:
foreach($oAllCategories as $oCategory)
{
$iArticleCount = $oArticles->find(array('categories' => $oCategory['_id']))->count();
// Debug
echo $oCategory['name'].' = '.$iArticleCount.' <br />';
}
Now the problem is, that with 70'000 articles and 2'200 categories this is slow and takes a lot of time. Also I can't sort my categories by the number of articles without iterating through all articles.
Is there a better way to do this?
I'm not familiar with PHP, so I'll use mongo shell syntax. You can use an aggregation pipeline to compute this server-side in one go:
db.articles.aggregate([
{ "$unwind" : "$categories" },
{ "$group" : { "_id" : "$categories", "count" : { "$sum" : 1 } } }
])
The $unwind stage "unwinds" each article document along its categories array, e.g.
{ "x" : 1, "categories" : ["a", "b", "c"] }
===>
{ "x" : 1, "categories" : "a" },
{ "x" : 1, "categories" : "b" },
{ "x" : 1, "categories" : "c" }
Then the $group stage merges all the documents along the values of categories and counts the number of elements in the group. The result looks like
{ "_id" : "c", "count" : 1 }
{ "_id" : "b", "count" : 1 }
{ "_id" : "a", "count" : 1 }
Your _id's would be category _id's, which you could join with the categories collection to turn into names. I think you should just store the category name along with the _id on the article, though. How often does a category name actually change?
Generally, you should avoid doing operations like this, though, because the aggregation is scanning every article, expanding it into multiple documents, the processing every one into its corresponding group. It's better to incrementally maintain this information in another collection For example, you could increment a count in each category document every time an article in that category is inserted.
Related
Going to try this again as I think my previous post Inserting complex PHP array directly into mongodb was poorly asked, or didn't provide a tl;dr... I'm having a really hard time finding much documentation on anything past the basics.
My array looks like:
[publication] => Array
(
[_id] => 100000009
[title] => title
[author] => author
)
I'm using PHPdriver for mongodb and I directly add the array like this:
$result = $publications->insertOne([
$publication
]);
and while this works, it give me this:
{
"_id" : ObjectId("5a910438834a9c2314006cf6"),
"0" : {
"_id" : "100000009",
"title" : "uf",
"author" : "author"
}
}
How do I make it like this instead:
{
"_id" : "100000009",
"title" : "uf",
"author" : "author"
}
Without manually breaking each line out into a "id" => $publication['id'] inside the insertOne statement?
It seems you want to insert a document, not a mongodb array.
You are creating a new array wrapping your document.
Try this:
$result = $publications->insertOne($publication);
Here is my JSON Array
{
"_id" : ObjectId("563b57c84abf457b395076f0"),
"project_id" : "563b57c84abf45ce1f5076f1",
"project_task" : [
{
"switch" : "Ball",
"deviceId" : "dasdqwdf124",
"slot" : "2344",
"MigrationStartDate" : "",
"MigrationEndDate" : "",
"MigrationStatus" : "",
"task_id" :12
}
]
}
In this array project_id and task_id (Sub Document element) values are unique.
So in this case i need update my sub document element one at a time When project_id and task_id are equals to given values.
So far i used update function with and condition and findAndModify() function but never worked for me.
Here is my Query using findAndModify()
findAndModify(array('project_id' => "$projectId", 'project_task' => array('task_id' => "$taskId")), array('$set' => array("$column" => "$value")))
Please help me out
First of all, I am VERY new to Mongodb, so please bear with me. I have a mongodb collection that has data like below (in PHP array representation):
Array
(
[_id] => MongoId Object
(
[$id] => 8974439e66777114648b47dc
)
[mechFamily] => X07_B22
[mechName] => X07_B22_61
[mechType] => Original
[spans] => Array
(
[noOfSpans] => 5
[span] => Array
(
[0] => Array
(
[type] => solid
[clipShape] => rectangle
[id] => 2d8c1d2a6756323beca8e6e59823e3b
My requirement is that I need to pass condition to find() which will give me only those records where noOfSpansis 10.
I tried:
$myCollection->find(array("spans.noOfSpans" => 10));
But that does not return anything, I looked at the nest query sections in docs as well as SO, but the answers are pretty confusing. Can anyone tell me how to query this mongodb structure?
I'm not fluent in PHP's syntax, so I'm going to translate your document into mongo shell syntax and then propose a query. Let me know if I did it wrong and I will try to correct it.
{
"_id" : "8974439e66777114648b47dc",
"mechFamily" : "X07_B22",
"mechName" : "X07_B22_61",
"mechType" : "Original",
"spans" : {
"noOfSpans" : 5,
"span" : [
{
"type" : "solid",
"clipShape" : "rectangle",
"id" : "2d8c1d2a6756323beca8e6e59823e3b"
}
]
}
}
The query to match documents where spans.noOfSpans is 10 is
db.collection.find({ "spans.noOfSpans" : 10 })
which looks to be the same as your PHP query that you say isn't working. Is my document structure incorrect? What do you mean when you say the find isn't returning anything? It's returning a cursor with no results? How are you checking?
I am trying to update() a specific single array in a collection, but while it works fine with $push parameter on a single, specific array, it does not work with a $set parameter.
I don't quite understand logic behind that, because when I use such an example of $pushing the element:
$post_comment = array('$push' =>
array("comments" => array(
"_id" => new MongoId(),
"comment" => htmlspecialchars($_POST['comment']),
"author" => $user->username,
"date" => new MongoDate()
)
)
);
$entries->update(array(
"_id" => $_GET["id"]), $post_comment);
It gives me an array in a MongoDB database which looks more or less like this (with four items pushed in, respectively) :
{
"_id" : "css-clearfix-explained",
"comments" : [
{
"_id" : ObjectId("540cc940af105b19133c9869"),
"comment" : "aaa",
"author" : "maciejsitko",
"date" : ISODate("2014-09-07T21:08:16.215Z")
},
{
"_id" : ObjectId("540cc943af105b19133c986a"),
"comment" : "bbb",
"author" : "maciejsitko",
"date" : ISODate("2014-09-07T21:08:19.542Z")
},
{
"_id" : ObjectId("540cc946af105b19133c986b"),
"comment" : "ccc",
"author" : "maciejsitko",
"date" : ISODate("2014-09-07T21:08:22.968Z")
}
]
}
Which is basically what I want to have, and logically, works fine according to the documentation. But when I try the same with $set as for to edit an individual comment, in the similar fashion as shown:
$edit_comment = array('$set' =>
array("comments" => array(
"_id" => new MongoId($_POST['cmt-id']),
"comment" => htmlspecialchars($_POST['edit-comment']),
"author" => $user->username,
"date" => new MongoDate()
)
)
);
$entries->update(array(
"_id" => $_GET["id"]), $edit_comment);
It outputs four different arrays in place of the previous arrays, to illustrate that, i'll show what happened when I updated first comment "aaa" to "ddd" :
{
"_id" : "css-clearfix-explained",
"comments" : {
"_id" : ObjectId("540cc940af105b19133c9869"),
"comment" : "ddd\r\n ",
"author" : "maciejsitko",
"date" : ISODate("2014-09-07T21:12:10.833Z")
}
}
All the four array elements were pretty much erased and in their place appeared four fields as four independent array elements.
How come? Shouldn't it just work just fine like the example with $push above?
You didn't specify an index within comments. Therefore, $set replaced the array comments with the associated array supplied.
If you want to update a comment, then change your query in the first argument to match a comment by a unique field. Ex, date. In the second argument use a positional $ operator.
Example:
$edit_comment = array('$set' =>
array("comments.$" => array(
"_id" => new MongoId($_POST['cmt-id']),
"comment" => htmlspecialchars($_POST['edit-comment']),
"author" => $user->username,
"date" => new MongoDate()
)
)
);
// this assumes the post date is unique. On second though use something else.
$query = array( "_id" => $_GET["id"], "comments.date" => $_POST['post-date'])
$entries->update( $query, $edit_comment);
Check this out for more info and better explanation:
MongoDB - $set to update or push Array element
Due to a bug in my PHP script, I have created multiple erroneous entries in my MongoDB. Specifically, I was using $addToSet and $each and under certain circumstances, the MongoDB object gets updated wrongly as below:
array (
'_id' => new MongoId("4fa4f815a6a54cedde000000"),
'poster' => 'alex#randommail.com',
'image' =>
array (
'0' => 'image1.jpg',
'1' => 'image2.jpg',
'2' => 'image3.png',
'3' =>
array (
'$each' => NULL,
),
),
You can see that "image.3" is different from the other array entries, and that was the incorrect entry. I have fixed the related in my PHP script, however I am having difficulties tracking down all the affected entries in MongoDB to remove such entries.
Is there any way in MongoDB to check if any of the image array entry contains another array instead of string? Since the index of containing the sub-array is a variable, it would not be possible to perform a $type check on image.3 for every entry.
Could use any suggestion. Thanks!
Since this was an error in your PHP, I suppose, now you want to upgrade all erroneous documents.
If so, you could just find all documents where array item is an array itself.
> db.arr.insert({values: [1, 2, 3, [4, 5]]})
> db.arr.insert({values: [6, 7, 8]})
>
> db.arr.find({values: {$type: 4}})
{ "_id" : ObjectId("4fae0750d59332f28c702618"), "values" : [ 1, 2, 3, [ 4, 5 ] ] }
Now let's fix this. To remove such entries without fetching them to PHP I offer this simple two-step operation.
First, find all documents with arrays and unset those arrays. This will leave nulls in place of them. Note, it will only match and update first array in the document. If there are several, you want to repeat the operation
> db.arr.update({values: {$type: 4}}, {$unset: {'values.$': 1}}, false, true);
> db.arr.find()
{ "_id" : ObjectId("4fae0750d59332f28c702618"), "values" : [ 1, 2, 3, null ] }
Remove nulls.
> db.arr.update({values: null}, {$pull: {values: null}}, false, true);
> db.arr.find()
{ "_id" : ObjectId("4fae0750d59332f28c702618"), "values" : [ 1, 2, 3 ] }