I have some entities in my database called "events". Each of these events contain an array of string, called "tags".
I want to make a query to get all the events matching an array of tags that I will give in parameter.
BUT, I want these events to be sorted like:
The first one is the one containing most of the tags I give in parameter.
The second one is the second one containing most of the tags I give in parameter.
And so on.
If there are more than one event containing the same amount of tags I want them to be sorted by their "name" property in alphabetical order.
Example:
"name" = "event1", "tags" = ["food", "music", "gaming", "sport"]
"name" = "event2", "tags" = ["gaming"]
"name" = "event3", "tags" = ["music", "sport"]
"name" = "event4", "tags" = ["food", "music", "gaming", "sport"]
"name" = "event5", "tags" = ["music", "sport", "coding"]
"name" = "event6", "tags" = ["coding"]
"name" = "event7", "tags" = ["food", "gaming", "sport"]
The array of tags that I give in parameter for this example is: ["food", "music", "gaming", "sport"]
The result for this example will be an array of events, containing in that order:
event1, event4, event7, event3, event5, event2
To get all the events containing the tags I simply make a query with the "$or" operators. This allowed me to get all the events if they contained at least one of the tag given in parameter.
Code:
var events []model.Event
var MyQuery []map[string]interface{}
for i := 0; i < len(tags); i++ {
currentCondition := bson.M{"tags": tags[i]}
MyQuery = append(MyQuery, currentCondition)
}
err := dbEvents.C(collectionEvents).Find(bson.M{"$or": OrQuery}).All(&events)
But i really don't know how to sort them like I showed you.
package main
import (
"fmt"
"github.com/ahmetb/go-linq"
)
type T struct {
Name string
Tags []string
}
func main() {
params := []string{"food", "music", "gaming", "sport"}
t := []T{
T{Name: "event1", Tags: []string{"food", "music", "gaming", "sport"}},
T{Name: "event2", Tags: []string{"gaming"}},
T{Name: "event3", Tags: []string{"music", "sport"}},
T{Name: "event4", Tags: []string{"food", "music", "gaming", "sport"}},
T{Name: "event5", Tags: []string{"music", "coding", "sport"}},
T{Name: "event6", Tags: []string{"coding"}},
T{Name: "event7", Tags: []string{"food", "gaming", "sport"}},
}
var result []T
linq.From(t).SortT(func(t1 T, t2 T) bool {
var rs1 []string
linq.From(t1.Tags).IntersectByT(linq.From(params), func(str string) string {
return str
}).ToSlice(&rs1)
var rs2 []string
linq.From(t2.Tags).IntersectByT(linq.From(params), func(str string) string {
return str
}).ToSlice(&rs2)
return len(rs1) > len(rs2)
}).ToSlice(&result)
fmt.Printf("%+v", result)
}
[{Name:event1 Tags:[food music gaming sport]} {Name:event4 Tags:[food music gaming sport]} {Name:event7 Tags:[food gaming sport]} {Name:event3 Tags:[music sport]} {Name:event5 Tags:[music coding sport]} {Name:event2 Tags:[gaming]} {Name:event6 Tags:[coding]}]
Above program sorts the array as per your requirements, Hope this will help you.
Related
I need help for transforming this input map into the output map. I try with switch/case and for but I didn't succeed it. Thanks a lot !
Input :
Values{
"toto_voiture_brand": Ad{
"CITROEN": "CITROEN",
},
"toto_voiture_model": Ad{
"CITROEN_toto": "C3",
},
"toto_moto_brand": Ad{
"KAWASAKI": "KAWASAKI",
},
"toto_moto_model": Ad{
"KAWASAKI_tata": "Ninja 1000 SX",
},
"toto_camion_brand": Ad{
"RENAULT": "RENAULT",
"PEUGEOT": "PEUGEOT",
},
"toto_camion_model": Ad{
"RENAULT_toto": "J5",
"PEUGEOT_tata": "255",
},
},
}
Output
Values{
"toto_voiture_model": {
"Citroen": {
{Value: "C3"},
},
},
"toto_moto_model": {
"Kawasaki": {
{Value: "Ninja 1000 SX"},
},
},
"toto_camion_model": {
"RENAULT": {
{Value: "J5"},
},
"PEUGEOT": {
{Value: "255"},
},
},
}
I've tried with switch case and loop for and map. But I don't have the result attendee, I didn't found how to match every map, key and value. Thanks a lot
I should have managed what you need with the following code:
package main
import (
"encoding/json"
"fmt"
"strings"
)
type Output struct {
Value string `json:"Value"`
}
func main() {
// declare output
output := make(map[string]map[string]Output, 0)
// input
input := make(map[string]map[string]string, 0)
input["toto_voiture_brand"] = map[string]string{
"CITROEN": "CITROEN",
}
input["toto_voiture_model"] = map[string]string{
"CITROEN_toto": "C3",
}
input["toto_moto_model"] = map[string]string{
"KAWASAKI_tata": "Ninja 1000 SX",
}
input["toto_camion_model"] = map[string]string{
"RENAULT_toto": "J5",
"PEUGEOT_tata": "255",
}
// transformation
for k, v := range input {
if strings.HasSuffix(k, "_model") {
tempMap := make(map[string]Output, len(v))
for kk, vv := range v {
key := strings.Split(kk, "_")[0]
tempMap[key] = Output{
Value: vv,
}
}
output[k] = tempMap
}
}
data, _ := json.MarshalIndent(&output, "", "\t")
fmt.Println(string(data))
}
I put some comments within the code just to separate sections. The first two parts are only supposed to define your input and output variables.
The section starting with // transformation is a good candidate to become a function but I preferred to leave it within the main function for demo purposes. Let me recap what's happens in the loop:
You range over the entries of your input variable
If the key has the suffix _model, you take it into consideration
You define a locally-scoped map (called tempMap) of type map[string]Output with the right number of elements that we're gonna add
You range over the v variable (that's why we're dealing with nested maps)
For each item, you're gonna add an entry to the tempMap
At the end of the nested loop, you add an entry to the parent map (output)
The last part is only for printing a beautiful JSON that can be easily read and checked.
Note that this code is simplified just to show off how to achieve your goal, adjust it before putting it into production.
Let me know if this helps, thanks!
Suppose we have multiple products with next fields: id, name, type, price, weight.
I want to return products that match some complex filter, for example:
name like '%bla%' and type = 3 - return all products that contains specific substring in name and belongs to specific type
name like '%bla%' and type=4 and weight/price < 10
(name like '%bla%' and type=5) or (name like '%lala%' and type=6 and price < 200)
I don't want to implement separate method for every possible filter.
Repository fetches data from db (I use postgres).
You can do like this:
package main
import(
"fmt"
"regexp"
)
type Items struct{
_id string;
_name string;
_type int;
_price float64;
_weight float64;
}
func (i *Items) filter(bla string) []Items{
items := []Items{
Items{
_id: "i_78676758",
_name: "packet1",
_type: 4,
_price: 44.65,
_weight: 3.6,
},
Items{
_id: "i_546458",
_name: "packet2",
_type: 5,
_price: 234.65,
_weight: 123.6,
},
Items{
_id: "i_5879788",
_name: "packet2",
_type: 5,
_price: 34.65,
_weight: 13.6,
},
Items{
_id: "i_7858758",
_name: "packet3",
_type: 3,
_price: 284.65,
_weight: 23.6,
},
};
var validID = regexp.MustCompile(regexp.QuoteMeta(bla))
new_items := []Items{}
for _, item := range items{
switch{
case ((validID.MatchString(item._name)) && (item._type == 3)):
new_items = items
break;
case ((validID.MatchString(item._name)) && (item._type == 4) && (item._price < 10) && (item._weight < 10)):
new_items = append(new_items, item)
case (((validID.MatchString(item._name)) && item._type == 5) || ((validID.MatchString(item._name)) && (item._type == 6) && (item._price < 200))):
new_items = append(new_items, item)
case ((validID.MatchString(item._name)) && (item._price > 100)):
new_items = append(new_items, item)
default:
}
}
return new_items;
}
func main(){
item := &Items{
_id: "i_7858758",
_name: "packet",
_type: 4,
_price: 234.65,
_weight: 23.6,
}
items := item.filter("et2")
fmt.Println(items)
}
In the above code Items struct holds the data which has filter method which loops over items which is a []Items and filter on the basis of text which is matched by Regexp and switch case to validate other things and finally returns a []Items which holds the filtered data.
So I have an Struct that holds data that has a AddedByUser which links to my User Struct.
What I want to be able to do it remove the UserLevel from the AddedByUser
Now I want to be able to do it from this function only, so using the json:"-" is not an option. That would remove it from all json output. I only want to remove it form this one function.
I should also say that these are Gorm models and when I have been trying to remove the 10 option (UserLevels) it only removes the outer data set not the UserLevel from all of the data.
{
"ID": 1,
"CreatedAt": "2019-01-08T16:33:09.514711Z",
"UpdatedAt": "2019-01-08T16:33:09.514711Z",
"DeletedAt": null,
"UUID": "00000000-0000-0000-0000-000000000000",
"Title": "title000",
"Information": "info999",
"EventDate": "2006-01-02T15:04:05Z",
"AddedByUser": {
"ID": 2,
"CreatedAt": "2019-01-08T15:27:52.435397Z",
"UpdatedAt": "2019-01-08T15:27:52.435397Z",
"DeletedAt": null,
"UUID": "b019df80-a7e4-4397-814a-795e7e84b4ca",
"Firstname": "Me",
"Surname": "admin",
"Password": "....",
"Email": "admin#email.co.uk",
"UserLevel": {
"ID": 0,
"CreatedAt": "0001-01-01T00:00:00Z",
"UpdatedAt": "0001-01-01T00:00:00Z",
"DeletedAt": null,
"LevelTitle": "",
"UserLevel": null
},
So this is what I have tried,
data := []models.MyData{}
data = append(data[0:2])
I have about 14 results, with out the append it loads all the results but with this is only loads two results. The idea was to remove either UpdateAt or Title. As I am not sure if the gorm model information is all 0 or if the slice sees them as 0,1,2,3,4 etc.
I have also tried to range over the slice of models, while I can access each of the sections, I can not seem to find a simple method to remove data by name from a struct? Maps seem to have that but not structs which I am not sure why?
Thanks.
UPDATE
This is the model I am using:
//Model
type MyData struct {
gorm.Model
UUID uuid.UUID
Title string
Information string
EventDate time.Time
AddedByUser Users `gorm:"ForeignKey:added_by_user_fk"`
AddedByUserFK uint
}
//Users Model
type Users struct {
gorm.Model
UUID uuid.UUID
Firstname string
Surname string
Password string
Email string
UserLevel UserLevels `gorm:"ForeignKey:user_level_fk" json:",omitempty"`
UserLevelFK uint
}
As mentioned in the comments, you cannot remove fields from a struct value, because that would yield a value of a different type.
However, you can set fields to their zero value. Combined with the omitempty JSON tag, you can exclude fields from the JSON encoding. To make this work properly, you have to change the UserLevel field to a pointer type (otherwise you end up with empty objects in the JSON document).
Types shortened for brevity:
package main
import (
"encoding/json"
"fmt"
)
type MyData struct {
Title string
AddedByUser Users
}
type Users struct {
ID int
UserLevel *UserLevels `json:",omitempty"` // pointer type with omitempty
}
type UserLevels struct {
LevelTitle string
}
func main() {
var x MyData
x.Title = "foo"
x.AddedByUser.ID = 2
x.AddedByUser.UserLevel = &UserLevels{}
f(x)
b, _ := json.MarshalIndent(x, "", " ")
fmt.Println("main:\n" + string(b))
}
func f(x MyData) {
// "unset" UserLevel. Since we are receiving a copy of MyData, this is
// invisible to the caller.
x.AddedByUser.UserLevel = nil
b, _ := json.MarshalIndent(x, "", " ")
fmt.Println("f:\n" + string(b))
}
// Output:
// f:
// {
// "Title": "foo",
// "AddedByUser": {
// "ID": 2
// }
// }
// main:
// {
// "Title": "foo",
// "AddedByUser": {
// "ID": 2,
// "UserLevel": {
// "LevelTitle": ""
// }
// }
// }
Try it on the playground: https://play.golang.org/p/trUgnYamVOA
Alternatively, you can define new types that exclude the AddedByUser field. However, since this field isn't at the top level, this is a lot of work, and it's easy to forget to update those types when new fields are added to the original types.
If the field were at the top level, the compiler would do most of the work for you, because types that only differ in their field tags can be directly converted to one another:
type MyData struct {
ID int
Title string
}
func main() {
var x MyData
x.ID = 1
x.Title = "foo"
f(x)
}
func f(x MyData) {
type data struct { // same as MyData, except the field tags
ID int
Title string `json:"-"`
}
b, _ := json.MarshalIndent(data(x), "", " ")
fmt.Println("main:\n" + string(b))
}
I have a struct like this:
type ArticleDocument struct {
ArticleID string `json:"article_id" bson:"article_id"`
ArticleTitle string `json:"article_title" bson:"article_title"`
Author string `json:"author" bson:"author"`
Board string `json:"board" bson:"board"`
Content string `json:"content" bson:"content"`
Date string `json:"date" bson:"date"`
IP string `json:"ip" bson:"ip"`
MessageConut struct {
All int `json:"all" bson:"all"`
Boo int `json:"boo" bson:"boo"`
Count int `json:"count" bson:"count"`
Neutral int `json:"neutral" bson:"neutral"`
Push int `json:"push" bson:"push"`
} `json:"message_count,inline" bson:"message_count,inline"`
Messages []interface{} `json:"messages" bson:"messages"`
URL string `json:"url" bson:"url"`
}
In MongoDB, my document looks like:
{
"_id" : ObjectId("5ab1da8133691b034b2be31d"),
"article_id" : "M.1521548086.A.DCA",
"article_title" : "some title",
"author" : "somebody",
"board" : "some board",
"content" : "some content",
"date" : "Tue Mar 20 20:14:42 2018",
"ip" : "1.1.1.1",
"message_conut" : {
"all" : 15,
"boo" : 0,
"count" : 14,
"neutral" : 1,
"push" : 14
},
"messages" : [],
"url" : "https://xxx.xxx.xxx"
}
In my Golang code, i am trying to use mgo to query this document and print it out, however, all document fields are converted to golang struct correctly except the nested document "MessageConut", all the values in "All", "Boo", "Count", ...etc are zero:
message_count":{"all":0,"boo":0,"count":0,"neutral":0,"push":0}"
Could you please guide how do I solve this issue?
I want to do a multi-level array element delete. My Structs are as follows:-
type Company struct {
Id bson.ObjectId `bson:"_id,omitempty"`
CompanyName string
Process []ProcessItem
}
type ProcessItem struct{
SortOrder int
Documents []DocumentTemplate
}
type DocumentTemplate struct {
Id bson.ObjectId `bson:"_id,omitempty"`
TemplateName string
}
I want to delete an object of type DocumentTemplate. The DocumentTemplate is a struct array in ProcessItem which is a struct array in Company struct. I have Company Id(field of struct Company) and TemplateName(field of struct DocumentTemplate).
I tried the below mgo pull query but it is not working.
c := db.C("company")
pullQuery := bson.M{"process": bson.M{"documents.templatename": "xyz"}}
err := c.Update(bson.M{"_id": "123"}, bson.M{"$pull": pullQuery})
Please point out the mistakes I made here. Thanks.
Edit:
Adding one example document for the clarity of the question
{
"_id" : ObjectId("573da7dddd73171e42a84045"),
"companyname" : "AAA",
"process" : [
{
"processname" : "Enquiry",
"sortorder" : 0,
"documents" : [
{
"templatename" : "xyz",
"processname" : "Enquiry"
},
{
"templatename" : "ss",
"processname" : "Enquiry"
}
]
},
{
"processname" : "Converted",
"processtype" : 1,
"sortorder" : 2,
"documents" : [
{
"templatename" : "dd",
"processname" : "Converted"
},
{
"templatename" : "fg",
"processname" : "Converted"
}
]
}
]
}
I need to pull out just one DocumentTemplete record, like the one below:
{
"templatename" : "xyz",
"processname" : "Enquiry"
}
N.B: TemplateName will be unique inside a Company.
You'll need to use the $ positional operator (https://docs.mongodb.com/manual/reference/operator/projection/positional/). In order to be able to use that you'll also have to add to your query the following:
"process.documents.templatename": "xyz"
Your Update statement should look like this:
c := db.C("company")
pullQuery := bson.M{"process.$.documents": bson.M{"templatename": "xyz"}}
err := c.Update(bson.M{"_id": "123", "process.documents.templatename": "xyz"}, bson.M{"$pull": pullQuery})
You can pull values in array from the array in mongo record
change2 := bson.M{
"$pull": bson.M{"sapinfo.systemstatus": bson.M{"$in": tags}},
}