Overview
Qt’s SQL module provides a unified API for relational databases. SQLite is the most common choice for embedded and desktop Qt apps — no server required, single file database.
Qt SQL Stack
┌──────────────────────────────────────┐
│ QSqlTableModel / QSqlQueryModel │ ← for Model/View
│ QSqlQuery │ ← direct queries
│ QSqlDatabase │ ← connection
│ Qt SQL Driver (QSQLITE, QMYSQL...) │ ← plugin layer
└──────────────────────────────────────┘
CMakeLists.txt Setup
find_package(Qt6 REQUIRED COMPONENTS Sql)
target_link_libraries(MyApp PRIVATE Qt6::Sql)
Connect to SQLite
#include <QSqlDatabase>
#include <QSqlError>
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
db.setDatabaseName("/var/data/sensors.db"); // file path, or ":memory:"
if (!db.open()) {
qWarning() << "DB open failed:" << db.lastError().text();
return;
}
qDebug() << "Database opened";
Create Table
#include <QSqlQuery>
QSqlQuery q;
q.exec(R"(
CREATE TABLE IF NOT EXISTS sensor_readings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp TEXT NOT NULL,
sensor_id TEXT NOT NULL,
value REAL NOT NULL,
unit TEXT NOT NULL DEFAULT '°C'
)
)");
if (q.lastError().isValid())
qWarning() << "Create table error:" << q.lastError().text();
Insert — Prepared Statement
QSqlQuery ins;
ins.prepare(R"(
INSERT INTO sensor_readings (timestamp, sensor_id, value, unit)
VALUES (:ts, :sid, :val, :unit)
)");
ins.bindValue(":ts", QDateTime::currentDateTime().toString(Qt::ISODate));
ins.bindValue(":sid", "temp-001");
ins.bindValue(":val", 36.5);
ins.bindValue(":unit", "°C");
if (!ins.exec())
qWarning() << "Insert failed:" << ins.lastError().text();
Select & Iterate
QSqlQuery sel("SELECT timestamp, sensor_id, value, unit "
"FROM sensor_readings ORDER BY id DESC LIMIT 20");
while (sel.next()) {
QString ts = sel.value("timestamp").toString();
QString sid = sel.value("sensor_id").toString();
double val = sel.value("value").toDouble();
QString unit= sel.value("unit").toString();
qDebug() << ts << sid << val << unit;
}
QSqlTableModel — Model/View Integration
#include <QSqlTableModel>
#include <QTableView>
QSqlTableModel *model = new QSqlTableModel(this, db);
model->setTable("sensor_readings");
model->setEditStrategy(QSqlTableModel::OnManualSubmit);
model->select(); // load data
// Rename column headers
model->setHeaderData(0, Qt::Horizontal, "ID");
model->setHeaderData(1, Qt::Horizontal, "Time");
model->setHeaderData(2, Qt::Horizontal, "Sensor");
model->setHeaderData(3, Qt::Horizontal, "Value");
model->setHeaderData(4, Qt::Horizontal, "Unit");
// Filter
model->setFilter("sensor_id = 'temp-001'");
model->select();
// Sort
model->setSort(1, Qt::DescendingOrder); // sort by timestamp
// Show in view
QTableView *view = new QTableView(this);
view->setModel(model);
view->resizeColumnsToContents();
Transactions — Batch Inserts
db.transaction();
QSqlQuery ins;
ins.prepare("INSERT INTO sensor_readings (timestamp,sensor_id,value,unit) "
"VALUES (?,?,?,?)");
for (const auto &reading : readings) {
ins.addBindValue(reading.ts);
ins.addBindValue(reading.sensorId);
ins.addBindValue(reading.value);
ins.addBindValue(reading.unit);
if (!ins.exec()) {
qWarning() << "Insert error:" << ins.lastError().text();
db.rollback();
return;
}
}
db.commit();
qDebug() << "Batch insert complete";
Visual: Qt SQL Architecture
Application Code
│
▼
QSqlDatabase::addDatabase("QSQLITE")
│
▼
QSqlQuery ◄──────────────────────────────────┐
│ │
▼ │
Qt SQL Driver Plugin (QSQLITE) QSqlTableModel
│ (wraps QSqlQuery)
▼
SQLite Engine
│
▼
sensors.db (file on disk / :memory:)
Lab 1 — Sensor Reading Database
class SensorDB : public QObject {
Q_OBJECT
QSqlDatabase m_db;
public:
explicit SensorDB(const QString &path, QObject *p = nullptr)
: QObject(p)
{
m_db = QSqlDatabase::addDatabase("QSQLITE", "sensorconn");
m_db.setDatabaseName(path);
if (!m_db.open()) {
qWarning() << "DB error:" << m_db.lastError().text();
return;
}
createSchema();
}
bool insert(const QString &sensorId, float value, const QString &unit) {
QSqlQuery q(m_db);
q.prepare("INSERT INTO readings (ts, sensor_id, value, unit) "
"VALUES (?, ?, ?, ?)");
q.addBindValue(QDateTime::currentMSecsSinceEpoch());
q.addBindValue(sensorId);
q.addBindValue(value);
q.addBindValue(unit);
if (!q.exec()) {
qWarning() << q.lastError().text();
return false;
}
return true;
}
QList<QPair<qint64,float>> history(const QString &sensorId, int limit = 100) {
QList<QPair<qint64,float>> result;
QSqlQuery q(m_db);
q.prepare("SELECT ts, value FROM readings "
"WHERE sensor_id=? ORDER BY ts DESC LIMIT ?");
q.addBindValue(sensorId);
q.addBindValue(limit);
q.exec();
while (q.next())
result.append({q.value(0).toLongLong(), q.value(1).toFloat()});
return result;
}
float average(const QString &sensorId, int lastN = 10) {
QSqlQuery q(m_db);
q.prepare("SELECT AVG(value) FROM ("
" SELECT value FROM readings "
" WHERE sensor_id=? ORDER BY ts DESC LIMIT ?"
")");
q.addBindValue(sensorId);
q.addBindValue(lastN);
q.exec();
return q.next() ? q.value(0).toFloat() : 0.0f;
}
private:
void createSchema() {
QSqlQuery q(m_db);
q.exec("CREATE TABLE IF NOT EXISTS readings ("
" id INTEGER PRIMARY KEY AUTOINCREMENT,"
" ts INTEGER NOT NULL,"
" sensor_id TEXT NOT NULL,"
" value REAL NOT NULL,"
" unit TEXT NOT NULL"
")");
q.exec("CREATE INDEX IF NOT EXISTS idx_sensor ON readings(sensor_id, ts)");
}
};
// Usage
SensorDB db("/var/data/sensors.db");
db.insert("temp-001", 36.5f, "°C");
db.insert("temp-001", 37.0f, "°C");
auto hist = db.history("temp-001", 50);
qDebug() << "Average:" << db.average("temp-001");
Lab 2 — Live Table View with Auto-Refresh
class SensorTableWindow : public QWidget {
Q_OBJECT
public:
SensorTableWindow(QWidget *p = nullptr) : QWidget(p) {
// Setup DB
QSqlDatabase db = QSqlDatabase::database("sensorconn");
auto *lay = new QVBoxLayout(this);
auto *view = new QTableView;
auto *model = new QSqlTableModel(this, db);
model->setTable("readings");
model->setSort(0, Qt::DescendingOrder);
model->select();
view->setModel(model);
view->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
view->setAlternatingRowColors(true);
// Filter by sensor
auto *filterEdit = new QLineEdit;
filterEdit->setPlaceholderText("Filter by sensor ID...");
connect(filterEdit, &QLineEdit::textChanged, [model](const QString &t) {
model->setFilter(t.isEmpty() ? "" : "sensor_id LIKE '%" + t + "%'");
model->select();
});
lay->addWidget(filterEdit);
lay->addWidget(view);
// Auto-refresh
auto *timer = new QTimer(this);
connect(timer, &QTimer::timeout, model, &QSqlTableModel::select);
timer->start(2000);
}
};
Interview Questions
Q1: What is the difference between QSqlQuery and QSqlTableModel?
QSqlQueryexecutes arbitrary SQL and gives you raw row-by-row access.QSqlTableModelwraps a single table and integrates with Qt’s Model/View framework — you can show it directly in aQTableViewwith sorting, filtering, and editing.
Q2: Why use prepared statements instead of string concatenation?
SQL injection prevention and performance. Prepared statements separate SQL code from data — the driver compiles the query once and executes it many times with different bound values. String concatenation is vulnerable to injection if inputs come from users.
Q3: When should you use transactions?
For batch inserts/updates (performance — SQLite wraps every
exec()in its own transaction by default, which is slow). Use explicit transactions for atomicity: either all records insert or none do (data integrity).
Q4: What is :memory: database name?
An in-memory SQLite database — exists only while the connection is open, no file created. Useful for tests, caches, or temporary data. Very fast.
Q5: How do you use multiple database connections?
Pass a connection name to
QSqlDatabase::addDatabase("QSQLITE", "connName"). Access it later withQSqlDatabase::database("connName"). EachQSqlQuerycan specify which connection to use. Useful for multiple simultaneous databases.
Application: IoT Data Historian
class IoTHistorian : public QObject {
Q_OBJECT
SensorDB *m_db;
QTimer *m_collectTimer;
QTimer *m_pruneTimer;
int m_maxDays = 30;
public:
IoTHistorian(const QString &dbPath, QObject *p = nullptr)
: QObject(p)
{
m_db = new SensorDB(dbPath, this);
// Collect every 5s
m_collectTimer = new QTimer(this);
m_collectTimer->setInterval(5000);
connect(m_collectTimer, &QTimer::timeout,
this, &IoTHistorian::collectAll);
// Prune old data daily
m_pruneTimer = new QTimer(this);
m_pruneTimer->setInterval(24 * 3600 * 1000);
connect(m_pruneTimer, &QTimer::timeout,
this, &IoTHistorian::pruneOld);
}
void start() {
m_collectTimer->start();
m_pruneTimer->start();
}
void addSensor(const QString &id, std::function<float()> readFn) {
m_sensors[id] = readFn;
}
void generateReport(const QString &sensorId) {
auto hist = m_db->history(sensorId, 1000);
if (hist.isEmpty()) { qDebug() << "No data for" << sensorId; return; }
float sum = 0, mn = hist[0].second, mx = hist[0].second;
for (const auto &[ts, v] : hist) {
sum += v; mn = qMin(mn, v); mx = qMax(mx, v);
}
qDebug() << "=== Report:" << sensorId << "===";
qDebug() << "Samples:" << hist.size();
qDebug() << "Min:" << mn << " Max:" << mx
<< " Avg:" << sum / hist.size();
}
signals:
void newReading(const QString &sensorId, float value);
private slots:
void collectAll() {
for (auto it = m_sensors.begin(); it != m_sensors.end(); ++it) {
float v = it.value()();
m_db->insert(it.key(), v, "raw");
emit newReading(it.key(), v);
}
}
void pruneOld() {
qint64 cutoff = QDateTime::currentMSecsSinceEpoch()
- (qint64)m_maxDays * 86400000LL;
QSqlQuery q(QSqlDatabase::database("sensorconn"));
q.prepare("DELETE FROM readings WHERE ts < ?");
q.addBindValue(cutoff);
q.exec();
qDebug() << "Pruned" << q.numRowsAffected() << "old records";
}
QHash<QString, std::function<float()>> m_sensors;
};
// Usage
IoTHistorian hist("/var/data/iot.db");
hist.addSensor("temp-001", []() -> float { return 20 + rand()%20; });
hist.addSensor("volt-001", []() -> float { return 3.0f + rand()%30/100.0f; });
hist.start();
References
| Resource | Link |
|---|---|
| Qt Docs: Qt SQL | https://doc.qt.io/qt-6/qtsql-index.html |
| Qt Docs: QSqlDatabase | https://doc.qt.io/qt-6/qsqldatabase.html |
| Qt Docs: QSqlQuery | https://doc.qt.io/qt-6/qsqlquery.html |
| Qt Docs: QSqlTableModel | https://doc.qt.io/qt-6/qsqltablemodel.html |
Next tutorial → Qt State Machine