gorm day6

gorm day6

  • SQL构建器

原生SQL

原生SQL和Scan

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)

var users []User
db.Raw("UPDATE users SET name = ? WHERE age = ? RETURNING id, name", "jinzhu", 20).Scan(&users)

解读:
这个例子展示了在 GORM 中如何使用原生 SQL 语句(通过 db.Raw 方法)执行查询,并将查询结果映射到 Go 的结构体或变量中(通过 Scan 方法)。这是在需要执行复杂查询或数据库特定操作时非常有用的功能,特别是当这些操作不能直接通过 GORM 的模型方法轻松实现时。

查询并映射到单个结构体实例

type Result struct {
  ID   int
  Name string
  Age  int
}

var result Result
db.Raw("SELECT id, name, age FROM users WHERE name = ?", "jinzhu").Scan(&result)

这段代码定义了一个 Result 结构体,用于接收查询结果
使用 db.Raw 执行了一个 SQL 查询,查询 users 表中 name 字段等于 “jinzhu” 的记录的 id、name 和 age 字段。
通过 Scan(&result) 将查询结果映射到 result 实例中

查询并映射到单个变量

var age int
db.Raw("SELECT SUM(age) FROM users WHERE role = ?", "admin").Scan(&age)

这段代码执行一个 SQL 查询来计算 role 为 “admin” 的所有 users 记录的 age 字段之和。
查询结果(年龄总和)被映射到一个整型变量 age 中。

使用 RETURNING 语句并映射到结构体切片

var users []User
db.Raw("UPDATE users SET name = ? WHERE age = ? RETURNING id, name", "jinzhu", 20).Scan(&users)

这段代码执行一个 UPDATE 操作,将 age 为 20 的所有 users 记录的 name 更新为 “jinzhu”
利用了支持 RETURNING 语句的数据库(如 PostgreSQL),该语句允许在更新操作完成后返回被更新记录的某些字段。
通过 Scan(&users),将返回的 id 和 name 字段的值映射到 users 切片中的 User 结构体实例。
注意这个users的结果是由RETURNING实现的。它是 SQL 语句的一部分,允许在执行 INSERT、UPDATE 或 DELETE 操作后直接返回被影响(插入、更新、删除)行的指定列数据。这个特性主要由像 PostgreSQL 这样的数据库系统支持。

总结:注意事项
1.当使用 db.Raw 和 Scan 进行操作时,确保 SQL 语句的正确性和与结构体字段的匹配。
2.使用原生 SQL 时,需要注意 SQL 注入的风险,确保查询参数的安全性。
3.RETURNING 语句的使用依赖于特定的数据库系统是否支持该特性。

这里提到一个SQL注入,那么这里做一个了解:
什么是SQL注入
SQL注入(SQL Injection)是一种常见的安全漏洞,它允许攻击者在应用程序的数据库查询中插入或“注入”恶意SQL命令。这种攻击可以用来获取、修改、删除数据库中存储的数据,甚至有可能获取到数据库服务器的更多权限。
如何发生
SQL注入通常发生在应用程序将用户输入作为查询或命令的一部分直接传递给SQL数据库时。 如果用户输入没有被正确地清理或转义,攻击者就可以通过构造特定的输入来改变原本的查询逻辑。
示例
假设有一个简单的Web应用程序,用户可以输入用户名来查询自己的信息,后端服务代码如下:

String query = "SELECT * FROM users WHERE username = '" + userInput + "'";

如果用户输入是正常的用户名,比如john,那么查询就会是:

SELECT * FROM users WHERE username = 'john';

但是,如果攻击者输入的是’ OR ‘1’='1,那么查询会变成:

SELECT * FROM users WHERE username = '' OR '1'='1';

由于’1’='1’总是为真,这个查询将返回users表中的所有记录,从而泄露了数据库中的敏感信息。

防范措施:
1.使用参数化查询:这是预防SQL注入的最有效手段之一。通过使用参数化查询(也称为预处理语句),应用程序可以让数据库区分代码和数据,即使输入含有SQL代码,数据库也不会执行它。
2.验证和清理用户输入:尽量验证用户输入的数据是否符合预期的格式,对于不符合的输入进行清理或拒绝。
3.使用ORM(对象关系映射)工具:大多数现代的ORM工具都会自动处理参数化查询,从而减少直接编写SQL语句的需要。
4.最小权限原则:确保应用程序连接数据库的账户仅具有执行当前操作所需的最小权限,避免使用具有高级权限的数据库账户。

