Adding objects to a deep array in MongoDB - node.js

I've just started building a little application using MongoDB and can't seem to find any examples where I can add objects to a deep array that I can then find on an individual basis.
Let me illustrate by the following set of steps I take as well as the code I've written.
I create a simple object in MongoDB like so:
testing = { name: "s1", children: [] };
db.data.save(testing);
When I query it everything looks nice and simple still:
db.data.find();
Which outputs:
{
"_id" : ObjectId("4f36121082b4c129cfce3901"),
"name" : "s1",
"children" : [ ]
}
However, after I update the "children" array by "pushing" an object into it, I get into all sorts of problems.
First the update command that I run:
db.data.update({ name:"s1" },{
$push: {
children: { name:"r1" }
}
});
Then when I query the DB:
db.data.find({
children: { name: "r1" }
});
Results in:
{
"_id" : ObjectId("4f36121082b4c129cfce3901"),
"children" : [ { "name" : "r1" } ],
"name" : "s1"
}
Which doesn't make any sense to me, since I would have expected the following:
{
"name": "r1"
}
Is there a better way of inserting data into MongoDB so that when I run queries I extract individual objects rather than the entire tree? Or perhaps a better way of writing the "find" query?

By default mongodb find retrieve all the fields(like * from in sql). You can extract the particular field by specifying the field name
.
db.data.find({ "children.name": "r1" },'children.name');

Why would you expect ot to return only part of a document? It returns the whole document unless you tell it which fields you want to explicitly include or exclude. See http://www.mongodb.org/display/DOCS/Retrieving+a+Subset+of+Fields

Related

how to create a new collection in $out in mongodb based on field names?

