Golang数据库行扫描与动态列数处理的插入记录实现

Golang数据库行扫描与动态列数处理的插入记录实现 我正在尝试编写一个程序,用于从数据库检索数据、修改数据并将数据插入到另一个表中。我只能在运行时获知列名和表名。以下是我目前已经实现的部分代码。唯一还没解决的部分是如何修改检索到的值。任何帮助都将不胜感激。

func ETL() {
	dbconn, err := sql.Open("mssql", "server=localhost;user id=[gouser];password=[password];database=test;Trusted_Connection=yes;connection timeout=30;")
	if err != nil {
		log.Fatal(err)
	}

	rows, err := dbconn.Query("select name, address1, email from t_people")

	cols, err := rows.Columns()
	vals := make([]interface{}, len(cols))
	for i := range cols {
		var ii interface{}
		vals[i] = &ii
	}
	for rows.Next() {
		err = rows.Scan(vals...)
	}


	// vals["name"] = "New Name" ??????????
	// vals["address1"] = "New Address" ??????????
	// vals["email"] = "New Email" ??????????

	tx, _ := dbconn.Begin()

	stmt, txerr := tx.Prepare("insert into t_people_new(name, address1, email) values (?,?,?)")

	if txerr != nil {
		log.Fatal(txerr)
	}

	_, err = stmt.Exec(vals...)

	if err != nil {
		log.Fatal(err)
	}
}

更多关于Golang数据库行扫描与动态列数处理的插入记录实现的实战教程也可以访问 https://www.itying.com/category-94-b0.html

13 回复

没问题!很高兴能帮上忙 😊

更多关于Golang数据库行扫描与动态列数处理的插入记录实现的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


本杰明,

今晚下班回家后(等孩子们睡着后)我会尝试一下。感谢你抽时间回复我的帖子,我真的很感激。

我会从另一个表或初始化文件中读取这些信息。在这个示例中,我认为将SQL硬编码在字符串中会更简单。

@radovskyb

我还有一个问题要请教您。我应该如何处理blob字段?它们通常以[]byte数组的形式返回。

另外,你是每次只检索一条记录还是多条记录?如果是单条记录,你可以使用映射而不是接口。(多条记录也适用,但没那么直接)

我将获取多条记录。

radovskyb:

tx, err := dbconn.Begin() if err != nil { log.Fatalln(err) }

感谢,这个方法有效。

哦,明白了。在你的示例中,你正在从 t_people 表中选择数据,并试图更改 t_people 表的列。在运行时你将如何知道要选择哪些字段和从哪个表进行选择?

radovskyb:

) type Person struct {

你好本杰明,

我正在尝试构建一个供企业使用的工具。在编码过程中我不会知道表的名称,只有在运行时才能获知。

type Person struct {
}

@Shiloh_Goodwin

我建议为表格类型设置一个特定的结构体。以下是我认为您想要实现的示例:

package main

import (
	"database/sql"
	"log"
)

type Person struct {
	Name  string
	Addr  string
	Email string
}

func main() {
	dbconn, err := sql.Open("mssql", "server=localhost;user id=[gouser];password=[password];database=test;Trusted_Connection=yes;connection timeout=30;")
	if err != nil {
		log.Fatal(err)
	}

	var people []*Person

	rows, err := dbconn.Query("select name, address1, email from t_people")
	if err != nil {
		// 处理错误
	}

	for rows.Next() {
		var p Person

		err := rows.Scan(&p.Name, &p.Addr, &p.Email)
		if err != nil {
			// 处理错误
		}

		// 在此处或下方更新结构体中的属性
		// p.Name = "新姓名"

		people = append(people, &p)
	}

	if err := rows.Err(); err != nil {
		// 处理错误
	}

	// 如果未在上方更改,在此处更新人员或个人的值
	people[0].Name = "返回的第一个人员的新姓名"

	// 在此处执行插入操作
}

你可以使用一些映射,如下所示…我在我的电脑上修改了一些内容,创建了一个实际可运行的示例:

package main

import (
	"database/sql"
	"log"

	_ "github.com/mattn/go-sqlite3"
)

func main() {
	dbconn, err := sql.Open("sqlite3", "dbtest.db")
	if err != nil {
		log.Fatal(err)
	}

	rows, err := dbconn.Query("select name, email from users")
	if err != nil {
		log.Fatalln(err)
	}

	cols, err := rows.Columns()
	if err != nil {
		log.Fatalln(err)
	}

	// 列名到值的映射。
	var maps = []map[string]interface{}{}

	for rows.Next() {
		vals := make([]interface{}, len(cols))
		for i := range cols {
			var ii interface{}
			vals[i] = &ii
		}

		err := rows.Scan(vals...)
		if err != nil {
			log.Fatalln(err)
		}

		m := map[string]interface{}{}
		for i, colName := range cols {
			val := vals[i].(*interface{})
			m[colName] = *val
		}

		// 在此处或下方更新映射中的属性。
		m["name"] = "New name"

		maps = append(maps, m)
	}

	if err := rows.Err(); err != nil {
		log.Fatalln(err)
	}

	// 在此处插入。
}

如果你不需要像我的示例那样将记录实际保存在内存中,这里有一个完整的可运行示例,用于更新记录和插入数据。

我展示了一个使表和字段动态化的示例,但我的示例实际上并不具备SQL注入防护功能,仅供参考 :)