Exec执行原生SQL

db.Exec("DROP TABLE users")
db.Exec("UPDATE orders SET shipped_at = ? WHERE id IN ?", time.Now(), []int64{1, 2, 3})

// Exec with SQL Expression
db.Exec("UPDATE users SET money = ? WHERE name = ?", gorm.Expr("money * ? + ?", 10000, 1), "jinzhu")

解读:
上面的代码展示了如何在 GORM 中使用 db.Exec 方法执行原生 SQL 语句。db.Exec 用于执行没有返回结果的 SQL 命令,如 INSERT、UPDATE、DELETE、DROP 等。这对于执行数据库迁移、更新数据记录或执行任何不需要返回结果集的操作非常有用。
示例解释
删除表

db.Exec("DROP TABLE users")

这行代码执行一个 DROP TABLE SQL 命令,用于删除数据库中的 users 表。
使用 db.Exec 执行此类操作时应格外小心,因为一旦执行,相关的数据和表结构将被永久删除。

更新记录

db.Exec("UPDATE orders SET shipped_at = ? WHERE id IN ?", time.Now(), []int64{1, 2, 3})

1.这行代码更新 orders 表中 id 为 1、2 或 3 的记录,将它们的 shipped_at 字段设置为当前时间。
2.? 占位符用于预防 SQL 注入攻击,它们将被方法调用时提供的参数(time.Now() 和一个整数切片 []int64{1, 2, 3})替换。
3.注意,对于 IN 子句,直接传递一个切片作为参数是不被所有数据库支持的,这个例子可能在特定的 GORM 驱动或数据库版本中有特别的处理。在一些情况下,你可能需要为 IN 子句中的每个元素使用单独的占位符。

使用 SQL 表达式更新记录

db.Exec("UPDATE users SET money = ? WHERE name = ?", gorm.Expr("money * ? + ?", 10000, 1), "jinzhu")

1.这行代码使用了 gorm.Expr 来构造一个复杂的 SQL 表达式,用于更新 users 表中 name 字段值为 “jinzhu” 的记录的 money 字段。
2.gorm.Expr(“money * ? + ?”, 10000, 1) 表达式计算每个匹配记录的新 money 值,将现有的 money 值乘以 10000 再加上 1。
3.这种方式允许在更新操作中直接使用 SQL 函数或表达式,提供了额外的灵活性。

总结
这些例子展示了如何在 GORM 中使用 db.Exec 执行各种数据库操作,包括如何安全地使用参数化查询以防止 SQL 注入,以及如何在更新操作中利用 SQL 表达式。这些操作提供了对数据库的直接控制,使开发者能够执行复杂和高级的数据库操作。

命名参数

GORM支持sql.NamedArg、map[string]interface{}{}或struct形式的命名参数,例如:

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

// 原生 SQL 及命名参数
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"

type NamedArgument struct {
    Name string
    Name2 string
}

db.Raw("SELECT * FROM users WHERE (name1 = @Name AND name3 = @Name) AND name2 = @Name2",
     NamedArgument{Name: "jinzhu", Name2: "jinzhu2"}).Find(&user)
// SELECT * FROM users WHERE (name1 = "jinzhu" AND name3 = "jinzhu") AND name2 = "jinzhu2"

这个之前也学过,就是@的那个就是占位符。
这个例子主要就是教你GORM如何支持使用命名参数进行数据库查询和更新操作。命名参数提供了一种更清晰、更易读的方式来指定 SQL 语句中的参数,尤其是在参数多次出现或者查询很复杂的情况下。GORM 支持多种方式来指定这些命名参数,包括 sql.NamedArg、map[string]interface{} 和结构体。

使用 sql.NamedArg

db.Where("name1 = @name OR name2 = @name", sql.Named("name", "jinzhu")).Find(&user)

通过 sql.Named 创建命名参数 @name,并在 Where 条件中使用。
这使得 SQL 查询更加清晰,尤其是当同一个参数需要在查询中多次使用时。 注意这里都用的@name。

使用 map[string]interface{}

db.Where("name1 = @name OR name2 = @name", map[string]interface{}{"name": "jinzhu2"}).First(&result3)

使用字典(map)为查询提供命名参数,其中键为参数名,值为参数值。
这种方法在处理动态参数或者参数数目变化时非常灵活。

使用结构体