I am trying to create a new collection based on field names of document in $out.
I have tried bellow command but didn't work.
{ $out: "$fieldName" }
output is: MongoServerError: PlanExecutor error during aggregation :: caused by :: error with target namespace: Invalid collection name: $fieldName.
one of my documents is like the bellow:
{
"_id" : {
"age" : "24",
"gender" : "female"
},
"fieldName" : "engineer",
"name" : "",
"value" : NumberInt(1)
}
I don't think you can do this.
The $out stage specifies a single place to direct the output of the entire pipeline to. There is no guarantee that the field names would all specify the same value (and my guess is that you expect that they don't). We can see in the documentation that the coll (and db) parameters take string values as input rather than expressions which resolve to strings.
If I attempt to run the command in the comments it fails with a warning about the wrong type:
test> db.version()
6.0.1
test> db.foo.aggregate([{ $out: {db: "db_name", coll: { $getField: "x" }} }])
MongoServerError: wrong type for field (coll) object != string
Interestingly, the playground actually doesn't fail. But it looks like that's because it is effectively not evaluating/executing the $out. I can put in a random parameter name for the stage and it still "works", whereas it normally validates all of the parameters. So I think that the playground here is actually misleading.

Delete whole document for matching objectId in array of objects - MongoDB

The structure of my document looks like this in MongoDB :-
{
_id : "7464bbuiecdhbjdje"
client : "MJMK"
users : [
{_id : "1234" , name : "first user"}
]
}
I would like to remove the whole document for matching users._id. In this case, for a user with _id 1234,the whole document needs to be removed. I have been unable to find any efficient function that does this in Node using mongoose. Thanks for your help.
You want to use deleteMany.
This will delete all documents containing the matches query, in this case a user with matching _id in the users array. Our query will be utilizing Mongo's dot notation to access the array like so:
Model.deleteMany({ 'users._id': "1234" })
From the shell following works. Isn't it possible to do the same in node?
db.collectionname.find({ "users": { $elemMatch: { "_id": "1234" } } })
db.collectionname.remove({ "users": { $elemMatch: { "_id": "1234" } } })
Try this:
db.collectionName.remove({"users._id":{_id:"1234"}})

Storing a complex Query within MongoDb Document [duplicate]

This is the case: A webshop in which I want to configure which items should be listed in the sjop based on a set of parameters.
I want this to be configurable, because that allows me to experiment with different parameters also change their values easily.
I have a Product collection that I want to query based on multiple parameters.
A couple of these are found here:
within product:
"delivery" : {
"maximum_delivery_days" : 30,
"average_delivery_days" : 10,
"source" : 1,
"filling_rate" : 85,
"stock" : 0
}
but also other parameters exist.
An example of such query to decide whether or not to include a product could be:
"$or" : [
{
"delivery.stock" : 1
},
{
"$or" : [
{
"$and" : [
{
"delivery.maximum_delivery_days" : {
"$lt" : 60
}
},
{
"delivery.filling_rate" : {
"$gt" : 90
}
}
]
},
{
"$and" : [
{
"delivery.maximum_delivery_days" : {
"$lt" : 40
}
},
{
"delivery.filling_rate" : {
"$gt" : 80
}
}
]
},
{
"$and" : [
{
"delivery.delivery_days" : {
"$lt" : 25
}
},
{
"delivery.filling_rate" : {
"$gt" : 70
}
}
]
}
]
}
]
Now to make this configurable, I need to be able to handle boolean logic, parameters and values.
So, I got the idea, since such query itself is JSON, to store it in Mongo and have my Java app retrieve it.
Next thing is using it in the filter (e.g. find, or whatever) and work on the corresponding selection of products.
The advantage of this approach is that I can actually analyse the data and the effectiveness of the query outside of my program.
I would store it by name in the database. E.g.
{
"name": "query1",
"query": { the thing printed above starting with "$or"... }
}
using:
db.queries.insert({
"name" : "query1",
"query": { the thing printed above starting with "$or"... }
})
Which results in:
2016-03-27T14:43:37.265+0200 E QUERY Error: field names cannot start with $ [$or]
at Error (<anonymous>)
at DBCollection._validateForStorage (src/mongo/shell/collection.js:161:19)
at DBCollection._validateForStorage (src/mongo/shell/collection.js:165:18)
at insert (src/mongo/shell/bulk_api.js:646:20)
at DBCollection.insert (src/mongo/shell/collection.js:243:18)
at (shell):1:12 at src/mongo/shell/collection.js:161
But I CAN STORE it using Robomongo, but not always. Obviously I am doing something wrong. But I have NO IDEA what it is.
If it fails, and I create a brand new collection and try again, it succeeds. Weird stuff that goes beyond what I can comprehend.
But when I try updating values in the "query", changes are not going through. Never. Not even sometimes.
I can however create a new object and discard the previous one. So, the workaround is there.
db.queries.update(
{"name": "query1"},
{"$set": {
... update goes here ...
}
}
)
doing this results in:
WriteResult({
"nMatched" : 0,
"nUpserted" : 0,
"nModified" : 0,
"writeError" : {
"code" : 52,
"errmsg" : "The dollar ($) prefixed field '$or' in 'action.$or' is not valid for storage."
}
})
seems pretty close to the other message above.
Needles to say, I am pretty clueless about what is going on here, so I hope some of the wizzards here are able to shed some light on the matter
I think the error message contains the important info you need to consider:
QUERY Error: field names cannot start with $
Since you are trying to store a query (or part of one) in a document, you'll end up with attribute names that contain mongo operator keywords (such as $or, $ne, $gt). The mongo documentation actually references this exact scenario - emphasis added
Field names cannot contain dots (i.e. .) or null characters, and they must not start with a dollar sign (i.e. $)...
I wouldn't trust 3rd party applications such as Robomongo in these instances. I suggest debugging/testing this issue directly in the mongo shell.
My suggestion would be to store an escaped version of the query in your document as to not interfere with reserved operator keywords. You can use the available JSON.stringify(my_obj); to encode your partial query into a string and then parse/decode it when you choose to retrieve it later on: JSON.parse(escaped_query_string_from_db)
Your approach of storing the query as a JSON object in MongoDB is not viable.
You could potentially store your query logic and fields in MongoDB, but you have to have an external app build the query with the proper MongoDB syntax.
MongoDB queries contain operators, and some of those have special characters in them.
There are rules for mongoDB filed names. These rules do not allow for special characters.
Look here: https://docs.mongodb.org/manual/reference/limits/#Restrictions-on-Field-Names
The probable reason you can sometimes successfully create the doc using Robomongo is because Robomongo is transforming your query into a string and properly escaping the special characters as it sends it to MongoDB.
This also explains why your attempt to update them never works. You tried to create a document, but instead created something that is a string object, so your update conditions are probably not retrieving any docs.
I see two problems with your approach.
In following query
db.queries.insert({
"name" : "query1",
"query": { the thing printed above starting with "$or"... }
})
a valid JSON expects key, value pair. here in "query" you are storing an object without a key. You have two options. either store query as text or create another key inside curly braces.
Second problem is, you are storing query values without wrapping in quotes. All string values must be wrapped in quotes.
so your final document should appear as
db.queries.insert({
"name" : "query1",
"query": 'the thing printed above starting with "$or"... '
})
Now try, it should work.
Obviously my attempt to store a query in mongo the way I did was foolish as became clear from the answers from both #bigdatakid and #lix. So what I finally did was this: I altered the naming of the fields to comply to the mongo requirements.
E.g. instead of $or I used _$or etc. and instead of using a . inside the name I used a #. Both of which I am replacing in my Java code.
This way I can still easily try and test the queries outside of my program. In my Java program I just change the names and use the query. Using just 2 lines of code. It simply works now. Thanks guys for the suggestions you made.
String documentAsString = query.toJson().replaceAll("_\\$", "\\$").replaceAll("#", ".");
Object q = JSON.parse(documentAsString);

mongoose exclude field in array

I have something like:
Schema Subdocument
name: String
data: Mixed
Schema Stuff
documents: [Subdocument]
Now, in my API there are two endpoints, one for the Subdocument and another for Stuff. When I want to get a Subdocument I need to contain the data field, but when I want to get Stuff, I want to show the name of those subdocuments, but I don't want to show the data field because is quite large and it won't be used.
So, to keep things clear, data is not private. It's just that I don't want it to be shown when I get it from Stuff
I tried by doing:
Stuff.findById(id)
.populate("documents")
.populate("-documents.data")
but that doesn't work... I'm getting the Stuffwith the Subdocumentcontaining the name and data. I feel like i'm missing to tell mongoose when I call populate("-documents.data") that documents is an array and I want to exclude the data field for each element in this array.
edit: Sorry the Schema I provided was not for my case. In my case it was not embedded, but a reference, like so:
Schema Subdocument
name: String
data: Mixed
Schema Stuff
documents: [{
type: Schema.Types.ObjectId,
ref: 'Subdocument'
}]
Assuming subDocument is not embedded and using as "ref" as you say populate is working but data part is not included:
Stuff.findById(id).populate( { "path" : "documents", "select" : "-data" })
Your documents have an "embedded" schema, so populate is not used here, it is used only for "referenced" schemas where the other objects are in another collection.
Fortunately with "embedded" there is an easy way using projection:
Stuff.findById(id,{ "documents.name": 1 },function(err,results) {
})
With results like
{ "documents": [{ "name": "this" },{ "name": "that" }] }
Or with .aggregate() and the $map operator:
Stuff.aggregate(
[
{ "$match": { "_id": ObjectID(id) } },
{ "$project": {
"documents": {
"$map": {
"$input": "$documents",
"as": "el",
"in": "$$el.name"
}
}
}}
],function(err,results) {
}
)
That will just tranform into an array of "only" the name "values", which is different to the last form.
{ "documents": ["this", "that"] }
Note, if using .aggregate() you need to properly cast the ObjectId as the autocasting from mongoose schema types does not work in aggregation pipeline stages.

Elasticsearch mapping config file boost field

I'm new to search and am having trouble interpreting the documentation on boosting fields in the mapping.
I want to achieve a simple boosting where the title of some article is more important than the tags associated with the article.
Here's an attempt at the config, which I have put in config/[index_name]/[some_name].json:
{
"[type]": {
"properties": {
"_boost": {
"name": "title",
"null_value": 2.0
}
"title": {
"type": "string"
}
}
}
}
I can tell the file is being read because of error messages from previous attempts at this file. I have also been deleting the index and recreating it between attempts so that it will use this mapping.
Will this work? It doesn't give any error messages, but I can't tell if there is any boost in effect from the output of _search or get _mapping API calls.
Here is the result of the _mapping call:
{
"[type]" : {
"properties" : {
"title" : {
"type" : "string"
}
"tags": {
"type" : "string"
}
}
}
}
Have a look at the example in the boost field documentation.
The boost field mapping (applied on the root object) allows to define
a boost field mapping where its content will control the boost level
of the document
The following mapping defines a field named _boost. If the _boost field itself exists within the JSON document indexed, its value will control the boost level of the indexed document.
{
"tweet" : {
"_boost" : {"name" : "_boost", "null_value" : 1.0}
}
}
Nothing special, the example just tells elasticsearch to consider the _boost field as it is and give a default 1.0 value to it when not present. But you are defining a boost for a specific document: that means that when the document matches a query, its score will be boosted according to the _boost field mapping that you applied to the root object. This doesn't have anything to do with boosting at a field level.
With your mapping you're saying that the content of the title field should be used as _boost, and you're giving a default _boost value of 2.0.
"_boost": {
"name": "title",
"null_value": 2.0
}
This doesn't make sense since the title contains text, and it's not either what you want I guess.
There are different ways to give more importance to a match on the title field.
As far as I understood from the documentation you can do it in your mapping like this:
{
"[type]" : {
"properties" : {
"title" : {
"type" : "string",
"boost" : 2.0
}
"tags": {
"type" : "string"
}
}
}
}
Quite honestly I haven't tried it and never used it before, but lucene does allow you to specify a boost per field at index time. The boost becomes part of the norms for that field and taken into account when there's a match on that specific field. So, this would be what you were looking for.
Anyway, I would personally do boosting at query time instead of index time, so that you don't need to modify your mapping and you can change the weight without reindexing. You can for example use a query string and search on different fields giving them different weights like this:
{
"query_string" : {
"fields" : ["title^2", "content"],
"query" : "this AND that OR thus"
}
}
You need to take into account that the query string query gets parsed and allows you to use the lucene query syntax.
Furthermore, you can combine different queries together using the bool query. You can express a boost for a match on title with a should clause containing for example a term query and a specific boost for it like this:
"should" : [
{
"term" : { "title" : "your query", "boost" : 2.0 }
}
]
You can use whatever query you want as should clause. If you go for the term query you need to remember it's not analyzed.

Resources