Golang中无法从PostgreSQL扫描datetime的问题
Golang中无法从PostgreSQL扫描datetime的问题 我想在Go Web服务器中使用PostgreSQL数据库,作为第一步,我只想实现对数据库的基本访问。我可以创建表并添加记录,但在检索该记录时,Go在处理时间戳时遇到了问题。
2023/05/24 14:15:36 sql: 扫描列索引3时出错,列名"created_at":不支持的扫描,将driver.Value类型存储到*time.Time类型中
这是出错的那一行代码。
if err := db.QueryRow(query, 1).Scan(&id, &username, &password, &createdAt); err != nil {
我发现有提到MySQL的一个问题,涉及在连接字符串中设置某个参数(parseTime=true),但PostgreSQL没有类似的东西。我刚开始学习Go,我的大部分代码是从别处复制过来的——而且那些代码使用的是MySQL。
package main
import (
"database/sql"
"fmt"
"log"
"time"
_ "github.com/lib/pq"
)
const (
host = "localhost"
port = 5432
user = "postgres"
password = "*******"
dbname = "com_dev"
)
func main() {
psqlInfo := fmt.Sprintf("host=%s port=%d user=%s "+
"password=%s dbname=%s sslmode=disable",
host, port, user, password, dbname)
db, err := sql.Open("postgres", psqlInfo)
if err != nil {
log.Fatal(err)
}
if err := db.Ping(); err != nil {
log.Fatal(err)
}
{ // Create a new table
fmt.Println("Trying to create user table")
query := `
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username TEXT NOT NULL,
password TEXT NOT NULL,
created_at TIMESTAMP
);`
if _, err := db.Exec(query); err != nil {
fmt.Println(err)
//log.Fatal(err)
}
}
{ // Insert a new user
fmt.Println("Trying to add user")
username := "johndoe"
password := "secret"
createdAt := time.Now()
result, err := db.Exec("INSERT INTO users (username, password, created_at) VALUES ($1, $2, $3)", username, password, createdAt)
if err != nil {
fmt.Println("add failed")
log.Fatal(err)
}
id, err := result.LastInsertId()
fmt.Println(id)
}
{ // Query a single user
fmt.Println("Trying to find user")
var (
id int
username string
password string
createdAt time.Time
)
query := "SELECT id, username, password, created_at FROM users WHERE id = $1"
if err := db.QueryRow(query, 1).Scan(&id, &username, &password, &createdAt); err != nil {
log.Fatal(err)
}
fmt.Println(id, username, password, createdAt)
}
}
更多关于Golang中无法从PostgreSQL扫描datetime的问题的实战教程也可以访问 https://www.itying.com/category-94-b0.html
我是从一个教程里复制粘贴的代码,那个教程就是这么做的。它用的是MySQL;也许timestamp不是内置的?或者它想说明某个观点?
更多关于Golang中无法从PostgreSQL扫描datetime的问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
我已切换到 pgx… 结果相同。
不过,这促使我删除了 pgAdmin 中的表,当我再次运行程序时,它成功了,所以我认为问题出在数据库中存在错误数据。
感谢您的帮助,这让我走上了正确的道路!
它使用了MySQL
我确实了解MySQL,但我认为让数据库创建时间戳很方便。
但问题是时间戳是否存储在数据库中?你是否使用pgAdmin4来“管理”数据库?强烈推荐用它来排查问题。它有一个内置的查询工具。
而且我同意 @Dean_Davidson 的观点,“pgx”是一个不错的选择。
引用:
query := ` CREATE TABLE users ( id SERIAL PRIMARY KEY, username TEXT NOT NULL, password TEXT NOT NULL, created_at TIMESTAMP );`
但你为什么不让PostgreSQL自动创建时间戳呢?
created_at TIMESTAMP default CURRENT_TIMESTAMP,
… 你能在pgAdmin4中看到时间戳吗?它被存储了吗?
问题出在PostgreSQL的TIMESTAMP类型与Go的time.Time类型之间的映射上。PostgreSQL的TIMESTAMP默认返回的是time.Time不直接支持的类型。以下是解决方案:
// 修改查询部分,使用类型转换或调整数据库类型
{ // Query a single user
fmt.Println("Trying to find user")
var (
id int
username string
password string
createdAt time.Time
)
// 方法1:在SQL查询中转换类型
query := "SELECT id, username, password, created_at::timestamp FROM users WHERE id = $1"
// 或者方法2:使用明确的类型转换
// query := "SELECT id, username, password, created_at AT TIME ZONE 'UTC' FROM users WHERE id = $1"
// 或者方法3:修改表结构使用timestamptz类型
// CREATE TABLE users (
// id SERIAL PRIMARY KEY,
// username TEXT NOT NULL,
// password TEXT NOT NULL,
// created_at TIMESTAMPTZ
// );
if err := db.QueryRow(query, 1).Scan(&id, &username, &password, &createdAt); err != nil {
log.Fatal(err)
}
fmt.Println(id, username, password, createdAt)
}
更完整的解决方案是修改表结构使用TIMESTAMPTZ:
{ // Create a new table
fmt.Println("Trying to create user table")
query := `
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username TEXT NOT NULL,
password TEXT NOT NULL,
created_at TIMESTAMPTZ
);`
if _, err := db.Exec(query); err != nil {
fmt.Println(err)
}
}
如果无法修改表结构,可以使用自定义扫描:
import (
"database/sql/driver"
"time"
)
type NullTime struct {
Time time.Time
Valid bool
}
func (nt *NullTime) Scan(value interface{}) error {
if value == nil {
nt.Time, nt.Valid = time.Time{}, false
return nil
}
switch v := value.(type) {
case time.Time:
nt.Time, nt.Valid = v, true
return nil
case []byte:
t, err := time.Parse("2006-01-02 15:04:05", string(v))
if err != nil {
return err
}
nt.Time, nt.Valid = t, true
return nil
case string:
t, err := time.Parse("2006-01-02 15:04:05", v)
if err != nil {
return err
}
nt.Time, nt.Valid = t, true
return nil
}
return fmt.Errorf("unsupported type: %T", value)
}
func (nt NullTime) Value() (driver.Value, error) {
if !nt.Valid {
return nil, nil
}
return nt.Time, nil
}
// 使用自定义类型
{ // Query a single user
fmt.Println("Trying to find user")
var (
id int
username string
password string
createdAt NullTime
)
query := "SELECT id, username, password, created_at FROM users WHERE id = $1"
if err := db.QueryRow(query, 1).Scan(&id, &username, &password, &createdAt); err != nil {
log.Fatal(err)
}
if createdAt.Valid {
fmt.Println(id, username, password, createdAt.Time)
}
}
最简单的解决方案是使用TIMESTAMPTZ类型创建表,这样Go的time.Time可以直接正确映射。


