# SQL 構建器
## 原生 SQL
原生 SQL 查詢
```
type Result struct {
ID int
Name string
Age int
}
var result Result
db.Raw("SELECT id, name, age FROM users WHERE name = ?", 3).Scan(&result)
db.Raw("SELECT id, name, age FROM users WHERE name = ?", 3).Scan(&result)
var age int
DB.Raw("select sum(age) from users where role = ?", "admin").Scan(&age)
```
執行原生 SQL
```
db.Exec("DROP TABLE users")
db.Exec("UPDATE orders SET shipped_at=? WHERE id IN ?", time.Now(), []int64{1,2,3})
// SQL 表達式
DB.Exec("update users set money=? where name = ?", gorm.Expr("money * ? + ?", 10000, 1), "jinzhu")
```
**注意** GORM 允許緩存準備好的語句來提高性能,詳情請參考 [性能](../教程/performance.md)
## `Row` & `Rows`
獲取 `*sql.Row` 結果
```
// 使用 GORM API 構建 SQL
row := db.Table("users").Where("name = ?", "jinzhu").Select("name", "age").Row()
row.Scan(&name, &age)
// 使用原生 SQL
row := db.Raw("select name, age, email from users where name = ?", "jinzhu").Row()
row.Scan(&name, &age, &email)
```
獲取 `*sql.Rows` 結果
```
// 使用 GORM API 構建 SQL
rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Select("name, age, email").Rows()
defer rows.Close()
for rows.Next() {
rows.Scan(&name, &age, &email)
// 業務邏輯...
}
// 原生 SQL
rows, err := db.Raw("select name, age, email from users where name = ?", "jinzhu").Rows()
defer rows.Close()
for rows.Next() {
rows.Scan(&name, &age, &email)
// 業務邏輯...
}
```
轉到 [FindInBatches](./advanced_query.md) 獲取如何在批量中查詢和處理記錄的信息, 轉到 [Group 條件](./advanced_query.md#group_conditions) 獲取如何構建復雜 SQL 查詢的信息
## 命名參數
GORM 支持 [`sql.NamedArg`](https://tip.golang.org/pkg/database/sql/#NamedArg) 或 `map[string]interface{}{}` 命名參數,例如:
```
DB.Where("name1 = @name OR name2 = @name", sql.Named("name", "jinzhu")).Find(&user)
// SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu"
DB.Where("name1 = @name OR name2 = @name", map[string]interface{}{"name": "jinzhu2"}).First(&result3)
// SELECT * FROM `users` WHERE name1 = "jinzhu2" OR name2 = "jinzhu2" ORDER BY `users`.`id` LIMIT 1
DB.Raw("SELECT * FROM users WHERE name1 = @name OR name2 = @name2 OR name3 = @name", sql.Named("name", "jinzhu1"), sql.Named("name2", "jinzhu2")).Find(&user)
// SELECT * FROM users WHERE name1 = "jinzhu1" OR name2 = "jinzhu2" OR name3 = "jinzhu1"
DB.Exec("UPDATE users SET name1 = @name, name2 = @name2, name3 = @name", sql.Named("name", "jinzhunew"), sql.Named("name2", "jinzhunew2"))
// UPDATE users SET name1 = "jinzhunew", name2 = "jinzhunew2", name3 = "jinzhunew"
DB.Raw("SELECT * FROM users WHERE (name1 = @name AND name3 = @name) AND name2 = @name2", map[string]interface{}{"name": "jinzhu", "name2": "jinzhu2"}).Find(&user)
// SELECT * FROM users WHERE (name1 = "jinzhu" AND name3 = "jinzhu") AND name2 = "jinzhu2"
```
## 將 `sql.Rows` 掃描至 struct
```
rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Select("name, age, email").Rows() // (*sql.Rows, error)
defer rows.Close()
for rows.Next() {
var user User
// ScanRows 將一行掃描至 user
db.ScanRows(rows, &user)
// 業務邏輯...
}
```
## DryRun 模式
在不執行的情況下生成 `SQL` ,可以用于準備或測試生成的 SQL,詳情請參考 [Session](../教程/session.md)
```
stmt := DB.Session(&Session{DryRun: true}).First(&user, 1).Statement
stmt.SQL.String() //=> SELECT * FROM `users` WHERE `id` = $1 ORDER BY `id`
stmt.Vars //=> []interface{}{1}
```
## 高級
### Clauses
GORM 內部使用 SQL builder 生成 SQL。對于每個操作,GORM 都會創建一個 `*gorm.Statement` 對象,所有的 GORM API 都是在為 `statement` 添加/修改 `Clause`,最后,GORM 會根據這些 Clause 生成 SQL
例如,當通過 `First` 進行查詢時,它會在 `Statement` 中添加以下 Clause
```go
clause.Select{Columns: "*"}
clause.From{Tables: clause.CurrentTable}
clause.Limit{Limit: 1}
clause.OrderByColumn{
Column: clause.Column{Table: clause.CurrentTable, Name: clause.PrimaryKey},
}
```
然后 GORM 在回調中構建最終的查詢 SQL,像這樣:
```go
Statement.Build("SELECT", "FROM", "WHERE", "GROUP BY", "ORDER BY", "LIMIT", "FOR")
```
生成 SQL:
```go
SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1
```
您可以自定義 `Clause` 并與 GORM 一起使用,這需要實現 [Interface](https://pkg.go.dev/gorm.io/gorm/clause?tab=doc#Interface) 接口
可以參考 [示例](https://github.com/go-gorm/gorm/tree/master/clause)
### Clause 構建器
不同的數據庫, Clause 可能會生成不同的 SQL,例如:
```go
db.Offset(10).Limit(5).Find(&users)
// SQL Server 會生成
// SELECT * FROM "users" OFFSET 10 ROW FETCH NEXT 5 ROWS ONLY
// MySQL 會生成
// SELECT * FROM `users` LIMIT 5 OFFSET 10
```
之所以支持 Clause,是因為 GORM 允許數據庫驅動程序通過注冊 Clause Builder 來取代默認值,這兒有一個 [Limit](https://github.com/go-gorm/sqlserver/blob/512546241200023819d2e7f8f2f91d7fb3a52e42/sqlserver.go#L45) 的示例
### Clause 選項
GORM 定義了很多 [Clause](https://github.com/go-gorm/gorm/tree/master/clause),其中一些 Clause 提供了你可能會用到的選項
盡管很少會用到它們,但如果你發現 GORM API 與你的預期不符合。這可能可以很好地檢查它們,例如:
```go
DB.Clauses(clause.Insert{Modifier: "IGNORE"}).Create(&user)
// INSERT IGNORE INTO users (name,age...) VALUES ("jinzhu",18...);
```
### StatementModifier
GORM 提供了 [StatementModifier](https://pkg.go.dev/gorm.io/gorm?tab=doc#StatementModifier) 接口,允許您修改語句,使其符合您的要求,這兒有一個 [Hint](http://v2.gorm.io/zh_CN/docs/hints.html) 示例
```go
import "gorm.io/hints"
DB.Clauses(hints.New("hint")).Find(&User{})
// SELECT * /*+ hint */ FROM `users`
```