QSqlRelationalTableModel 是 Qt 中提供的高级数据库模型类,继承自 QSqlTableModel,专门用于处理关系型数据表的外键关联。它能在显示数据时自动将外键ID转换为关联表的实际值,极大简化了关系数据的展示和编辑。
一、核心特点
-
外键关系处理:自动将外键ID转换为关联表的可读值
-
可编辑性:支持通过视图直接编辑数据
-
组合框支持:在外键字段提供下拉框选择
-
数据一致性:维护关系完整性
二、基本用法示例
#include <QApplication>
#include <QSqlDatabase>
#include <QSqlRelationalTableModel>
#include <QTableView>
#include <QSqlQuery>
#include <QComboBox>
#include <QSqlRelation>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
// 建立内存数据库
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
db.setDatabaseName(":memory:");
if (!db.open()) {
qDebug() << "无法打开数据库";
return 1;
}
// 创建示例表:员工表和部门表
QSqlQuery query;
query.exec("CREATE TABLE departments (id INTEGER PRIMARY KEY, name TEXT)");
query.exec("INSERT INTO departments VALUES (1, '研发部')");
query.exec("INSERT INTO departments VALUES (2, '市场部')");
query.exec("CREATE TABLE employees ("
"id INTEGER PRIMARY KEY, "
"name TEXT, "
"department_id INTEGER, "
"FOREIGN KEY(department_id) REFERENCES departments(id))");
query.exec("INSERT INTO employees VALUES (1, '张三', 1)");
query.exec("INSERT INTO employees VALUES (2, '李四', 2)");
query.exec("INSERT INTO employees VALUES (3, '王五', 1)");
// 创建关系模型
QSqlRelationalTableModel model;
model.setTable("employees");
// 设置部门ID到部门名称的关系
model.setRelation(2, QSqlRelation("departments", "id", "name"));
// 创建视图
QTableView view;
view.setModel(&model);
view.setItemDelegate(new QSqlRelationalDelegate(&view));
// 显示数据
model.select();
view.show();
return app.exec();
}
三、核心方法详解
1. 设置表关系
// 设置关系:参数依次为(外键列索引, 关联表名, 关联ID列, 关联显示列)
model.setRelation(2, QSqlRelation("departments", "id", "name"));
// 设置多个关系
model.setRelation(3, QSqlRelation("positions", "pos_id", "pos_name"));
model.setRelation(4, QSqlRelation("levels", "level_id", "level_name"));
2. 数据操作
// 插入记录
QSqlRecord record = model.record();
record.setValue("name", "赵六");
record.setValue("department_id", 2); // 使用实际ID值
model.insertRecord(-1, record);
// 更新记录
model.setData(model.index(0, 1), "张三丰"); // 修改姓名
model.submitAll(); // 提交更改
// 删除记录
model.removeRow(1); // 删除第二行
model.submitAll();
3. 自定义关系处理
// 自定义关系显示
QVariant MyModel::data(const QModelIndex &index, int role) const
{
if (role == Qt::DisplayRole && index.column() == 2) {
// 获取原始外键值
int deptId = QSqlRelationalTableModel::data(index, Qt::EditRole).toInt();
// 自定义查询关联表
QSqlQuery query;
query.prepare("SELECT short_name FROM departments WHERE id = ?");
query.addBindValue(deptId);
if (query.exec() && query.next()) {
return query.value(0); // 返回简称
}
}
return QSqlRelationalTableModel::data(index, role);
}
四、高级用法
1. 级联关系(多级外键)
// 三级关联:员工->部门->公司
model.setTable("employees");
// 第一级关系:部门
model.setRelation(2, QSqlRelation("departments", "id", "name"));
// 获取部门模型并设置第二级关系:公司
QSqlTableModel *deptModel = model.relationModel(2);
if (deptModel) {
deptModel->setRelation(2, QSqlRelation("companies", "comp_id", "comp_name"));
deptModel->select();
}
2. 自定义委托(增强编辑)
class CustomRelationalDelegate : public QSqlRelationalDelegate
{
public:
QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option,
const QModelIndex &index) const override
{
if (index.column() == 2) { // 部门列
QComboBox *editor = new QComboBox(parent);
// 添加额外选项
editor->addItem("未分配", 0);
// 获取关系模型
QSqlTableModel *relModel = model()->relationModel(index.column());
if (relModel) {
for (int i = 0; i < relModel->rowCount(); ++i) {
int id = relModel->data(relModel->index(i, 0)).toInt();
QString name = relModel->data(relModel->index(i, 1)).toString();
editor->addItem(name, id);
}
}
return editor;
}
return QSqlRelationalDelegate::createEditor(parent, option, index);
}
};
// 使用自定义委托
view.setItemDelegate(new CustomRelationalDelegate());
3. 条件过滤
// 基本过滤
model.setFilter("department_id = 1"); // 只显示研发部员工
model.select();
// 动态过滤
void filterByDepartment(int deptId)
{
model.setFilter(QString("department_id = %1").arg(deptId));
model.select();
}
// 复杂条件过滤
model.setFilter("department_id IN (SELECT id FROM departments WHERE name LIKE '%技术%')");
五、实际应用场景
1. 主从表视图
// 主表:部门
QSqlRelationalTableModel deptModel;
deptModel.setTable("departments");
deptModel.select();
// 从表:员工(根据主表选择过滤)
QSqlRelationalTableModel empModel;
empModel.setTable("employees");
empModel.setRelation(2, QSqlRelation("departments", "id", "name"));
// 连接选择信号
QObject::connect(deptView->selectionModel(), &QItemSelectionModel::currentRowChanged,
[&](const QModelIndex ¤t, const QModelIndex &){
int deptId = deptModel.data(deptModel.index(current.row(), 0)).toInt();
empModel.setFilter(QString("department_id = %1").arg(deptId));
empModel.select();
});
2. 数据验证
bool MyModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
// 验证部门ID是否存在
if (index.column() == 2 && role == Qt::EditRole) {
int deptId = value.toInt();
QSqlQuery query;
query.prepare("SELECT 1 FROM departments WHERE id = ?");
query.addBindValue(deptId);
if (!query.exec() || !query.next()) {
qWarning() << "无效的部门ID";
return false;
}
}
return QSqlRelationalTableModel::setData(index, value, role);
}
3. 级联删除
// 删除部门时同时删除员工
bool deleteDepartment(int deptId)
{
QSqlDatabase::database().transaction();
QSqlQuery query;
query.prepare("DELETE FROM employees WHERE department_id = ?");
query.addBindValue(deptId);
if (!query.exec()) {
QSqlDatabase::database().rollback();
return false;
}
query.prepare("DELETE FROM departments WHERE id = ?");
query.addBindValue(deptId);
if (!query.exec()) {
QSqlDatabase::database().rollback();
return false;
}
QSqlDatabase::database().commit();
return true;
}
六、性能优化技巧
按需加载关系:不是所有外键都需要设置为关系
// 只对需要显示的外键设置关系
model.setRelation(2, QSqlRelation("departments", "id", "name")); // 需要显示
// 不需要显示的status_id不设置关系
批量操作:关闭自动提交
model.setEditStrategy(QSqlTableModel::OnManualSubmit);
// 批量操作...
model.submitAll(); // 最后统一提交
索引优化:确保外键字段有索引
CREATE INDEX idx_employee_dept ON employees(department_id);
延迟加载:大数据量时
model.setTable("large_table");
model.setRelation(2, QSqlRelation("ref_table", "id", "name"));
model.select(); // 初始不加载数据
model.fetchMore(); // 需要时加载
七、常见问题解决方案
问题1:如何显示复合字段?
// 自定义data()方法显示"部门-职位"
QVariant MyModel::data(const QModelIndex &index, int role) const
{
if (index.column() == 2 && role == Qt::DisplayRole) {
int deptId = QSqlRelationalTableModel::data(index, Qt::EditRole).toInt();
QString deptName = QSqlRelationalTableModel::data(index).toString();
// 获取职位
QSqlQuery query;
query.prepare("SELECT position FROM employees WHERE id = ?");
query.addBindValue(data(this->index(index.row(), 0)).toInt());
if (query.exec() && query.next()) {
return QString("%1-%2").arg(deptName).arg(query.value(0).toString());
}
}
return QSqlRelationalTableModel::data(index, role);
}
问题2:如何处理多对多关系?
// 使用中间表处理学生-课程多对多关系
model.setTable("student_course"); // 中间表
model.setRelation(1, QSqlRelation("students", "stu_id", "name"));
model.setRelation(2, QSqlRelation("courses", "course_id", "title"));
// 自定义委托允许选择多个课程
// 需要实现自定义委托创建多选控件
问题3:如何实现级联下拉框?
// 国家->省->市三级联动
model.setTable("addresses");
model.setRelation(1, QSqlRelation("countries", "country_id", "country_name"));
// 自定义委托实现级联
class CascadeDelegate : public QSqlRelationalDelegate {
// 根据国家选择变化动态更新省份下拉框
// 实现createEditor和setModelData
};
八、总结
QSqlRelationalTableModel 是 Qt 中处理关系数据的强大工具:
-
自动处理外键关系,简化开发
-
与视图组件完美集成,支持直接编辑
-
通过 QSqlRelationalDelegate 提供友好的下拉选择
-
适合大多数主从关系数据的展示和编辑场景
对于复杂的关系数据应用,结合自定义模型和委托可以满足各种特殊需求。相比直接使用 QSqlTableModel 或 QSqlQuery,它能显著减少处理外键关系的代码量,提高开发效率。