如何获取QSqlQuery批量插入数据后的所有插入行ID?
批量插入QSqlQuery后获取所有插入行ID的解决方案
问题描述
使用QSqlQuery执行批量插入时,原代码无法通过query.next()获取插入的ID(始终返回false),而QSqlQuery::lastInsertId()仅适用于单条插入场景,批量插入时行为未定义,Qt文档明确说明:
如果数据库支持,返回最近插入行的对象ID。如果查询未插入任何值或数据库不返回ID,则返回无效的QVariant。如果插入操作影响多行,行为未定义。
原代码示例:
QString queryContent = "INSERT INTO table (field1, field2) VALUES"; bool insertedFirstEntry = false; for (const auto& data : dataCollection) { if (insertedFirstEntry) queryContent += ","; else insertedFirstEntry = true; queryContent += QString(" ('%1', '%2')").arg(data.field1).arg(data.field2); } QSqlQuery query(db); const bool result = query.exec(queryContent); std::vector<int> insertedIds; if (result) { while (query.next()) insertedIds.push_back(query.value(0).toInt()); } return insertedIds;
可行解决方案
方案1:事务包裹单条插入(通用兼容所有数据库)
将批量拆分为单条插入,用事务保证原子性,每次插入后调用lastInsertId()获取当前行ID:
std::vector<int> insertedIds; QSqlQuery query(db); // 开启事务 if (!db.transaction()) return insertedIds; // 预编译插入语句,避免SQL注入,提升性能 if (!query.prepare("INSERT INTO table (field1, field2) VALUES (:field1, :field2)")) { db.rollback(); return insertedIds; } for (const auto& data : dataCollection) { query.bindValue(":field1", data.field1); query.bindValue(":field2", data.field2); if (!query.exec()) { db.rollback(); return {}; } // 获取当前插入行的ID QVariant id = query.lastInsertId(); if (id.isValid()) insertedIds.push_back(id.toInt()); } // 提交事务 if (db.commit()) return insertedIds; else { db.rollback(); return {}; }
该方案兼容性最强,不受数据库类型限制,同时预编译语句能避免SQL注入风险。
方案2:利用数据库的INSERT ... RETURNING特性(针对支持的数据库)
如果使用的数据库支持RETURNING子句(如PostgreSQL、MySQL 8.0+、SQLite 3.35+),可以直接在批量插入语句后添加RETURNING id,执行后通过query.next()遍历获取所有ID:
std::vector<int> insertedIds; QString queryContent = "INSERT INTO table (field1, field2) VALUES"; bool insertedFirstEntry = false; int index = 0; for (const auto& data : dataCollection) { if (insertedFirstEntry) queryContent += ","; else insertedFirstEntry = true; // 使用占位符避免SQL注入 queryContent += QString(" (:f1_%1, :f2_%1)").arg(index++); } // 添加RETURNING子句获取所有插入的ID queryContent += " RETURNING id"; QSqlQuery query(db); index = 0; for (const auto& data : dataCollection) { query.bindValue(QString(":f1_%1").arg(index), data.field1); query.bindValue(QString(":f2_%1").arg(index++), data.field2); } if (query.exec()) { while (query.next()) { insertedIds.push_back(query.value(0).toInt()); } } return insertedIds;
注意:该方案依赖数据库特性,需确认所用数据库版本支持RETURNING,必须使用参数绑定避免SQL注入,禁止直接拼接用户输入的数据。
方案3:预先获取ID范围(仅适合单连接无并发场景)
部分数据库可以在插入前获取自增ID的当前值,插入后计算ID范围,但该方法存在并发风险,仅适用于单连接、无其他写入操作的场景:
std::vector<int> insertedIds; QSqlQuery query(db); // 获取当前自增ID最大值(假设ID是自增主键) query.exec("SELECT MAX(id) FROM table"); int startId = 0; if (query.next()) startId = query.value(0).toInt(); // 执行批量插入 QString queryContent = "INSERT INTO table (field1, field2) VALUES"; bool insertedFirstEntry = false; for (const auto& data : dataCollection) { if (insertedFirstEntry) queryContent += ","; else insertedFirstEntry = true; queryContent += QString(" ('%1', '%2')").arg(data.field1).arg(data.field2); } bool result = query.exec(queryContent); if (result) { int rowsInserted = query.numRowsAffected(); for (int i = 1; i <= rowsInserted; ++i) { insertedIds.push_back(startId + i); } } return insertedIds;
警告:若插入过程中有其他连接写入数据,会导致ID范围不准确,不推荐在生产环境的并发场景使用。
内容的提问来源于stack exchange,提问作者Mickaël C. Guimarães
相关产品推荐
相关产品推荐

