How to do multi-level push of array element in mgo? - mongodb

So I want to do a multi level push for the struct below :
type Inspector_Pool struct{
Unique_Id string `json:"unique_id" form:"unique_id" query:"unique_id"`
Email string `json:"email" form:"email" query:"email"`
Branch []string `json:"branch" query:"branch"`
Date []Date `json:"date" query:"date"`
}
type Date struct {
Event_Timestamp string `json:"event_timestamp" query:"event_timestamp"`
Event []Event `json:"event" query:"event"`
}
type Event struct {
Event_Name string `json:"event_name" form:"event_name" query:"event_name"`
Event_Id string `json:"event_id" form:"event_id" query:"event_id"`
Status string `json:"status" query:"status"`
}
So what i want is to push data in Event array so what I have to achieve code below:
//push a new node in date array
query := bson.M{"branch":"400612", "unique_id":c.Unique_Id}
update := bson.M{"$push": bson.M{"date": bson.M{"event_timestamp": t, "event": []models.Event{
{
Event:models.INSPECTION,
Event_Id:"123456789",
Status:models.PENDING,
},
}}}}
err = look_up.Update(query, update)
if err != nil {
panic(err)
}
//push the data in the nested event
pushQuery := bson.M{"date.event": bson.M{"event": []models.Event{
{
Event_Name:models.INSPECTION,
Event_Id:"4984984198",
Status:models.PENDING,
},
}}}
err = look_up.Update(bson.M{"unique_id": "2549090", "date.event_timestamp":"08-05-2017"}, bson.M{"$push": pushQuery})
if err != nil {
//panic(err)
fmt.Print("error_2",err)
}
but it doesn'tpush it in the event object but instead create a new entry in date object heres the snapshot

You can utilise the $ positional operator to update. Which basically identifies an element in an array to update without explicitly specifying the position of the element in the array.
Alter your second push statement as below:
pushQuery := bson.M{"date.$.event": Event{
Event_Name:"foobar",
Event_Id:"4984984198",
}}
err = collection.Update(bson.M{"unique_id":"2549090",
"date.event_timestamp":"08-05-2017"},
bson.M{"$push": pushQuery})
The above will push event 'foobar' into the same date array matching event_timestamp '08-05-2017' i.e.
{"_id": ObjectId("5909287934cb838fe8f89b6e"),
"unique_id": "2549090",
"date": [
{
"event_timestamp": "08-05-2017",
"event": [
{
"event_name": "baz",
"event_id": "123456789"
},
{
"event_name": "foobar",
"event_id": "4984984198"
}
]
}
]}
Generally having an array of array makes querying or extracting data difficult/complex later on. Depending on your application's use case, I would also suggest to re-consider your document Data Model.

Related

how can I set the value of objectId to another property different that _id when creating a document?

I'm trying to create an object that looks like this:
const userSettingsSchema = extendSchema(HistorySchema, {
user: //ObjectId here,
key:{type: String},
value:{type: String}
});
this is the post method declared in the router
app.post(
"/api/user/settings/:key",
userSettingsController.create
);
and this is the method "create":
async create(request, response) {
try {
const param = request.params.key;
const body = request.body;
console.log('body', body)
switch (param) {
case 'theme':
var userSettings = await UserSettings.create(body) // user:objecId -> missing...
response.status(201).send(userSettings);
break;
}
} catch (e) {
return response.status(400).send({ msg: e.message });
}
}
I don't know how to assign the value of ObjectId to the user property, because ObjectId is generate when the doc is created, thus, I can not do this: userSettings.user = userSettings._id, because the objectr is already. I only manage to get something like this created:
{
"_id": "60c77565f1ac494e445cccfe",
"key": "theme",
"value": "dark",
}
But it should be:
{
"user": "60c77565f1ac494e445cccfe",
"key": "theme",
"value": "dark",
}
_id is the only mandatory property of a document. It is unique identifier of the document and you cannot remove it.
If you provide document without _id the driver will generate one.
You can generate ObjectId as
let id = mongoose.Types.ObjectId();
and assign it to as many properties as you want.
You can even generate multiple different ObjectIds for different properties of the same document.
Now, you don't really need to assign ObjectId to "user" property. _id will do just fine. In fact it is most likely you don't need user's settings in separate collection and especially as multiple documents with single key-value pair.
You should be good by embedding "settings" property to your "user" collection as a key-value json.

What is wrong with this mongo $or query