type NamedArgument struct {
    Name string
    Name2 string
}

db.Raw("SELECT * FROM users WHERE (name1 = @Name AND name3 = @Name) AND name2 = @Name2",
     NamedArgument{Name: "jinzhu", Name2: "jinzhu2"}).Find(&user)

直接使用一个结构体实例来提供命名参数GORM 会自动将结构体的字段名和值映射为 SQL 语句中的命名参数和它们的值。
这种方法特别适用于参数结构复杂或需要重复使用的情况。

DryRun模式

在不执行的情况下生成SQL及其参数,可以用于准备或测试生成的SQL

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}

解读:
什么是DryRun模式
DryRun 模式是 GORM 提供的一种功能,允许开发者构建和准备 SQL 语句而不实际执行它。这在测试和调试过程中特别有用,因为你可以检查生成的 SQL 语句和它的参数是否符合预期,而不需要对数据库进行实际的读写操作。这种模式可以帮助开发者理解 GORM 如何转换高级查询操作到底层的 SQL 语句,同时也有助于优化查询性能。

示例解释:

stmt := db.Session(&Session{DryRun: true}).First(&user, 1).Statement

可能有疑问,Statement是什么?这里我进行解读:
在 GORM 中,Statement 是一个结构体,它代表了构建 SQL 语句过程中的所有相关信息,包括 SQL 字符串、绑定的变量(参数)、模型信息、表名等。当你使用 GORM 构建一个数据库查询或操作时,GORM 内部会创建并填充一个 Statement 对象,以表示和管理这个操作的所有细节。

再来解读例子:
db.Session(&Session{DryRun: true}):这一部分创建了一个新的数据库会话(Session),并通过设置 DryRun: true 开启 DryRun 模式。在 DryRun 模式下,GORM 会构建 SQL 语句和填充参数,但不会实际执行这个语句。

.First(&user, 1):这一部分指示 GORM 构建一个查询,用于获取主键为 1 的第一个 user 记录。这个操作会导致 GORM 填充一个 Statement 对象,其中包含了生成的 SQL 语句和用于查询的参数(在这个例子中,参数就是 1)。

.Statement:这是对前面操作生成的 Statement 对象的引用。 通过这个属性,你可以访问到 GORM 构建的 SQL 语句 (stmt.SQL.String()) 和参数 (stmt.Vars)。

简单来说,Statement 对象是 GORM 用来表示一个即将执行或已经构建的 SQL 操作的内部表示。通过访问这个对象,你可以获取到很多关于这个操作的详细信息,比如:

stmt.SQL.String():返回 SQL 语句的字符串表示。
stmt.Vars:返回 SQL 语句中用到的所有参数值的切片。

总结:
在开启了 DryRun 模式的情况下,通过查看 Statement 对象,你可以检查 GORM 为执行的操作生成的确切 SQL 语句和参数,这对于调试和优化查询非常有用。

ToSQL

返回生成的SQL但不执行。
GORM使用database/sql的参数占位符来构建SQL语句,它会自动转义参数以避免SQL注入,但我们不保证生成SQL的安全,请只用于调试。

gosql := DB.ToSQL(func(tx *gorm.DB) *gorm.DB {
  return tx.Model(&User{}).Where("id = ?", 100).Limit(10).Order("age desc").Find(&[]User{})
})
sql //=> SELECT * FROM "users" WHERE id = 100 AND "users"."deleted_at" IS NULL ORDER BY age desc LIMIT 10

这段内容介绍了 GORM 提供的 DB.ToSQL 方法,这个方法允许你获取一个查询的 SQL 表达式而不实际执行它。这是在调试或优化你的数据库查询时非常有用的功能,因为它可以帮助你理解 GORM 如何将你的查询操作转换为底层的 SQL 语句。

功能解释
1.据库/SQL 参数占位符:GORM 构建 SQL 语句时,使用 database/sql 包的参数占位符规则。这意味着所有的参数(如查询中的 ?)都会被安全地处理和转义,以避免 SQL 注入攻击的风险。
2.自动转义参数:通过自动转义参数值,GORM 增加了构建的 SQL 语句的安全性。这一过程是自动完成的,无需开发者手动干预。
3.安全警告:尽管 GORM 会自动转义参数以防止 SQL 注入,但开发者仍需谨慎,不要对生成的 SQL 完全放心。尤其是在动态构建 SQL 语句时,开发者应该始终警惕潜在的安全漏洞。GORM 提醒,生成的 SQL 应该只用于调试目的。

