How to write a gorm function for where clause with dynamic variables - go

I need to create a sql query :
SELECT * FROM users WHERE id = 10 AND name = "Chetan"
Now, gorm's where function looks like below,
// Where return a new relation, filter records with given conditions, accepts `map`, `struct` or `string` as conditions, refer http://jinzhu.github.io/gorm/crud.html#query
func (s *DB) Where(query interface{}, args ...interface{}) *DB {
return s.clone().search.Where(query, args...).db
}
Which mean it accepts a query and args. Example :
dbDriver.Where("id = ?", id).First(t)
How do i dynamically pass multiple variables. Example:
SELECT * FROM users WHERE id = 10 AND name = "Chetan"
SELECT * FROM users WHERE id = 10
SELECT * FROM users WHERE gender = "male" AND name = "Chetan" AND age = "30"
Is it poosible to write a single gorm function for such dynamic SQL statements?

You can use map[string]interface{} for coditions in .Where()
m := make(map[string]interface{})
m["id"] = 10
m["name"] = "chetan"
db.Where(m).Find(&users)
Just add your conditions in map then send inside where.
Or you can use struct in .Where(). Create a variable of struct and set those field for which you want to query and send inside where
db.Where(&User{Name: "chetan", Gender: "Male"}).First(&user)
NOTE: When query with struct, GORM will only query with those fields has non-zero value, that means if your field’s value is 0, '', false or other zero values, it won’t be used to build query conditions.
Referrence: https://gorm.io/docs/query.html#Struct-amp-Map

The first param of .Where() accepts string and the rest is variadic, this means you have the capability to modify the query and the values.
In the below example, I've prepared field1 & field2, and also value1 & value2 for representing the names of the fields I want to filter and their values respectively.
The values can be in any type since it's interface{}.
var field1 string = "id"
var value1 interface{} = 10
var field2 string = "age"
var value2 interface{} = "30"
dbDriver.Where(field1 " = ? AND " + field2 + " = ?", value1, value2).First(t)
Update 1
What if i am not sure what are number of parameter i will be passing? In this case we are hardcoding to two. What if that function/method passes 3 ?
One possible solution to achieve that, is by using slices to hold the criteria. You will have the control to dynamically adjust the fields and values.
fields := []string{"id = ?", "age = ?"}
values := []interface{}{10, "30"}
dbDriver.Where(strings.Join(fields, " AND "), values...).First(t)
Update 2
As per #Eklavya's comment, it's also possible to use a predefined struct object or a map instead of a string on the where clause.
db.Where(&User{Name: "jinzhu", Age: 20}).First(&user)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 20 ORDER BY id LIMIT 1;
db.Where(map[string]interface{}{"name": "jinzhu", "age": 20}).Find(&users)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 20;
Reference: GORM query reference

Related

GORM how to Joins Preloading and user filter as well

I'm new in golang and Gorm
Here is my struct
type SonModel struct {
ID int64
Age int
Name string
FatherID int64
Father FaterModel `gorm:"foreignKey:ID;references:FatherID"`
}
type FaterModel struct {
ID int64
Name string
GrandID int64
Grand GrandModel `gorm:"foreignKey:ID;references:GrandID"`
}
type GrandModel struct {
ID int64
Name string
}
in raw sql what i want is
select son.id,son.name,to_json(father.*) father from son join father on father.id = son.father_id where (son.name like '%name%' or father.name like '%name%') and son.age = 15
i want join and filter with father
in gorm what i'm doing is
db = db.Joins("Father").Preload("Father.Grand")
db = db.Joins("left join father on father.id = son.id left join grand on grand.id = father.grand_id")
db = db.Where("age = ?",15)
db = db.Where("father.name like ? or son.name like ? or grand.name like ?",name)
and i found it left join father and grand twice
first join Father as Father to get father's column
send is my custom left join
how can i Joins("Father") only one time and use its column to filter
Assuming you want to stick with these struct names, there are a couple of things that need to be done.
First, by convention, GORM determines a table name from the struct name. If you want to use different names than that, you need to implement the Tabler interface for each of your models. Something like this:
func (SonModel) Table() string {
return "son"
}
func (FaterModel) Table() string {
return "father"
}
func (GrandModel) Table() string {
return "grand"
}
After this is done, you can write your query like this:
var sons []SonModel
name = fmt.Sprintf("%%%s%%", name) //for example, output in the resulting query should be %John%
err := db.Preload("Father.Grand").
Joins("left join father on father.id = son.father_id").
Joins("left join grand on grand.id = father.grand_id").
Where("sone.age = ?", 15).
Where("son.name like ? or father.name like ? or grand.name like ?", name, name, name).
Find(&sons).Error
I try this code
sql := db.ToSQL(func(tx *gorm.DB) *gorm.DB {
return tx.Model(&SonModel{}).Select("son.id, son.name, father.*").Joins("left join father on father.id = son.id").Where("son.name LIKE ?", "%name%").Where("father.name LIKE ?", "%name%").Where("age = ?", 15).Scan(&SonModel{})
})
fmt.Println(sql)
And the result
SELECT son.id, son.name, father.* FROM "son_models" left join father on father.id = son.id WHERE son.name LIKE '%name%' AND father.name LIKE '%name%' AND age = 15
Is this solve your problem?

