【问题标题】:How to get the ID of a previously inserted row within a transaction using QSqlQuery如何使用 QSqlQuery 在事务中获取先前插入的行的 ID
【发布时间】:2012-11-27 23:08:59
【问题描述】:

我试图在事务范围内获取插入行的主键,因为我不想让数据库处于逻辑上不一致的状态。

我的问题是我找不到一种方法来检索以前执行的查询的 ID 值,我想将其用于下一个插入查询。在事务生效时查询 PostgreSQL 数据库在非外键表中显示没有结果(该行尚未提交?)。我相信这是由于事务的隔离级别造成的。

以下是我正在尝试对生产代码执行的操作,尽管为了清晰起见,对其进行了轻微编辑并缩小为一个功能。 const int lastInsertId 始终为 0,在这种情况下应该意味着没有找到任何值(从技术上讲,toInt() 函数失败了)。我尝试手动插入一个有效的非外键行,然后调用LASTVAL() 产生预期的结果 - 插入行的 ID。

那么,我做错了什么?我在这里遗漏或误解了什么?

void createEntityWithoutForiegnKeyConstraint(const QString &nameOfEntity)
{
  db_.transaction();

  QSqlQuery insertQuery(db_);
  insertQuery.prepare("INSERT INTO \"EntityWithoutForeignKey\" (\"name\") VALUES (:name);");

  insertQuery.bindValue(":name", nameOfEntity);
  execQuery(__LINE__, insertQuery);
  QSqlQuery lastIdQuery("SELECT LASTVAL();", db_); // auto executes
  const int lastInsertId = lastIdQuery.value(0).toInt();

  if (lastInsertId <= 0) // 0 is not a valid ID
    throw exception("Oh noes.");

  createEntityWithForeignKeyConstraint(lastInsertId, someData);

  if (!db_.commit())
    db_.rollback();
}

【问题讨论】:

  • 同意; INSERT ... RETURNING 是这里的理想选择。详情请参阅INSERT documentation。
  • 已确认 RETURNING 从 pgAdmin 用于测试插入,例如 INSERT INTO nonforeignkeyentity ("id", "name") VALUES (DEFAULT, 'teste') RETURNING "id"。但这不适用于QSqlQuery,所以我在这里仍然需要指导。如何从查询对象中检索该值。我必须为返回值绑定一个变量吗?
  • insertQuery.record().indexOf("id"); 返回 0,根据 Qt 文档,这是一个有效的索引。我真的很慌张。如果该字段存在,为什么那里没有值?
  • 您可能必须在获取值之前调用 insertQuery.next()。

标签: qt transactions qtsql


【解决方案1】:

我意识到这是一个老问题,但在 Qt 5.10(可能更早)中有一个函数 QSqlQuery::lastInsertId() 可以在 QSqlQuery::exec() 之后调用。

如果您使用的数据库(例如 SQLite)不支持 INSERT 语句中的 RETURNING 子句,这将非常有用。

QSqlQuery::lastInsertId() documentation.

用法大致如下:

QSqlQuery q;
q.prepare("INSERT INTO table_name VALUES(:some_column_name)");
q.bindValue(":some_column_name", "FooBar");
q.exec();

qDebug() << "Last ID was:" << q.lastInsertId();

【讨论】:

    【解决方案2】:

    @Kasheen 完全正确,但我想将重点放在您应该牢记的一个重要方面:不要忘记将所有内容封装在一个事务中。

    为什么?如果使用生成的主键(您使用q.lastInsertId())进行第二次插入失败,它可以节省时间并避免数据库损坏。

    所以你的插入应该是这样的(这基本上是@Kasheen 的回答,有一些补充):

    db_.transaction(); // Starts a transaction
    
    QSqlQuery q;
    
    // first insert
    q.prepare("INSERT INTO table_name VALUES(:some_column_name)");
    q.bindValue(":some_column_name", "FooBar");
    q.exec();
    
    auto pk == q.lastInsertId().toInt;
    
    // second insert
    q.prepare("INSERT INTO other_table VALUES(:other_column_name, :fk)");
    q.bindValue(":other_column_name", "OtherText");
    q.bindValue(":fk", pk);
    q.exec();
    
    db_.commit(); // Commits transaction
    

    @average joe 可以正确处理事务,但如果只看解决方案,您可能会忘记它。

    【讨论】:

      猜你喜欢
      • 2019-11-02
      • 1970-01-01
      • 2011-04-20
      • 1970-01-01
      • 2018-09-15
      • 2013-07-01
      • 1970-01-01
      • 1970-01-01
      • 2018-08-07
      相关资源
      最近更新 更多