示例代码解析:

gosql := DB.ToSQL(func(tx *gorm.DB) *gorm.DB {
  return tx.Model(&User{}).Where("id = ?", 100).Limit(10).Order("age desc").Find(&[]User{})
})

DB.ToSQL 方法接收一个函数作为参数。这个函数以 *gorm.DB 类型的 tx 为参数,并返回一个 *gorm.DB 实例,表示一个数据库操作或查询。

在这个函数内部,构建了一个针对 User 模型的查询,条件是 id = 100,限制返回结果的数量为 10 条,并按 age 字段降序排序。使用 .Find(&[]User{}) 来尝试检索满足条件的 User 记录。

DB.ToSQL 不会执行这个查询,而是返回这个查询对应的 SQL 字符串给 gosql 变量。

sql //=> SELECT * FROM "users" WHERE id = 100 AND "users"."deleted_at" IS NULL ORDER BY age desc LIMIT 10

上述 SQL 语句是 DB.ToSQL 方法生成的查询的字符串表示。注意到,如果 User 模型支持软删除(即含有 deleted_at 字段),生成的 SQL 还会自动包含检查 “users”.“deleted_at” IS NULL 的条件,这是 GORM 软删除机制的一部分。

总之, DB.ToSQL 是 GORM 提供的一个非常有用的调试工具,允许开发者查看由高级查询操作生成的实际 SQL 语句,而无需执行这些操作。这有助于优化查询性能和确保查询逻辑的正确性。

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)

解读:
这段内容展示了如何在 GORM 中使用 Row() 方法获取单个 *sql.Row 结果,并如何使用 Scan() 方法将这个单行结果的列值扫描到变量中。这是在需要处理单条查询结果时的一种常用方法,特别是当你只期望查询返回一条记录或者只对返回结果的第一条记录感兴趣时。

使用 GORM API 构建 SQL

row := db.Table("users").Where("name = ?", "jinzhu").Select("name", "age").Row()
row.Scan(&name, &age)

db.Table(“users”) 指定了要查询的表为 users。
.Where(“name = ?”, “jinzhu”) 添加了一个查询条件,即查找 name 字段值为 “jinzhu” 的记录。
.Select(“name”, “age”) 指定了只选择 name 和 age 这两个字段的值。
.Row() 方法构建了查询并返回一个表示单个结果行的 *sql.Row 对象。
row.Scan(&name, &age) 方法将 Row 中的 name 和 age 字段的值分别扫描到 name 和 age 这两个变量中。

使用原生 SQL

row := db.Raw("select name, age, email from users where name = ?", "jinzhu").Row()
row.Scan(&name, &age, &email)

db.Raw(…).Row() 使用原生 SQL 语句执行查询,并获取单行结果。这里的 SQL 语句是 “select name, age, email from users where name = ?”,查询条件是查找 name 字段值为 “jinzhu” 的记录,并选择 name、age 和 email 这三个字段。
row.Scan(&name, &age, &email) 方法将单个结果行中的 name、age 和 email 字段值扫描到对应的变量中。

总结
无论是使用 GORM 的链式API构建查询,还是直接使用原生 SQL,Row() 方法都是用来获取查询结果中的第一行(或唯一一行)数据,而 Scan() 方法用于将这行数据的各个列值映射到提供的变量中。这种方法适用于处理那些只返回单个结果行的查询,如根据主键查询或当你只需要查询结果的第一条记录时。

获取*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)

  // 业务逻辑...
}

这个和上面是同理的,只是Rows可以包含多个查询的结果。
然后对这个rows进行遍历处理就行了。

将sql.Rows扫描至model

将sql.Rows扫描至model,使用ScanRows将一行记录扫描至struct,例如:

rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Select("name, age, email").Rows() // (*sql.Rows, error)
defer rows.Close()

var user User
for rows.Next() {
  // ScanRows 将一行扫描至 user
  db.ScanRows(rows, &user)

  // 业务逻辑...
}

解读:
这段内容展示了如何在 GORM 中处理通过 Rows() 方法获取的多行查询结果。Rows() 方法执行一个查询,并返回一个 *sql.Rows 结果集,它是一个迭代器,可以用来遍历所有的查询结果行。使用 db.ScanRows(rows, &user) 可以将当前迭代到的每一行数据映射到一个模型实例(在这个例子中是 User 结构体)。