computed selected field not present in response

I have this model in gorm with 2 fields which I do not want inside the table as they are computed from the sql query:
type User struct {
ID uint `json:"id" gorm:"primarykey,autoIncrement"`
MutedUsers []*User `json:"-" gorm:"many2many:user_muted_users;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
BlockedUsers []*User `json:"-" gorm:"many2many:user_blocked_users;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
...
IsMuted bool `json:"is_muted" gorm:"-"`
IsBlocked bool `json:"is_blocked" gorm:"-"`
}
Query which computes above fields:
var users []models.User
userMutedQ := database.Instance.
Table("user_muted_users").
Select("1").
Where("user_id = ? and muted_user_id = u.id", 1)
userBlockedQ := database.Instance.
Table("user_blocked_users").
Select("1").
Where("user_id = ? and blocked_user_id = u.id", 1)
database.Instance.
Table("users u").
Select("u.*, "+
"(case when exists(?) then 'true' else 'false' end) as is_muted, "+
"(case when exists(?) then 'true' else 'false' end) as is_blocked, ",
userMutedQ, userBlockedQ,
).
Where("u.id = 1").
Scan(&users)
The sql from the console, when run against the database will produce columns with the right value for is_muted is_blocked (some true some false).
The users list however, have all values for is_muted is_blocked to false. Maybe gorm is ignoring them due to the - flag definition. If I don't use - then gorm will create is_muted is_blocked columns inside my database which are totally redundant since the value is always computed.
What is the right way here?
According to docs (Declaring Models/Field-Level Permission) and new feature (Add field tag to ignore migration and PR), you can use migration directive for - tag to ignore field creation during migration and use -> to readonly field permission:
IsBlocked bool `gorm:"->;-:migration"`

Go-gorm returns all records if filtered with Struct instance that has default Value in a field

Lets say we have the following struct:
type Task struct {
...
Completed bool `gorm:"default:false" json:"-"`
}
There are 5 entries in the MySQL DB:
2 of them have Completed=1
3 of them have Completed=0
I face the following peculiarity:
db, err = gorm.Open("mysql", connstr)
var ret []Task
// This returns 3 rows
db.Debug().Model(&Task{}).Find(&ret, "Completed =?", false)
// This returns 2 rows
db.Debug().Model(&Task{}).Find(&ret, Task{Completed: true})
// BUT THIS RETURNS 5 ROWS!
db.Debug().Model(&Task{}).Find(&ret, Task{Completed: false})
Any ideas why is this happening? The SQL executed at the last call was:
SELECT * FROM 'tasks'
I would like to avoid writing a new SQL query for each struct field. Passing the struct (object) seems more sensible.
Reason
GORM looks at the fields of the struct provided to Find(), and checks to see which fields are set, to construct the query. So in the 2nd example, it sees that Completed is set to true, and adds a WHERE clause to the query:
db.Debug().Model(&Task{}).Find(&ret, Task{Completed: true})
generates:
SELECT * FROM "tasks" WHERE "tasks"."completed" = true
In the second example, GORM has no way of knowing if you passed in Task{Completed: false} or simply Task{} because false is the zero-value of a bool.
The same thing would happen if you had a field that was an int and tried to query for 0:
db.Debug().Model(&Task{}).Find(&ret, Task{Num: 0}) // Generates: SELECT * FROM 'tasks'
Solution
If you really want to use the struct for queries, you can change your model so that Completed is a *bool instead of a bool. Since the zero-value of a pointer is nil, providing a pointer to false will tell GORM to add that clause:
trueBool := true
falseBool := false
db.Debug().Model(&Task{}).Find(&ret, Task{Completed: &falseBool})
db.Debug().Model(&Task{}).Find(&ret, Task{Completed: &trueBool})
generate (respectively)
SELECT * FROM "tasks" WHERE "tasks"."completed" = false
SELECT * FROM "tasks" WHERE "tasks"."completed" = true
Just remember that doing this means that Completed can be saved in the DB as NULL if not set.

