You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何获取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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 09:27:47