incorrectly drafted MongoDB aggregation pipeline $match stage - mongodb

I'm trying to make a query in golang language (below I've attached working code of pure mongodb query) using go.mongodb.org/mongo-driver/mongo library, below is golang query code. I can't get matchStage to work correctly, I've tried many variants and I'm sure I'm just very inattentive or just don't understand
How can I use $match, $expr, $and and $lte at once to make a correct matchStage?
func (r *Mongo) ChatHistory(ctx context.Context, chatID string, f *Filter) ([]*Message, error) {
matchStage := bson.D{
primitive.E{
Key: "$match",
Value: bson.D{
primitive.E{Key: "$expr", Value: bson.D{
primitive.E{Key: "$and", Value: bson.A{
bson.D{
primitive.E{Key: "$lte", Value: bson.D{
primitive.E{
Key: "$create_date",
Value: f.Date, // int64
},
}},
},
}},
}},
},
},
}
sortStage := bson.D{
{
Key: "$sort", Value: bson.D{
primitive.E{Key: "create_date", Value: -1},
},
},
}
limitStage := bson.D{primitive.E{Key: "$limit", Value: f.Count}}
cursor, err := r.colMessage.Aggregate(ctx, mongo.Pipeline{matchStage, sortStage, limitStage})
if err != nil {
l.Error().Err(err).Msg("failed find")
return nil, err
}
var res []*Message
if err = cursor.All(ctx, &res); err != nil {
l.Error().Err(err).Msg("failed find all documents")
return nil, err
}
if err = cursor.Close(ctx); err != nil {
l.Error().Err(err).Msg("failed close cursor")
return nil, err
}
return res, nil
}
Error: (InvalidPipelineOperator) Unrecognized expression '$create_date'
MongoDB playground link

Value of $lte must be an array not a document:
matchStage := bson.D{
primitive.E{
Key: "$match",
Value: bson.D{
primitive.E{Key: "$expr", Value: bson.D{
primitive.E{Key: "$and", Value: bson.A{
bson.D{
primitive.E{Key: "$lte", Value: bson.A{
"$create_date",
f.Date, // int64
}},
},
}},
}},
},
},
}
Also note that you can leave out the primitive.E type from the composite literal:
matchStage := bson.D{
{
Key: "$match",
Value: bson.D{
{Key: "$expr", Value: bson.D{
{Key: "$and", Value: bson.A{
bson.D{
{Key: "$lte", Value: bson.A{
"$create_date",
f.Date, // int64
}},
},
}},
}},
},
},
}
But note that your expression on the mongo playground is incorrect. Quoting from the doc:
$match takes a document that specifies the query conditions. The query syntax is identical to the read operation query syntax
...
When using $expr, you have to use $eq, for example:
matchStage := bson.D{
{
Key: "$match",
Value: bson.D{
{Key: "$expr", Value: bson.D{
{Key: "$and", Value: bson.A{
bson.D{
{Key: "$eq", Value: bson.A{
"$chat_id",
chatID,
}},
},
bson.D{
{Key: "$lte", Value: bson.A{
"$create_date",
f.Date, // int64
}},
},
}},
}},
},
},
}
Try it here: https://mongoplayground.net/p/SBEJD-Fyhjl
You should use a normal query document in $match.
See this equivalent, much simpler solution:
matchStage := bson.D{
{
Key: "$match",
Value: bson.D{
{Key: "chat_id", Value: chatID},
{Key: "create_date", Value: bson.D{
{
Key: "$lte",
Value: f.Date, // int64
}},
},
},
},
}
And even much-much simpler if you use bson.M instead of bson.D:
matchStage := bson.M{
"$match": bson.M{
"chat_id": chatID,
"create_date": bson.M{"$lte": f.Date},
},
}
Of course in this last case you can't use mongo.Pipeline for the pipeline, but []any or []bson.M would also do.

Related

MongoDB Aggregation - lookup pipeline is not returning correct document

