如何重复 SQL 插入直到 pg-promise 成功?

How to repeat SQL insertion until successful with pg-promise?

在我的程序中,我将一些数据插入 table 并取回它的 ID,我需要确保将该 ID 输入到另一个 table 中,并使用随机生成的唯一字符串。但是,如果尝试插入一个已经存在的随机字符串而导致插入失败,我该如何重复插入直到成功?

我正在使用 pg-promise 与 postgreSQL 对话。我可以 运行 编写这样的程序,在随机字符串不存在的情况下将数据插入到两个 table 中:

   db.none(
            `
            WITH insert_post AS
            (
                INSERT INTO table_one(text) VALUES('abcd123')
                RETURNING id
            )
            INSERT INTO table_two(id, randstr)
                    VALUES((SELECT id FROM insert_post), '${randStrFn()}')
            `
        )
    .then(() => console.log("Success"))
    .catch(err => console.log(err));

我不确定是否有任何基于 SQL/JS/pg-promise 的简单解决方案可供我使用。

最简单的方法是将它放入一个方法中,然后在 catch 中重新调用它:

const insertPost = (post, numRetries) => {
    return
       db.none(
                `
                WITH insert_post AS
                (
                    INSERT INTO table_one(text) VALUES('abcd123')
                    RETURNING id
                )
                INSERT INTO table_two(id, randstr)
                        VALUES((SELECT id FROM insert_post), '${randStrFn()}')
                `
            )
        .then(() => console.log("Success"))
        .catch(err => {
            console.log(err)
            if (numRetries < 3) {
              return self.insertPost(post, numRetries + 1);
            }
            throw err;
        });

}

我鼓励问题的作者寻求一个纯粹的SQL解决方案来解决他的问题,因为就性能而言,它比其他任何方法都更有效率。

但由于问题是关于如何使用 pg-promise 重新 运行 查询,除了已经发布的示例之外,我将提供一个示例,除了不获取和释放每个连接尝试,加上适当的数据完整性。

db.tx(t => {
    // BEGIN;
    return t.one('INSERT INTO table_one(text) VALUES() RETURNING id', 'abcd123', a => +a.id)
        .then(id => {
            var f = attempts => t.none('INSERT INTO table_two(id, randstr) VALUES(, randStrFn())', id)
                .catch(error => {
                    if (--attempts) {
                        return f(attempts); // try again
                    }
                    throw error; // give up
                });
            return f(3); // try up to 3 times
        });
})
    .then(data => {
        // COMMIT;
        // success, data = null
    })
    .catch(error => {
        // ROLLBACK;
    });

因为你正在尝试重新运行一个依赖查询,你不应该让第一个查询保持成功,如果你对第二个查询的所有尝试都失败了,你应该回滚所有更改,即使用事务 - 方法 tx,如代码所示。

这就是我们在交易中拆分您的 WITH 查询以确保完整性的原因。

更新

下面是它的更好版本。因为事务内部的错误需要隔离,为了避免破坏事务堆栈,每次尝试都应该在自己的内部SAVEPOINT,这意味着使用另一个事务级别:

db.tx(t => {
    // BEGIN;
    return t.one('INSERT INTO table_one(name) VALUES() RETURNING id', 'abcd123', a => +a.id)
        .then(id => {
            var f = attempts => t.tx(sp => {
                // SAVEPOINT level_1;
                return sp.none('INSERT INTO table_two(id, randstr) VALUES(, randStrFn())', id);
            })
                .catch(error => {
                    // ROLLBACK TO SAVEPOINT level_1;
                    if (--attempts) {
                        return f(attempts); // try again
                    }
                    throw error; // give up
                });
            return f(3); // try up to 3 times
        });
})
    .then(data => {
        // 1) RELEASE SAVEPOINT level_1;
        // 2) COMMIT;
    })
    .catch(error => {
        // ROLLBACK;
    });

我还建议使用 pg-monitor,这样您就可以看到并理解下面发生的事情,以及实际上正在执行的查询。


P.S。我是 pg-promise.

的作者