This query works perfectly
{
$or:[{author:this.userId} , {somethingelse:true} ]
}
But when I try:
{
$or:[{author:this.userId} , {sharedwith[this.userId]:true} ]
}
I receive the message
Errors prevented startup:
While processing files with ecmascript (for target os.linux.x86_64): server/main.js:113:43: Unexpected token, expected
, (113:43)
=> Your application has errors. Waiting for file change.
And thats where the comma , in the $or statement is
Help
I guess that you are trying to retrieve all documents for which the current user is the author, or which have been shared with him/her? And therefore that you have structured your documents with a sharedWith field which is a hash map of userId as keys and boolean as value?
Document structure:
{
author: string,
sharedWith: {
<userId1>: boolean
// <userId2>…
}
}
In that case, your MongoDB query should use the dot notation to specify the value of a nested field within sharedWith field:
{
$or: [{
author: string
}, {
"sharedWith.<userId>": boolean
}]
}
To easily create the query with the interpolated value of <userId>, you can use a computed key in your object (ES6 syntax):
{
$or:[{
author: this.userId
} , {
// The query computed key must be in square brackets.
// Specify the field child key using the dot notation within your query.
["sharedwith." + this.userId]: true
}]
}
Or with good old ES5 syntax, similarly to #MichelFloyd's answer:
var query = {
$or: [{
author: this.userId
}]
};
var newCondition = {};
newCondition["sharedWith." + this.userId] = true;
query.$or.push(newCondition);
Note: the above described document structure could conveniently replace the sharedWith hash map by an array (since having a false value for the boolean could simply be replaced by removing the corresponding userId from the array):
{
author: string,
sharedWith: [
<userId1>
// <userId2>…
]
}
In which case the query would simply become:
{
$or:[{
author: this.userId
} , {
// In MongoDB query, the below selector matches either:
// - documents where `sharedWith` value is a string equal to
// the value of `this.userId`, or
// - documents where `sharedWith` value is an array which contains
// an element with the same value as `this.userId`.
sharedwith: this.userId
}]
}
Try building the query as a variable before running it.
let query = { $or: [{ author: this.userId }]};
const sw = sharedwith[this.userId];
query.$or.push({sw: true});
MyCollection.find(query);

select function does't work to only get the array i want

i have a document like this
{
"_id": {
"$oid": "570bc73da8ebd9005dd54de3"
},
"title": "dota",
"imgurl": "asd.com",
"description": "",
"hints": [
{
"date": "2016-04-26 22:50:12.6069011 +0430 IRDT",
"description": "narinin"
},
{
"date": "2016-04-26 22:50:12.6069011 +0430 IRDT",
"description": "doros shod"
}
]
}
the script i execute is
hints := hints{}
err := db.C("games").Find(bson.M{"title": game}).Select(bson.M{"hints": 0}).One(&hints)
my 2 structs are
type Game struct {
Id bson.ObjectId `bson:"_id,omitempty"`
Title string `json:"title"`
Imgurl string `json:"imgurl"`
Description string `json:"desc"`
Hints []*hint `bson:"hints", json:"hints"`
}
type hint struct {
Description string `json:"desc"`
Date time.Time `json:"date"`
}
when i use the script all i get is a none sense date string witch is not even in document
how can i get just the slice of hints from a game
You have too keep using Game struct to receive the result, even for only hints column. Also your select query should be .Select(bson.M{"hints": 1}).
I fixed your code, and tried in my local, this one is working.
game := Game{}
err = db.C("games").Find(bson.M{"title": "dota"})
.Select(bson.M{"hints": 1}).One(&game)
if err != nil {
panic(err)
}
for _, hint := range game.Hints {
fmt.Printf("%#v\n", *hint)
}
All properties of game is empty, except Hints.
EDITED 1
To get the first 10 rows on hints, the easiest way would be by playing the slice, but this is bad because it need to fetch all the rows first.
for _, hint := range game.Hints[:10] { // 10 rows
fmt.Printf("%#v\n", *hint)
}
The other solution (which is better), is by using $slice on the .Select() query.
selector := bson.M{"hints": bson.M{"$slice": 10}} // 10 rows
err = db.C("so").Find(bson.M{"title": "dota"})
.Select(selector).One(&game)
EDITED 2
Use []int{skip, limit} on the $slice, to support skip and limit.
selector := bson.M{"hints": bson.M{"$slice": []int{0, 10}}}

How do I remove an array item in Mongodb / Golang?