package main

import (
	"database/sql"
	"fmt"
	"log"
	"strings"

	_ "github.com/mattn/go-sqlite3"
)

func main() {
	tableName := "users"
	fields := []string{"name", "email"}

	dbconn, err := sql.Open("sqlite3", "dbtest.db")
	if err != nil {
		log.Fatal(err)
	}

	rows, err := dbconn.Query("select name, email from " + tableName)
	if err != nil {
		log.Fatalln(err)
	}

	cols, err := rows.Columns()
	if err != nil {
		log.Fatalln(err)
	}

	// tx for inserting.
	tx, err := dbconn.Begin()
	if err != nil {
		log.Fatalln(err)
	}

	query := fmt.Sprintf("insert into %s_new(%s) values (%s)",
		tableName,
		strings.Join(fields, ", "),
		strings.TrimSuffix(strings.Repeat("?,", len(fields)), ","),
	)

	stmt, txerr := tx.Prepare(query)
	if txerr != nil {
		log.Fatal(txerr)
	}

	for rows.Next() {
		vals := make([]interface{}, len(cols))
		for i := range cols {
			var ii interface{}
			vals[i] = &ii
		}

		err := rows.Scan(vals...)
		if err != nil {
			log.Fatalln(err)
		}

		m := map[string]interface{}{}
		for i, colName := range cols {
			val := vals[i].(*interface{})
			m[colName] = *val
		}

		var args []interface{}

		for _, field := range fields {
			if _, found := m[field]; !found {
				continue
			}

			// Update attributes in map here or further down below.
			m[field] = fmt.Sprintf("%s (New)", m[field])

			args = append(args, m[field])
		}

		_, err = stmt.Exec(args...)
		if err != nil {
			log.Fatal(err)
		}
	}

	if err := rows.Err(); err != nil {
		log.Fatalln(err)
	}

	// Save commits.
	if err := tx.Commit(); err != nil {
		log.Fatalln(err)
	}
}

以下是针对您问题的解决方案,主要解决如何在运行时动态修改扫描到的数据库记录值。您当前的代码已经正确实现了动态列扫描,但缺少对扫描后值的修改机制。由于vals切片存储的是指向接口的指针,您需要先解引用这些指针来访问实际值,然后进行修改。

以下是修改后的完整代码示例,展示了如何动态修改扫描到的值并插入到新表中:

package main

import (
	"database/sql"
	"log"
	"strings"

	_ "github.com/denisenkom/go-mssqldb"
)

func ETL() {
	dbconn, err := sql.Open("mssql", "server=localhost;user id=[gouser];password=[password];database=test;Trusted_Connection=yes;connection timeout=30;")
	if err != nil {
		log.Fatal(err)
	}
	defer dbconn.Close()

	rows, err := dbconn.Query("select name, address1, email from t_people")
	if err != nil {
		log.Fatal(err)
	}
	defer rows.Close()

	cols, err := rows.Columns()
	if err != nil {
		log.Fatal(err)
	}

	vals := make([]interface{}, len(cols))
	for i := range cols {
		var ii interface{}
		vals[i] = &ii
	}

	tx, err := dbconn.Begin()
	if err != nil {
		log.Fatal(err)
	}

	// 构建动态插入语句
	placeholders := make([]string, len(cols))
	for i := range placeholders {
		placeholders[i] = "?"
	}
	insertSQL := "insert into t_people_new(" + strings.Join(cols, ",") + ") values (" + strings.Join(placeholders, ",") + ")"
	
	stmt, err := tx.Prepare(insertSQL)
	if err != nil {
		log.Fatal(err)
	}
	defer stmt.Close()

	for rows.Next() {
		err = rows.Scan(vals...)
		if err != nil {
			log.Fatal(err)
		}

		// 修改扫描到的值
		for i, col := range cols {
			// 解引用指针获取实际值
			valPtr := vals[i].(*interface{})
			actualVal := *valPtr

			// 根据列名修改值
			switch col {
			case "name":
				*valPtr = "New Name"
			case "address1":
				*valPtr = "New Address"
			case "email":
				*valPtr = "New Email"
			default:
				// 保持原值不变
				*valPtr = actualVal
			}
		}

		// 准备执行参数(需要解引用指针获取实际值)
		execArgs := make([]interface{}, len(vals))
		for i := range vals {
			valPtr := vals[i].(*interface{})
			execArgs[i] = *valPtr
		}

		_, err = stmt.Exec(execArgs...)
		if err != nil {
			log.Fatal(err)
		}
	}

	err = tx.Commit()
	if err != nil {
		log.Fatal(err)
	}
}

关键改进点:

  1. 值修改机制:通过解引用vals切片中的指针来访问和修改实际值
  2. 动态SQL构建:根据检索到的列名动态构建插入语句
  3. 事务处理:使用事务确保数据一致性
  4. 错误处理:添加了完整的错误检查

对于值修改部分,代码通过列名匹配来修改特定字段的值。您可以根据实际需求扩展switch语句中的case来修改更多列的值。这种方法保持了代码的灵活性,能够处理运行时确定的列结构。

回到顶部