Perl and MongoDB: Inserting Array of Objects into Key-Value Pairs - perl

I'd like to insert into my MongoDB using perl the following BSON structure:
{"name" : "BOB", "stuff" : [{"area1": [1,2,3,4,5]}, {"area2": [5,6,7,8,9]}]}
But have had a hard time finding a good example of this. I tried the following:
#!/usr/bin/perl
use MongoDB;
use MongoDB::Database;
use MongoDB::OID;
my $conn = MongoDB::Connection->new;
my $db = $conn->test;
my $users = $db->real_time10;
$users->insert
({
"name" => "BOB",
"stuff" =>
"area1" => [1,2,3,4,5],
"area2" => [5,6,7,8,9]
});
Which grossly outputs upon query in the mongo shell:
db.real_time10.find()
{ "_id" : ObjectId("4fc912fa000207ec08000000"), "ARRAY(0xa5bdd4)" : "area2", "A
RAY(0x2f2e844)" : null, "name" : "BOB", "stuff" : "area1" }
What is going on? Is there a simple way to do this?
My dream/desired output would be:
> db.real_time10.find()
{ "_id" : ObjectId("4fc912fa000207ec08000000"), "stuff" : {"area1" : [1,2,3,4,5],
"area2": [5,6,7,8,9]}, "name" : "BOB" }

Your missing your anonymous-array-constructor (square-brackets) in your example code - but including them in your BSON example. To get your desired output try:
$users->insert({
"name" => "BOB",
"stuff" => {
"area1" => [1,2,3,4,5],
"area2" => [5,6,7,8,9]
}
});
By excluding the array constructor it builds up a hash with the supplied array key, value pairs so it would be parsed as the following (which matches your data-dump):
{
"name" => "BOB",
"stuff" => "area1",
[1,2,3,4,5] => "area2",
[5,6,7,8,9] => undef
}
Note: an array-ref in scalar context will be seen as a string like "ARRAY(0x6052b8)"

Ah, it's this:
#!/usr/bin/perl
use MongoDB;
use MongoDB::Database;
use MongoDB::OID;
my $conn = MongoDB::Connection->new;
my $db = $conn->test;
my $users = $db->real_time10;
$users->insert({
"name" => "BOB",
"stuff" =>
{"area1" => [1,2,3,4,5],
"area2" => [5,6,7,8,9]}
});
This outputs:
{ "_id" : ObjectId("4fc91f110064e9d40b000000"), "name" : "BOB", "stuff" : { "are
a2" : [ 5, 6, 7, 8, 9 ], "area1" : [ 1, 2, 3, 4, 5 ] } }

Related

Perl get array element from mongodb

I wanted to fetch array element and store in Perl variable. If I put 0 in replace of ? in $cur->{Type}[?]->{_id} I'm able to get only one array element but I want all. below is my collection
{
"_id" : ObjectId("5b7fdb050cc3c23478005741"),
"DBName" : "sample",
"DBServerURL" : "mongodb://localhost:27017/",
"Type" : [
{
"_id" : ObjectId("5b801dc963f8c81df83891bd")
},
{
"_id" : ObjectId("5b801dc963f8c81df83891be")
},
{
"_id" : ObjectId("5b801dc963f8c81df83891bf")
},
{
"_id" : ObjectId("5b801dc963f8c81df83891c0")
}
]
}
I'm trying to get ObjectId from all fields
$cursor = $CustColl->find(
{DBName => "sample",DBServerURL => "mongodb://localhost:27017/"},{'_id' => 1, 'Type.$._id' => 1, 'DBServerURL' => 1, 'DBName' => 1}
);
while(my $cur = $cursor->next){
my $cid = "$cur->{_id}" ;
my $jid = "$cur->{Type}[?]->{_id}" ;
my $url = "$cur->{DBServerURL}" ;
my $name = "$cur->{DBName}" ;
print "$cid : $jid : $url : $name\n" ;
}
I wanted an output like below:
5b7fdb050cc3c23478005741 : 5b801dc963f8c81df83891bd : mongodb://localhost:27017/ sample
5b7fdb050cc3c23478005741 : 5b801dc963f8c81df83891be : mongodb://localhost:27017/ sample
5b7fdb050cc3c23478005741 : 5b801dc963f8c81df83891bf : mongodb://localhost:27017/ sample
5b7fdb050cc3c23478005741 : 5b801dc963f8c81df83891c0 : mongodb://localhost:27017/ sample
You are almost there. First, I fixed up your data to make it JSON but that's not a big deal:
my $json = q([{
"_id" : "5b7fdb050cc3c23478005741",
"DBName" : "sample",
"DBServerURL" : "mongodb://localhost:27017/",
"Type" : [
{
"_id" : "5b801dc963f8c81df83891bd"
},
{
"_id" : "5b801dc963f8c81df83891be"
},
{
"_id" : "5b801dc963f8c81df83891bf"
},
{
"_id" : "5b801dc963f8c81df83891c0"
}
]
} ]);
use JSON::XS;
my $perl = decode_json( $json );
That's a JSON array so you can go through it one element at a time. In Perl that shows up as an array reference Using the postfix dereference introduced in v5.20 makes this palatable (but not so hard without it):
while(my $cur = shift $perl->#*){ # or #$perl
my $cid = $cur->{_id} ;
my $url = $cur->{DBServerURL} ;
my $name = $cur->{DBName} ;
foreach my $hash ( $cur->{Type}->#* ) { # or #{ $cur->{Type} }
my $jid = $hash->{_id};
print "$cid : $jid : $url : $name\n" ;
}
}
The trick is that the $jid stuff is in another array and you want to go through those individually. There's a foreach inside the while to do that. It runs once for each of those and outputs the lines.

MongoDB + Laravel + jenssegers/laravel-mongodb + update nested child elements

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 :)

MongoDB Aggregation: Value in Array

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.

mongodb join-like query with two collections and a where clause

Suppose we have following collections in a database:
db.documents.insert([{'name': 'A'}, {'name': 'B'}, {'name': 'C'}])
db.fragments.insert([{'value:': 'A1', doc_name: 'A'}, {'value:': 'A2', doc_name: 'A'},
{'value:': 'B1', doc_name: 'B'}, {'value:': 'B2', doc_name: 'B'},
{'value:': 'C1', doc_name: 'C'}, {'value:': 'C2', doc_name: 'C'}])
where documents collection stores the names of the documents (and other stuff omitted in this example), fragments collections refers by doc_name to a document related to the fragment.
Now, if I only want to consider a subset of documents
> db.documents.find().limit(2)
{ "_id" : ObjectId("52d1a3bf49da25160ad6f076"), "name" : "A" }
{ "_id" : ObjectId("52d1a3bf49da25160ad6f077"), "name" : "B" }
then how can I see the fragments of associated to these selected documents, so I would get
{ "_id" : ObjectId("52d1a3bf49da25160ad6f079"), "value:" : "A1", "doc_name" : "A" }
{ "_id" : ObjectId("52d1a3bf49da25160ad6f07a"), "value:" : "A2", "doc_name" : "A" }
{ "_id" : ObjectId("52d1a3bf49da25160ad6f07b"), "value:" : "B1", "doc_name" : "B" }
{ "_id" : ObjectId("52d1a3bf49da25160ad6f07c"), "value:" : "B2", "doc_name" : "B" }
As a solution, I was thinking that I should store the document names in an array, something like var docnames = ??? such that
> docnames
[ "A", "B" ]
and then trying to use this array in a where clause, something like
> db.fragments.find({$where: function(x) { return (this.doc_name in docnames)}})
error: {
"$err" : "ReferenceError: docnames is not defined near 'c_name in docnames)}' ",
"code" : 16722
}
But as I am very new to mongodb, so I am having trouble figuring it out. I believe this could be done as a one-liner as well.
db.fragments.find( { 'doc_name': { $in : ['A' , 'B'] } } );
Execute this commands in mongo:
var f = db.documents.find().limit(2) , n = [];
for (var i = 0; i < f.length(); i++) n.push(f[i]['name']);
db.fragments.find( { 'doc_name': { $in : n } } );

How do I find documents with an element at a particular position in an array using MongoDB?

I would like to find all documents where element 0 of ingredients is apple. So I want to get document 1 and 3, but not 2. Is such a thing possible natively in Mongo?
The example as it is does not make sense, but my application was too complicated to put up here.
{
_id => 1
name => 'best smoothie'
ingredients => Array
(
[0] => apple
[1] => raspberry
[2] => orange
[3] => banana
)
}
{
_id => 2
name => 'summer smoothie'
ingredients => Array
(
[0] => lemon
[1] => mint
[2] => apple
)
}
{
_id => 3
name => 'yogurt smoothie'
ingredients => Array
(
[0] => apple
[1] => blueberry
)
}
Example borrowed from - Querying array elements with Mongo.
You can use the array positional operator (docs). What's useful is that you can use that same pattern and specify a specific index rather than using the general $ syntax.
Assuming this is your data:
> db.so1.insert({name:"best smoothie", ingredients: ['apple','raspberry','orange','banana']})
> db.so1.insert({name:"summer smoothie", ingredients: ['lemon','mint','apple']})
> db.so1.insert({name:"yogurt smoothie", ingredients: ['apple','blueberry']})
If you want limit the search to only index position 0, just add that to the array property name as shown below:
> db.so1.find({'ingredients.0':'apple'})
Results:
{
"_id" : ObjectId("51c4425ff227e278e59f5df5"),
"name" : "best smoothie",
"ingredients" : [
"apple",
"raspberry",
"orange",
"banana"
]
}
{
"_id" : ObjectId("51c4428af227e278e59f5df7"),
"name" : "yogurt smoothie",
"ingredients" : [
"apple",
"blueberry"
]
}
You should use $unwind with $project mongo functions.
`$unwind` - split up array
`$project` - add smth like index for each splitted element
after you could use simple findOne statement.
I don't see any way to achieve this using simple array. However here is what you could do using an array of hashes:
> db.collections.find()
{ "_id" : ObjectId("51c400d2b9f10d2c26817c5f"), "ingredients" : [ { "value1" : "apple" }, { "value2" : "orange" } ] }
{ "_id" : ObjectId("51c400dbb9f10d2c26817c60"), "ingredients" : [ { "value1" : "mint" }, { "value2" : "apple" } ] }
{ "_id" : ObjectId("51c400e1b9f10d2c26817c61"), "ingredients" : [ { "value1" : "apple" }, { "value2" : "lemon" } ] }
> db.collections.find({ ingredients: { $elemMatch: { value1: 'apple' }}})
{ "_id" : ObjectId("51c400d2b9f10d2c26817c5f"), "ingredients" : [ { "value1" : "apple" }, { "value2" : "orange" } ] }
{ "_id" : ObjectId("51c400e1b9f10d2c26817c61"), "ingredients" : [ { "value1" : "apple" }, { "value2" : "lemon" } ] }