I am trying to join two collections. MQL is working fine in mongo compass but when i exported it into Golang, the api is returning incorrect data with correct arr length.
here is my go code
var query= bson.A{
bson.D{
{"$addFields",
bson.D{
{"opStartDate",
bson.D{
{"$dateFromString",
bson.D{
{"dateString",
bson.D{
{"$concat",
bson.A{
"$indate",
" ",
"$intime",
},
},
},
},
{"format", "%d-%m-%Y %H:%M:%S"},
},
},
},
},
{"opEndDate",
bson.D{
{"$cond",
bson.A{
bson.D{
{"$ne",
bson.A{
"$outdate",
"",
},
},
},
bson.D{
{"$dateFromString",
bson.D{
{"dateString",
bson.D{
{"$concat",
bson.A{
"$outdate",
" ",
"$outtime",
},
},
},
},
{"format", "%d-%m-%Y %H:%M:%S"},
},
},
},
time.Now(),
},
},
},
},
},
},
},
bson.D{
{"$lookup",
bson.D{
{"from", "transaction"},
{"let",
bson.D{
{"ophubname", "$hubname"},
{"inDateTime", "$opStartDate"},
{"outDateTime", "$opEndDate"},
},
},
{"pipeline",
bson.A{
bson.D{
{"$match",
bson.D{
{"$and",
bson.A{
bson.D{{"start", bson.D{{"$exists", true}}}},
bson.D{{"stop", bson.D{{"$exists", true}}}},
bson.D{{"stop", bson.D{{"$ne", ""}}}},
},
},
},
},
},
bson.D{
{"$match",
bson.D{
{"$expr",
bson.D{
{"$and",
bson.A{
bson.D{
{"$eq",
bson.A{
"$hubname",
"$$ophubname",
},
},
},
bson.D{
{"$gte",
bson.A{
bson.D{
{"$convert",
bson.D{
{"input", "$start"},
{"to", "date"},
{"onError", ""},
},
},
},
"$$inDateTime",
},
},
},
bson.D{
{"$lte",
bson.A{
bson.D{
{"$convert",
bson.D{
{"input", "$stop"},
{"to", "date"},
{"onError", time.Now()},
},
},
},
"$$outDateTime",
},
},
},
},
},
},
},
},
},
},
},
},
{"as", "transaction"},
},
},
},
}
cur, err := collection.Aggregate(context.TODO(), query)
i tried using mongo.Pipeline instead of bson.A - it didnt work

Go Mongo driver Use of undefined variable: this

While trying to run the following query, which is working fine on mongo shell, I have the following error on my golang application:
Invalid $set :: caused by :: Use of undefined variable: this
It looks like there is a problem with the use of $$this
Mongo Shell query:
db.getCollection('interests').aggregate([
{
$lookup:{
from: "products",
localField: "product_types.product_id",
foreignField: "_id",
as: "productInterestData"
}
},
{
$set: {
"product_types": {
$map: {
input: "$product_types",
in: {
$mergeObjects: [
"$$this",
{ product: {
$arrayElemAt: [
"$productInterestData",
{$indexOfArray: ["$productInterestData._id", "$$this.product_id"]}
]
}}
]
}
}
}
}
}
])
Golang code (implementation of mongo query):
pipeline := mongo.Pipeline{
// join product model
// lookup field
bson.D{bson.E{Key: "$lookup", Value: bson.M{
"from": "products",
"localField": "product_types.product_id",
"foreignField": "_id",
"as": "productInterestData",
}}},
// set array
bson.D{bson.E{Key: "$set", Value: bson.E{
Key: "product_types", Value: bson.E{
Key: "$map", Value: bson.D{
bson.E{Key: "input", Value: "$product_types"},
bson.E{Key: "in", Value: bson.E{
Key: "$mergeObjects", Value: bson.A{
"$$this",
bson.E{Key: "product", Value: bson.E{Key: "$arrayElemAt", Value: bson.A{
"$productInterestData",
bson.E{Key: "$indexOfArray", Value: bson.A{"$productInterestData._id", "$$this.product_id"}},
}}},
},
}},
},
},
}}},
// join data
bson.D{bson.E{Key: "$unset", Value: "productInterestData"}},
}
cursor, err := ctx.Store.GetCollection(ctx, &models.Interest{}).Aggregate(c, pipeline)
if err != nil {
utils.Log(c, "warn", err)
return
}

Unable to Query MongoDB