Access field value in a function in Power Query M

I want to create a function that gets the first value of a table field if two other field values match the two given function parameters.
I thought this would be easy. But I found nothing in the internet or M documentation that could solve this.
I don't know if I have to loop through a record or if there is a top level function.
= (val1 as text, val2 as text) as text =>
let
result = if [Field1] = val1 and [Field2] = val2 then [Field3] else ""
in
result
As far as I understand your wish, table and column names are hard coded (i.e. you intend to apply the function only for specific table). Then you may use following approach:
// table
let
t1 = #table({"Field1"}, List.Zip({{"a".."e"}})),
t2 = #table({"Field2"}, List.Zip({{"α".."ε"}})),
join = Table.Join(t1&t1,{}, t2&t2,{}),
add = Table.AddIndexColumn(join, "Field3", 0, 1)
in
add
// func
(val1 as text, val2 as text) => Table.SelectRows(table, each [Field1] = val1 and [Field2] = val2)[Field3]{0}
// result
func("d","β") //31

LINQ Lamba Select all from table where field contains all elements in list

I need a Linq statement that will select all from a table where a field contains all elements in a list<String> while searching other fields for the entire string regardless of words.
It's basically just a inclusive word search on a field where all words need to be in the record and string search on other fields.
Ie I have a lookup screen that allows the user to search AccountID or Detail for the entire search string, or search clientID for words inclusive words, I'll expand this to the detail field if I can figure out the ClientId component.
The complexity is that the AccountId and Detail are being searched as well which Basically stops me from doing the foreach in the second example due to the "ors".
Example 1, this gives me an the following error when I do query.Count() afterwards:
query.Count(); 'query.Count()' threw an exception of type 'System.NotSupportedException' int {System.NotSupportedException}
+base {"Local sequence cannot be used in LINQ to SQL implementations of query operators except the Contains operator."} System.SystemException {System.NotSupportedException}
var StrList = searchStr.Split(' ').ToList();
query = query.Where(p => p.AccountID.Contains(searchStr) || StrList.All(x => p.clientID.Contains(x)) || p.Detail.Contains(searchStr));
Example 2, this gives me an any search word type result:
var StrList = searchStr.Split(' ').ToList();
foreach (var item in StrList)
query = query.Where(p => p.AccountID.Contains(searchStr) || p.clientID.Contains(item) || p.Detail.Contains(searchStr));
Update
I have a table with 3 fields, AccountID, ClientId, Details
Records
Id, AccountID, CLientId, Details
1, "123223", "bobo and co", "this client suxs"
2, "654355", "Jesses hair", "we like this client and stuff"
3, "456455", "Microsoft", "We love Mircosoft"
Search examples
searchStr = "232"
Returns Record 1;
searchStr = "bobo hair"
Returns no records
searchStr = "bobo and"
Returns Record 1
searchStr = "123 bobo and"
Returns returns nothing
The idea here is:
if the client enters a partial AccountId it returns stuff,
if the client wants to search for a ClientId they can type and cancel down clients by search terms, ie word list. due to the large number of clients the ClientID Will need to contain all words (in any order)
I know this seems strange but it's just a simple interface to find accounts in a powerful way.
I think there are 2 solutions to your problem.
One is to count the results in memory like this:
int count = query.ToList().Count();
The other one is to not use All in your query:
var query2 = query;
foreach (var item in StrList)
{
query2 = query2.Where(p => p.clientID.Contains(item));
}
var result = query2.Union(query.Where(p => p.AccountID.Contains(searchStr) || p.Detail.Contains(searchStr)));
The Union at the end acts like an OR between the 2 queries.

Resources