I'm trying to execute a query with "sort" and "limit". With mgo you could do Find(nil).Sort(“-when”).Limit(10) but the new, official mongo driver has no such methods. How can I sort and "limit" with the new driver?
In the current version mongo-go-driver v1.0.3, the options are simplified. For example to perform find, sort and limit:
import (
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
options := options.Find()
// Sort by `_id` field descending
options.SetSort(bson.D{{"_id", -1}})
// Limit by 10 documents only
options.SetLimit(10)
cursor, err := collection.Find(context.Background(), bson.D{}, options)
See more available options on godoc.org/go.mongodb.org/mongo-driver/mongo/options. Especially FindOptions for all possible options for Find().
The official driver is not straightforward as mgo. You can do sort and limit using the findopt.Limit and findopt.Sort.
You can see examples from the official repository.
https://github.com/mongodb/mongo-go-driver/blob/5fea1444e52844a15513c0d9490327b2bd89ed7c/mongo/crud_spec_test.go#L364
You can use
findOptions := options.Find()
findOptions.SetLimit(2)
findOptions.SetSkip(2)
...
cursor, err := collection.Find(context.Background(), bson.M{}, findOptions)
resource at https://www.mongodb.com/blog/post/mongodb-go-driver-tutorial
you need import "github.com/mongodb/mongo-go-driver/options" package to build a findOptions.
import github.com/mongodb/mongo-go-driver/options
findOptions := options.Find() // build a `findOptions`
findOptions.SetSort(map[string]int{"when": -1}) // reverse order by `when`
findOptions.SetSkip(0) // skip whatever you want, like `offset` clause in mysql
findOptions.SetLimit(10) // like `limit` clause in mysql
// apply findOptions
cur, err := collection.Find(context.TODO(), bson.D{}, findOptions)
// resolve err
for cur.Next(context.TODO()) {
// call cur.Decode()
}
The sort-option apparently requires you to add a map[string]interface{} where you can specify a field as key and a sortOrder as value (where 1 means ascending and -1 means descending) as following:
sortMap := make(map[string]interface{})
sortMap["version"] = 1
opt := findopt.Sort(sortMap)
As far as I can see this means that you are only able to properly sort results by one sortField because keys in a go map are stored in a random order.
ONE LINE OPTION
I know there is already a lot of answers but you can do it as one line (if you need it for any case of yours)
// From the Doc
// func (f *FindOptions) SetSort(sort interface{}) *FindOptions
cursor, err := collection.Find(context.Background(), bson.M{}, options.Find().SetSort(map[string]int{"when": -1}).SetLimit(10))
SetSort() and the others currently returns the parent pointer itself
Related
I am trying to find whether a field exist in a nested document using Go.
Currently, the document looks like this.
I am trying to see if the item id field- 5f15d53f205c36fa8b022089 exist in the shoppingCart for this user. Using the Mongo Compass, I am able to successfully query the right document using this filter command.
{"_id": ObjectId('5f19a8950268ef67ce0c5124'), "shoppingCart.items.5f15d53f205c36fa8b022089": {$exists: true}}
I tried to do use the same syntax in Go, but I still get nothing back from the results.
cursor, err := customersCollection.Find(
ctx,
bson.M{"_id": customerID, "shoppingCart.items.5f15d53f205c36fa8b022089": bson.M{"$exists": true}},
options.Find().SetProjection(bson.M{"_id": 1}),
)
// This is how I am reading the results from the cursor. When
// printing the results, I get an empty array.
var results []bson.M
if err = cursor.All(ctx, &results); err != nil {
customerLog.Errorf(logger.LogError(logger.LogInfo()), err)
}
fmt.Printf("Products Result: %v\n", results)
I am unable to find any documentation for the proper way to include element query operators in the filter parameter.
This is the Mongo driver I am using, https://godoc.org/go.mongodb.org/mongo-driver/mongo
Edit 1.0
Things that I have tried:
Used bson.D instead of bson.M. Updated code segment.
cursor, err := customersCollection.Find(
ctx,
bson.D{{"_id", customerID}, {"shoppingCart.items.5f15d53f205c36fa8b022089", bson.D{{"$exists", true}}}},
options.Find().SetProjection(bson.M{"_id": 1}),
)
if you use go.mongodb.org/mongo-driver/bson package, you can do the following:
query := bson.M{}
query["_id"] = bson.M{
"$exists": true,
}
If you want, you can also do it cleaner, by using a wrapper:
type FilterQuery bson.M
func NewFilterQuery() FilterQuery {
return FilterQuery(bson.M{})
}
func (q FilterQuery) SetIdExists(exist bool) FilterQuery {
q["_id"] = bson.M{
"$exists": exist,
}
return q
}
And then from your code, you can do something like
query := NewFilterQuery()
query.SetIdExist(true)
..
cursor, err := customersCollection.Find(
ctx,
query,
)
...
Try the following:
bson.D{
{"$exists", true},
}
(I searched the driver's source for $exist.)
I have this kind of query to run. Running this query manually return OK with upsertedCount = 1 when the key not exist
db.test.update({Key: 'random-id'}, {$inc: {Version: 1}},{upsert: true})
I try to convert it to mongodb golang version below
client, _ := mongo.Connect(context.TODO(), options.Client().ApplyURI("mongodb://localhost:27017/"))
coll := client.Database("test").Collection("test")
filter := bson.D{bson.E{"Key", "random-id"}}
docs := bson.D{bson.E{"$inc", bson.E{"Version", 1}}}
upsert := true
result, err := coll.UpdateOne(
context.TODO(),
filter, docs,
&options.UpdateOptions{Upsert: &upsert})
if err != nil {
panic(err)
}
fmt.Print(result)
Unfortunately, this query returns error
multiple write errors: [{write errors: [{Cannot increment with non-numeric argument: {key: "Version"}}]}, {<nil>}]
Why can't it works? It seems that the driver trying to increment it without sending it to mongo
Edit:
change the schema case to Upper, to follow the go code
Use simpler version of code
The problem is with your docs value. It's supposed to be a valid document. bson.D is a valid document if all its elements are valid. It has an element with $inc key, which requires its value to be a valid document too. bson.E is not a document, it's an element of a document.
Change your docs to this:
docs := bson.D{bson.E{"$inc", bson.D{bson.E{"Version", 1}}}}
And it will work.
If order is not important (it isn't in your case), alternatively you may use bson.M to model your filter and docs like this:
filter := bson.M{"Key": "random-id"}
docs := bson.M{
"$inc": bson.M{"Version": 1},
}
This is much simpler, clearer and more intuitive.
Also note that there are builders for the options. Obtain your options.UpdateOptions value safely, idiomatically and clearly like this:
options.Update().SetUpsert(true)
How to query find using golang mongodb driver?
I try this one :
db.Collection("products").Find(nil, bson.M{}, &options.FindOptions{Sort: "-price"})
But I got this error :
cannot transform type string to a BSON Document: WriteString can only write while positioned on a Element or Value but is positioned on a TopLevel
I don't know what to pass to Sort variable becuase it is an interface{}.
try the below code
findOptions := options.Find()
// Sort by `price` field descending
findOptions.SetSort(bson.D{{"price", -1}})
db.Collection("products").Find(nil, bson.D{}, findOptions)
I couldn't pass bson.D to options(It caused error).
but this code worked for me:
queryOptions := options.FindOneOptions{}
queryOptions.SetSort(bson.D{{"priority", -1}, {"last_error_time", 1}})
sResult := collection.FindOne(context.TODO(), queryFilter, &queryOptions)
A few notes I've come across trying to solve a related problem:
If trying to sort by multiple fields be sure to use bson.D rather
than bson.M because bson.M doesn't preserve order.
If trying to programmatically build up multiple sort fields, try
appending bson.E to a bson.D
As dassum did, pass bson.M{} for an empty filter as recommended by
the mongo documentation
Applied:
sort := bson.D{}
for _, example := examples {
sort = append(sort, bson.E{example, 1})
}
findOptions.SetSort(sort)
db.Collection("products").Find(nil, bson.D{}, findOptions)
I want to update some fields in DB and also want to it to return some fields, can you suggest how to retrieve the return fields?
so i am using here,
returnFields := map[string]interface{}{"order_id":1}
data := FindAndUpdateVerticalsOffers(updateQuery, updateFields, returnFields)
How to get order_id from "data":
func FindAndUpdateVerticalsOffers(updateQuery map[string]interface{}, updateFields interface{}, returnFields map[string]interface{}) map[string]interface{} {
session := db.GetSession()
defer session.Close()
collection := session.DB("").C(VerticalsOffersName)
updateSet := bson.M{"$set": updateFields}
return collection.FindOneAndUpdate(updateQuery, updateSet, returnFields)
}
I want to update some fields in DB and also want to it to return some fields,
If you're using mongo-go-driver (currently v1.1), you can utilise FindOneAndUpdate() which finds a single document and updates it, returning either the original or the updated.
The method accepts argument for FindOneAndUpdateOptions, which supports projection. For example:
collection := client.Database("dbName").Collection("collName")
// Sets projection (or return fields)
findUpdateOptions := options.FindOneAndUpdateOptions{}
findUpdateOptions.SetProjection(bson.M{"order_id": 1})
result := collection.FindOneAndUpdate(context.TODO(),
bson.M{"foo":1},
bson.M{"$set": bson.M{"bar":1}},
&findUpdateOptions)
doc := bson.M{}
err = result.Decode(&doc)
The above query will match a document where field foo is 1, update field bar to 1, and return only order_id as the result. Note that by default the _id field is also returned. You can suppress the _id field from being projected by setting it to 0.
Please note that the return type of FindOneAndUpdate is a SingleResult object, which represents a single document returned from an operation. If the operation returned an error, the Err method of SingleResult will return that error.
I am trying to update/replace a mongodb document using a struct but i keep on getting err: update document must contain key beginning with '$'
collection := r.client.Database(database).Collection(greetingCollection)
payment.MongoID = objectid.New()
filter := bson.NewDocument(bson.EC.String("id", payment.ID))
_, err := collection.UpdateOne(ctx, filter, payment)
return err
I believe the accepted answer did not work for me because I am using the go.mongodb.org/mongo-driver package. With this package, the syntax is even simpler:
update := bson.M{
"$set": yourDocument,
}
collection.UpdateOne(ctx, filter, update)
You should provide an update statement instead of a document as third parameter to the Collection.UpdateOne method. For example:
update := bson.NewDocument(
bson.EC.SubDocumentFromElements(
"$set",
bson.EC.Double("pi", 3.14159),
),
)
collection.UpdateOne(ctx, filter, update)
See more on the available update operators in the MongoDB docs (the keys begin with '$').