Mongodb query:
db.products.aggregate([
{
$match : {
"_id" : ObjectId("60d95b5ab861ccc04fd4b598")
}
},
{
$project: {
title : "Test Product 10",
offers: {
$filter: {
input: "$offers",
as: "offers",
cond: {
$eq: [ "$$offers.active", true ]
}
}
}
}
}
]).pretty()
Golang Query:
productMatch := bson.D{{"$match", bson.M{"_id": objID}}}
project := bson.D{{"$project", bson.D{{"offers", bson.D{{"$filter", bson.D{{
"input", "$offers"}, {"as", "offers"}, {"cond", bson.D{{
"$eq", bson.D{{"$$offers.active", true}}}}}}}}}}}}
pipeLine := mongo.Pipeline{productMatch, project}
result, err := s.DB.Collection(collectionProducts).Aggregate(context.TODO(), pipeLine)
This part:
cond: {
$eq: [ "$$offers.active", true ]
}
The value for $eq is an array. bson.D is to model documents. To model an array, use bson.A.
So instead of:
{"cond", bson.D{{
"$eq", bson.D{{"$$offers.active", true}}}}}
Use
{"cond", bson.D{{
"$eq", bson.A{"$$offers.active", true}}}}

How to write bson form of mongo query in golang?

I can query my mongodb collection to get the ipv4Addresses based on the nfType and the minimum distance using the command line query
db.nfinstancesdb.aggregate([
{
"$match": {
"nfType": "AMF"
}
},
{
"$unwind": "$ipv4Addresses"
},
{
$group: {
"_id": "$distance",
"ipv4Addresses": {
"$addToSet": "$ipv4Addresses"
}
}
},
{
"$sort": {
"_id": 1
}
},
{
"$limit": 1
}
])
This give the output am expecting as
[{"_id": 10,"ipv4Addresses": ["172.16.0.11","172.16.0.10"]}]
How can I write the bson form of the above query on Go?
I did in the function below but am getting all the ipv4Addresses
instead of the above result.
func (m *NfInstanceDataAccess) FindIp(nfType string) ([]NfInstance, error) {
var ip []NfInstance
collection := db.C(COLLECTION)
pipeline := mongo.Pipeline{
{{"$match", bson.D{
{"nfType", "AMF"},
}}},
{{"$unwind", "$ipv4Addresses"}},
{{"$group", bson.D{
{"_id", "$distance"},
{"ipv4Addresses", bson.D{
{"$addToSet", "$ipv4Addresses"},
}},
}}},
{{"$sort", bson.D{
{"_id", 1},
}}},
{{"$limit", 1}},
}
cursor, err := collection.Aggregate(context.Background(), pipeline)
defer cursor.Close(context.Background())
for cursor.Next(context.Background()) {
var ip []NfInstance
err := cursor.Decode(&ip)
if err != nil {
log.Fatal(err)
}
//fmt.Println(doc)
}
return ip, nil
}
My collection has the following items
{
"nfInstanceID": "1",
"nfType": [
"AMF"
],
"nfStatus": [
"REGISTERED"
],
"ipv4Addresses": [
"172.16.0.10"
],
"distance": 10
},
{
"nfInstanceID": "2",
"nfType": [
"UPF"
],
"nfStatus": [
"REGISTERED"
],
"ipv4Addresses": [
"172.16.0.20"
],
"distance": 20
},
{
"nfInstanceID": "3",
"nfType": [
"AMF"
],
"nfStatus": [
"REGISTERED"
],
"ipv4Addresses": [
"172.16.0.30"
],
"distance": 30
},
{
"nfInstanceID": "4",
"nfType": [
"AMF"
],
"nfStatus": [
"REGISTERED"
],
"ipv4Addresses": [
"172.16.0.11"
],
"distance": 10
}
And I am expecting the same or similar output.
If you are using the official MongoDB Go driver you can utilise Collection.Aggregate to perform Aggregation Pipeline. The example Go code snippet that you posted is using find() which is different than aggregation.
For example, using MongoDB Go driver v1.0.4 (current):
collection := client.Database("dbname").Collection("collname")
pipeline := mongo.Pipeline{
{{"$match", bson.D{
{"nfType", "AMF"},
}}},
{{"$unwind", "$ipv4Addresses"}},
{{"$group", bson.D{
{"_id", "$distance"},
{"ipv4Addresses", bson.D{
{"$addToSet", "$ipv4Addresses"},
}},
}}},
{{"$sort", bson.D{
{"_id", 1},
}}},
{{"$limit", 1}},
}
cursor, err := collection.Aggregate(context.Background(), pipeline)
defer cursor.Close(context.Background())
for cursor.Next(context.Background()) {
doc := bson.D{}
err := cursor.Decode(&doc)
if err != nil {
log.Fatal(err)
}
fmt.Println(doc)
}
The example documents that you posted only have 1 element for all of ipv4Addresses, I'm assuming that this is just an example. If however, all of the documents do only have 1 element array for ipv4Addresses you're likely better off to just use $project.
Generally bson.D is used when serialising to BSON (and when order matters).
The problem with your golang code is you didn't group.
You can make use of Pipe which prepares a pipeline to aggregate:
pipe := db.C(COLLECTION).Pipe([]bson.M{
{"$match": bson.M{"nfType": "AMF"}},
{"$unwind": "$ipv4Addresses"},
{"$group": bson.M{
"_id": "$distance",
"ipv4Addresses": bson.M{"$addToSet": "$ipv4Addresses"},
}},
{"$sort": bson.M{"_id": 1}},
{"$limit": 1},
})
err := pipe.All(&ip)