步骤解析
1.执行查询并获取结果集

rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Select("name, age, email").Rows()

这行代码执行了一个查询,选取 name 为 “jinzhu” 的用户的 name、age 和 email 字段。
db.Model(&User{}) 指定查询的模型是 User。
Where(“name = ?”, “jinzhu”) 添加了一个过滤条件,以限制结果只包含 name 字段等于 “jinzhu” 的记录。
Select(“name, age, email”) 指明只选择 name、age 和 email 这三个字段。
*Rows() 返回一个包含查询结果的 sql.Rows 对象以及可能的错误。

2.关闭结果集:

defer rows.Close()

使用 defer 关键字确保结果集 rows 在处理完毕后被关闭,以释放数据库连接资源。

3.遍历结果集

var user User
for rows.Next() {
  db.ScanRows(rows, &user)
  // 业务逻辑...
}

for rows.Next() 循环遍历结果集中的每一行。
db.ScanRows(rows, &user) 将当前行的数据扫描到 user 实例中。这一步是将数据库的行记录映射到 Go 的结构体实例中允许后续在 Go 代码中直接使用这些数据。
在循环体内部,可以执行任何需要的业务逻辑,比如处理或展示扫描到的 user 数据。

注意事项:
当处理完 *sql.Rows 结果集后,一定要调用 rows.Close() 来释放相关资源。这是通过 defer 在获取结果集后立即声明的,确保无论函数如何返回,结果集都会被正确关闭。
在循环中使用 db.ScanRows 方法扫描每一行至模型实例时,需要注意该实例的字段应与 Select 方法中指定的列对应。如果字段类型不匹配或字段缺失,ScanRows 可能会失败。
这种方法适用于需要处理每一行结果并可能执行一些行级别的业务逻辑时。如果只是简单地查询并加载所有结果到一个切片中,使用 Find 方法会更简单直接。

Connection

在同一个db tcp连接中允许多个SQL(不在事务中)

db.Connection(func(tx *gorm.DB) error {
  tx.Exec("SET my.role = ?", "admin")

  tx.First(&User{})
})

解读:
这段内容展示了如何在 GORM 中使用 db.Connection 方法来在同一个数据库连接中执行多个 SQL 操作。 db.Connection 方法允许你在其回调函数中执行多个操作,这些操作会共享同一个底层的数据库 TCP 连接,而不需要每次操作都开启一个新的连接。这可以提高数据库操作的效率,尤其是当你需要执行多个相关的数据库操作但又不想每次操作都创建新的连接时。

实例解析:

db.Connection(func(tx *gorm.DB) error {
  tx.Exec("SET my.role = ?", "admin")

  tx.First(&User{})
})

db.Connection 方法接受一个回调函数,这个回调函数的参数 tx 是一个 *gorm.DB 实例,代表当前的数据库连接你可以在这个函数内部使用 tx 来执行多个数据库操作。

tx.Exec(“SET my.role = ?”, “admin”):这行代码使用当前的数据库连接执行一个 SET 操作,可能用于设置会话级别的参数或执行某些预处理操作。请注意,这里的 SQL 语句 “SET my.role = ?” 仅作为示例,实际的 SQL 语法和可用的操作会根据你使用的数据库系统而有所不同。

tx.First(&User{}):紧接着,使用相同的连接执行另一个操作,比如获取 User 表中的第一条记录。这说明在 db.Connection 的回调函数中,所有操作都在同一个数据库连接上执行,而不是每次操作都开启新的连接。

注意事项
1.使用 db.Connection 方法可以提高操作效率,因为它减少了创建和销毁数据库连接的需要,尤其是在需要连续执行多个数据库操作的场景中。

2.虽然这种方法在非事务性的场景下共享同一个数据库连接,但需要注意它并不提供事务性的保证。如果需要事务性的操作(即要么全部成功,要么全部失败),应该使用 db.Transaction 方法来确保操作的原子性。

3.示例中的 tx.Exec(“SET my.role = ?”, “admin”) 可能并不适用于所有数据库,因为不同的数据库管理系统(DBMS)有不同的会话级别的设置命令。务必根据你使用的具体数据库系统调整这些命令。

总之,db.Connection 方法是一个在同一个数据库连接上执行多个操作的有效手段,适用于那些不需要事务保证但希望提高操作效率的场景。

高级应用

子句(clause)