I have the following data structure, and I'm attempting to remove an item from the 'artists' array.
[
{
"id": "56b26eeb4a876400011369e9",
"name": "Ewan Valentine",
"email": "ewan#test.com",
"artists": [
"56b26f334a876400011369ea",
"56b2702881318d0001dd1441",
"56b2746fdf1d7e0001faaa92",
],
"user_location": "Manchester, UK"
}
]
Here's my function...
// Remove artist from user
func (repo *UserRepo) RemoveArtist(userId string, artistId string) error {
change := bson.M{"artists": bson.M{"$pull": bson.ObjectIdHex(artistId)}}
fmt.Println(userId)
err := repo.collection.UpdateId(bson.ObjectIdHex(userId), change)
return err
}
I'm getting the following error:
{
"_message": {
"Err": "The dollar ($) prefixed field '$pull' in 'artists.$pull' is not valid for storage.",
"Code": 52,
"N": 0,
"Waited": 0,
"FSyncFiles": 0,
"WTimeout": false,
"UpdatedExisting": false,
"UpsertedId": null
}
}
The $pull operator is a "top level" operator in update statements, so you simply have this the wrong way around:
change := bson.M{"$pull": bson.M{"artists": bson.ObjectIdHex(artistId)}}
The order of update operators is always operator first, action second.
If there is no operator at the "top level" keys, MongoDB interprets this as just a "plain object" to update and "replace" the matched document. Hence the error about the $ in the key name.

MongoDB (Mgo v2) Projection returns parent struct

I have here a Building Object where inside sits an Array of Floor Objects.
When Projecting, my goal is to return or count the number of Floor Objects inside a Building Object after matching the elements accordingly. The code is as follows:
Objects:
type Floor struct {
// Binary JSON Identity
ID bson.ObjectId `bson:"_id,omitempty"`
// App-level Identity
FloorUUID string `bson:"f"`
// Floor Info
FloorNumber int `bson:"l"`
// Units
FloorUnits []string `bson:"u"`
// Statistics
Created time.Time `bson:"y"`
}
type Building struct {
// Binary JSON Identity
ID bson.ObjectId `bson:"_id,omitempty"`
// App-level Identity
BldgUUID string `bson:"b"`
// Address Info
BldgNumber string `bson:"i"` // Street Number
BldgStreet string `bson:"s"` // Street
BldgCity string `bson:"c"` // City
BldgState string `bson:"t"` // State
BldgCountry string `bson:"x"` // Country
// Building Info
BldgName string `bson:"w"`
BldgOwner string `bson:"o"`
BldgMaxTenant int `bson:"m"`
BldgNumTenant int `bson:"n"`
// Floors
BldgFloors []Floor `bson:"p"`
// Statistics
Created time.Time `bson:"z"`
}
Code:
func InsertFloor(database *mgo.Database, bldg_uuid string, fnum int) error {
fmt.Println(bldg_uuid)
fmt.Println(fnum) // Floor Number
var result Floor // result := Floor{}
database.C("buildings").Find(bson.M{"b": bldg_uuid}).Select(
bson.M{"p": bson.M{"$elemMatch": bson.M{"l": fnum}}}).One(&result)
fmt.Printf("AHA %s", result)
return errors.New("x")
}
It turns out, no matter how I try the query returns a Building Object, not a floor object? What changes do I need to make in order to have the query fetch and count Floors and not Buildings?
This is done so to check if a Floor inside Building already exists before insertion. If there's a better a approach then I'll replace mine with the better!
Thanks!
You are querying for a Building document so mongo returns that to you even though you try to mask some of its fields using projection.
I don't know of a way to count the number of elements in a mongo array in a find query, but you can use the aggregation framework, where you have the $size operator that does exactly this. So you should send a query like this to mongo :
db.buildings.aggregate([
{
"$match":
{
"_id": buildingID,
"p": {
"$elemMatch": {"l": fNum}
}
}
},
{
"$project":
{
nrOfFloors: {
"$size": "$p"
}
}
}])
Which in go it would look like
result := []bson.M{}
match := bson.M{"$match": bson.M{"b": bldg_uuid, "p": bson.M{"$elemMatch": bson.M{"l": fNum}}}}
count := bson.M{"$project": bson.M{"nrOfFloors": bson.M{"$size": "$p"}}}
operations := []bson.M{match, count}
pipe := sess.DB("mgodb").C("building").Pipe(operations)
pipe.All(&result)