违反唯一约束的行导致整个 pq.CopyIn postgresql 导入失败

unique constraint violation on a row causes entire pq.CopyIn postgresql import to fail

我正在尝试使用 pq.CopyIn 进行批量导入,如下所述:

https://godoc.org/github.com/lib/pq

导入比我尝试过的其他方法快得多,但我发现仅在一条记录中违反唯一约束将导致整个导入失败。

有什么方法可以使用 pq.CopyIn.

设置 ON CONFLICT DO NOTHING

这是我的 table 结构的副本

CREATE TABLE test (
    id serial PRIMARY KEY,
    unique_token VARCHAR ( 10 ) UNIQUE NOT NULL,
    frequency INT DEFAULT 0
);

我尝试使用下面的@mkopriva 回答,但出现错误:pq: null “id”列中的值违反了非空约束

下面的代码示例

tx, _ := db.Begin()

_, err = tx.Exec(`CREATE TEMP TABLE token_temp ON COMMIT DROP AS 
   SELECT id, unique_token FROM test WITH NO DATA`)
if err != nil {
    return err
}

stmt, err := tx.Prepare(pq.CopyIn("token_temp", "unique_token"))
if err != nil {
    fmt.Println("error here")
    return err
}

for _, token := range tokenList {
    _, err = stmt.Exec(token)

    if err != nil {
        return err
    }

}

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

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

_, err = tx.Exec(`INSERT INTO test SELECT id, unique_token FROM 
  token_temp   ON CONFLICT(unique_token) DO UPDATE SET frequency= 
   test.frequency + 1 `)
if err != nil {
    fmt.Println("Error")
    return err
}

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

pq.CopyIn 内部使用不支持 ON CONFLICT 子句的 COPY FROM

但是,您可以做的是创建一个没有约束的临时 table,将数据复制到该临时 table,然后执行 INSERT目标 table,使用 ON CONFLICT 子句,使用临时 table 作为要插入的数据源。

一个例子应该更清楚,假设你有一个 users table 看起来像这样:

CREATE TABLE users (
    id serial PRIMARY KEY
    , name text
    , email text UNIQUE
);

假设您有一部分这样的用户:

var users = []User{
    {Name: "John Doe", Email: "jdoe@example.com"},
    {Name: "Joe Blow", Email: "jblow@example.com"},
    {Name: "Jane Doe", Email: "jdoe@example.com"}, // duplicate email!
    {Name: "Foo Bar", Email: "fbar@example.com"},
}

有了它,您可以执行以下操作:

_, err = txn.Exec(`
CREATE TEMP TABLE users_temp
ON COMMIT DROP
AS SELECT * FROM users
WITH NO DATA`)
if err != nil {
    panic(err)
}

stmt, err := txn.Prepare(pq.CopyIn("users_temp", "name", "email"))
if err != nil {
    panic(err)
}

for _, u := range users {
    if _, err := stmt.Exec(u.Name, u.Email); err != nil {
        panic(err)
    }
}
if _, err := stmt.Exec(); err != nil {
    panic(err)
}
if err := stmt.Close(); err != nil {
    panic(err)
}

_, err = txn.Exec(`
INSERT INTO users (name, email)
SELECT name, email FROM users_temp
ON CONFLICT DO NOTHING`)
if err != nil {
    panic(err)
}

if err := txn.Commit(); err != nil {
    panic(err)
}

在你 运行 上面你可以做 SELECT * FROM users; 你会得到这个:

 id |   name   |       email
----+----------+-------------------
  1 | John Doe | jdoe@example.com
  2 | Joe Blow | jblow@example.com
  4 | Foo Bar  | fbar@example.com
(3 rows)

对于您的具体示例和要求,您可以在 INSERT ... SELECT ... 查询中执行类似的操作:

_, err = txn.Exec(`
INSERT INTO test (unique_token, frequency)
SELECT unique_token, COUNT(*) FROM token_temp
GROUP BY unique_token`)
if err != nil {
    panic(err)
}