什么是子句
**子句(Clause)在编程和数据库查询语言(如 SQL)的上下文中,是构成查询和命令的各个独立部分。**在 SQL 中,一个完整的语句可能包含多个子句,每个子句都承担不同的角色,指定了查询或命令的某个特定方面。

在 SQL 中的例子
考虑一个简单的 SQL 查询语句:

SELECT name, age FROM users WHERE age > 18 ORDER BY age DESC LIMIT 10;

这个查询包含了几个不同的子句:
SELECT 子句:指定了要从数据库表中检索的列(name, age)。
FROM 子句:指定了查询的目标表(users)。
WHERE 子句:定义了查询的条件(age > 18),只有满足这个条件的记录才会被包含在结果中。
ORDER BY 子句:指定了结果的排序方式(按 age 降序)。
LIMIT 子句:限制了查询结果的数量(最多 10 条记录)。

在编程框架中的例子
在使用数据库操作框架(如 GORM)时,子句的概念同样适用。框架内部使用类似的结构来构建和表示 SQL 语句,允许开发者以编程的方式指定各个子句的内容。
例如,当你在 GORM 中构建一个查询时:

db.Select("name, age").From("users").Where("age > ?", 18).OrderBy("age DESC").Limit(10)

这里每个方法(Select、From、Where、OrderBy、Limit)实际上都在构建或添加相应的子句到最终生成的 SQL 查询中。

子句的作用
**灵活性:**通过分解查询和命令为多个子句,SQL 提供了极高的灵活性,允许开发者精确地指定他们想要执行的操作。
**可读性:**子句的分解也使得 SQL 语句更易于阅读和理解,特别是对于复杂的查询。
**扩展性:**在编程框架中,通过以编程方式操作子句,可以轻松扩展和自定义查询逻辑,适应各种复杂的业务需求。

现在来看文档上的例子:

GORM使用SQL builder在内部生成SQL,对于每个操作,GORM创建一个*gorm.Statement对象,所有GORM API添加/更改Clause,Statement最后,GORM基于这些子句生成SQL。
例如,当使用查询时 First,它将以下子句添加到statement

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 构建过程涉及使用一系列子句(Clauses)来组合和生成 SQL 语句。*在内部,GORM 通过操作 gorm.Statement 对象来管理这些子句。Statement 对象是每次操作的核心,它携带了构建 SQL 所需的所有信息,包括要查询的表、选择的列、条件限制、排序规则等。