How to translate this mongo query cmd using golang

db.demo.aggregate([
{$group:{_id:"$name",
a:{$sum:"$a"},
b:{$sum:"$b"}}},
{$project:{name:1,a:1,b:1,_id:1,bdiva:{$cond:[{$eq:["$a",0]},0,{$divide:["$b","$a"]}]}}},
{$sort:{bdiva:1}}
])
I'm using "gopkg.in/mgo.v2" as mongo driver for Go
and I try translating this query cmd:
project := bson.M{
"$project": bson.M{
"name": true, "a": true, "b": true, "_id": true,
"bdiva": bson.M{"$cond": []interface{}{bson.M{"$eq": []interface{}{"$a", 0}}, 0, bson.M{"$divide": []interface{}{"$b", "$a"}}}},
},
}
group := bson.M{
"$group": bson.M{
"_id": "$name",
"a": bson.M{"$sum": "$a"},
"b": bson.M{"$sum": "$b"},
},
}
sort := bson.M{
"$sort": bson.M{
"bdiva": 1,
},
}
but I can't get bdiva right, how should I fix it?
------what I tried----
I execute the cmd in shell and get the result:
field bdiva is outputted,
but when I run the go code,it Only output:
field bdiva is missing,why?
The complete code:
func AggregateQueryDemo() {
println("//<<-------------------------AggregateQueryDemo start-----------")
start := time.Now()
session, err := mgo.Dial(ADDRESSPORT)
if err != nil {
return
}
defer session.Close()
c := session.DB(DB).C(COLLECTION)
project := bson.M{
"$project": bson.M{
"name": true, "a": true, "b": true, "suma": true, "sumb": true, "_id": true,
"bdiva": bson.M{"$cond": []interface{}{bson.M{"$eq": []interface{}{"$suma", 0}}, 0, bson.M{"$divide": []interface{}{"$sumb", "$suma"}}}},
},
}
group := bson.M{
"$group": bson.M{
"_id": "$name",
"sumb": bson.M{"$sum": "$b"},
"suma": bson.M{"$sum": "$a"},
},
}
sort := bson.M{
"$sort": bson.M{
"bdiva": 1,
},
}
operations := []bson.M{project, group, sort}
pipe := c.Pipe(operations)
ret := []interface {
}{}
err = pipe.All(&ret)
if err != nil {
panic(err.Error())
return
}
for k, v := range ret {
fmt.Printf("%+v: %+v\n", k, v)
}
fmt.Printf(" %v microseconds\n", time.Since(start)/1000000)
println("//---------------------------AggregateQueryDemo end----------->>")
}
Query conditions are right written,and these are worth being noticed :
to writh $eq condition you should use interface slice in go
Operation Order is important, to fix issue of the problem ,it should be
operations := []bson.M{group, project, sort}
thank Blake Seven