解析示例:
当你使用 First 方法进行查询时,GORM 内部会向 Statement 添加一组特定的子句来构造相应的 SQL 查询。这些子句分别代表 SQL 语句的不同部分:
1.clause.Select{Columns: " * ”}: 这个子句指定了查询应返回的列。在这个例子中,“*” 表示选择所有列。
2.clause.From{Tables: clause.CurrentTable}: 指定了查询的表。clause.CurrentTable 是一个动态引用,代表当前操作的模型对应的表。
3.clause.Limit{Limit: 1}: 这个子句限制了查询结果的数量。Limit: 1 指明只查询第一条记录,这是 First 方法的核心行为。
4.clause.OrderByColumn{Column: clause.Column{Table: clause.CurrentTable, Name: clause.PrimaryKey},}: 指定了查询结果的排序方式。这里,它按照当前表的主键进行排序。这保证了 First 方法能够返回表中的“第一条”记录,尽管“第一条”的具体含义取决于表的主键排序。

结合子句生成 SQL
在构建了所有必要的子句后,GORM 将它们组合成完整的 SQL 语句。这个过程涉及将子句转换为对应的 SQL 文本,并用正确的顺序和格式拼接它们。
GORM 负责处理子句中的参数和占位符,确保生成的 SQL 语句既符合预期的操作,又能避免常见的安全问题,如 SQL 注入攻击。

然后在GORM构建最终在Query回调中查询SQL,例如:

Statement.Build("SELECT", "FROM", "WHERE", "GROUP BY", "ORDER BY", "LIMIT", "FOR")

生成SQL:

SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1

你可以自己定义自己的clause并与GORM一起使用,它需要实现接口。

解读:
这段内容讲述了 GORM 如何通过组合不同的子句(clauses)来构建 SQL 查询,并提供了一个示例来展示这个过程。同时,它也提到了开发者可以自定义子句并将其与 GORM 一起使用的可能性。

GORM 的 SQL 构建过程
GORM 使用一系列标准的 SQL 子句(如 SELECT, FROM, WHERE, GROUP BY, ORDER BY, LIMIT, FOR)来构建查询。这些子句对应于 SQL 语言的关键组成部分,它们定义了一个查询的不同方面:

SELECT 指定要返回的字段。
FROM 指定查询的数据表。
WHERE 提供查询的条件。
GROUP BY 指定如何将行分组。
ORDER BY 指定结果的排序方式
LIMIT 限制返回的记录数
FOR 可以用于锁定选定的行。

Statement.Build 方法接受这些子句作为参数,并按照给定的顺序组合它们来生成最终的 SQL 语句。例如,调用:

Statement.Build("SELECT", "FROM", "WHERE", "GROUP BY", "ORDER BY", "LIMIT", "FOR")

可以构建一个完整的 SQL 查询,具体的 SQL 字符串取决于每个子句的具体内容。

示例SQL
给出的示例 SQL 查询:

SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1

这个查询从 users 表中选择所有列(*),结果根据 id 字段进行排序,并且只返回一条记录(LIMIT 1)。

自定义子句
GORM 允许开发者定义自己的子句来扩展或修改标准的查询行为。为了实现这一点,你需要创建一个实现了特定接口的 Go 类型。这个接口定义了子句应该如何被构建和应用到 SQL 查询中。通过这种方式,可以添加新的查询功能或修改现有逻辑以满足特定的需求。

还有一点语法上面的问题:

SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1

这个语句中users和id用了一个反引号,这里我大概是直到它想表达users.id的意思。这里我们来看看:
在 SQL 语句中,users.id 使用了点(.)符号来引用特定表中的列。这种语法用于明确指定一个列名和它所属的表,特别是在涉及多个表的查询中,这样可以避免列名冲突或歧义。
使用场景
当你的查询只涉及一个表时,直接使用列名(如 id)通常就足够了。但在涉及多个表的查询中,如果不同的表中有相同名称的列,就需要使用 表名.列名 的格式来明确指定你要引用的是哪个表的哪个列。

为什么不能直接写users.id,而要写成`users`.`id`

在 SQL 中,使用反引号( )围绕表名和列名是一种特定的语法,用于确保数据库正确解释那些可能与 SQL 关键字冲突或包含特殊字符的标识符。这种做法在不同的数据库管理系统(DBMS)中有所不同,反引号特别是在 MySQL 中使用。其他数据库系统可能使用双引号(" ")或方括号([ ])来达到类似的目的。

为什么使用反引号?
避免关键字冲突: 如果表名或列名与 SQL 的保留关键字相同或包含空格、特殊字符,不使用特定的引号可能会导致查询解析错误。例如,order 是 SQL 的一个保留关键字,用作排序数据的关键字。如果有一个列名叫做 order,直接写 SELECT order FROM table 可能会导致错误,因为数据库会误认为 order 指的是排序操作。在这种情况下,使用反引号写作 SELECT order FROM table 可以消除歧义。
特殊字符:表名或列名包含空格、破折号(-)、或其他特殊字符时,使用反引号可以确保名称被正确解释。

示例
不使用反引号:

SELECT users.id FROM users ORDER BY users.id LIMIT 1;

使用反引号:

SELECT `users`.`id` FROM `users` ORDER BY `users`.`id` LIMIT 1;

在第一个例子中,如果 users 和 id 都不是保留关键字,也不包含特殊字符,这个查询通常不会有问题。但第二个例子确保了即使 users 或 id 是保留关键字或包含特殊字符,查询也能被数据库系统正确解析和执行。

总结
使用反引号是为了确保 SQL 查询在包含特殊名称或遇到潜在的关键字冲突时仍能正确执行。
具体是否需要使用反引号,以及如何使用,取决于你使用的具体数据库系统的语法规则。MySQL 中常用反引号,而 SQL Server 使用方括号,PostgreSQL 默认使用双引号。
在没有特殊需要时,为了保持查询的简洁性,可以不使用这些特殊引号,但在开发中应该了解并根据情况正确使用它们以避免潜在的问题。

子句构造器

**对于不同的数据库,子句可能会产生不同的SQL,**例如:

db.Offset(10).Limit(5).Find(&users)
// Generated for SQL Server
// SELECT * FROM "users" OFFSET 10 ROW FETCH NEXT 5 ROWS ONLY
// Generated for MySQL
// SELECT * FROM `users` LIMIT 5 OFFSET 10

支持是因为GORM允许数据库驱动注册Clause Builder替换默认的,以limit为例。

子句选项

GORM定义了许多子句,并且一些子句提供了可用于你的应用程序的高级选项。
尽管它们中的大多数都很少使用,但如果您发现GORM公共API无法满足您的要求,不妨检查一下,例如:

db.Clauses(clause.Insert{Modifier: "IGNORE"}).Create(&user)
// INSERT IGNORE INTO users (name,age...) VALUES ("jinzhu",18...);

解读:
这个例子展示了如何在使用 GORM 进行数据库操作时,通过 Clauses 方法添加特定的子句来修改 SQL 语句的行为。具体来说,它演示了如何使用 clause.Insert 子句,并通过设置 Modifier 为 “IGNORE” 来生成一个带有 INSERT IGNORE 命令的 SQL 语句。

INSERT IGNORE 的含义
INSERT IGNORE 语句在尝试向数据库表中插入数据时,如果遇到违反唯一约束(如主键或唯一索引冲突)的情况,不会执行插入操作,也不会抛出错误。相反,它会忽略当前的插入尝试,并继续执行后续的操作(如果有的话)。
这与普通的 INSERT 语句不同,后者在遇到此类约束冲突时会导致错误。
使用 INSERT IGNORE 可以在某些情况下简化代码逻辑,特别是当你希望简单地忽略那些因为唯一约束而无法插入的记录,而不是处理错误。

示例解释

db.Clauses(clause.Insert{Modifier: "IGNORE"}).Create(&user)

db.Clauses(clause.Insert{Modifier: “IGNORE”}):这里通过 Clauses 方法添加了一个 clause.Insert 子句,其中 Modifier 属性被设置为 “IGNORE”。这指示 GORM 生成一个带有 IGNORE 关键字的 INSERT 语句。
.Create(&user):执行插入操作,尝试将 user 对象的数据插入到 users 表中。由于使用了 IGNORE 修饰符,如果存在任何违反唯一约束的情况,这次插入会被安静地忽略,不会导致错误。

生成的SQL:
INSERT IGNORE INTO users (name, age…) VALUES (“jinzhu”, 18…);

这条 SQL 语句尝试将新记录插入到 users 表中。如果记录的 name、age 等字段的值违反了表的唯一约束,插入操作将被忽略,而不是引发错误。

语句修饰符

GORM提供接口StatementModifier允许你修改语句以匹配你的要求,以Hints为例:

import "gorm.io/hints"

db.Clauses(hints.New("hint")).Find(&User{})
// SELECT * /*+ hint */ FROM `users`

解读:
这段内容介绍了 GORM 如何使用 StatementModifier 接口来允许开发者自定义和修改 SQL 语句,以满足特定的数据库查询优化需求或适应数据库的特定特性。特别地,它通过 hints 包中的 New 函数演示了如何添加 SQL hint(提示)到查询中。

SQL Hint(提示)
SQL Hint 是一种特殊的注释或指令,可以嵌入到 SQL 语句中,向数据库管理系统(DBMS)提供如何执行查询的提示。不同的数据库支持不同的 hint,它们可以用来影响查询计划的选择、优化查询性能等。

示例解释

import "gorm.io/hints"

db.Clauses(hints.New("hint")).Find(&User{})

导入 hints 包:首先,通过 import “gorm.io/hints” 导入 GORM 的 hints 包,该包提供了用于添加 SQL hint 的功能。
使用 hints.New(“hint”):hints.New(“hint”) 创建了一个新的 hint 实例,其中 “hint” 是你想要添加到 SQL 查询中的具体 hint 文本。
通过 Clauses 方法应用 hint:db.Clauses(hints.New(“hint”)) 将这个 hint 作为一个子句添加到查询中。Clauses 方法是 GORM 提供的一种方式,用于向操作添加自定义或修改后的子句。
执行查询:Find(&User{}) 执行查询,这里尝试检索所有的 User 记录。

*SELECT * /*+ hint / FROM users

在生成的 SQL 语句中,*可以看到 hint 被添加为 SQL 注释的形式 /*+ hint /。这种形式确保了即使数据库不支持或不理解这个 hint,SQL 语句仍然能够被执行,因为数据库会忽略这种注释。
在这个例子中,hint 是占位符,实际使用时应替换为具体的、数据库支持的 hint 文本。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值