Raw SQL & Utilities
The dictionary helpers cover the common cases, but the same connection is available for
arbitrary SQL. These methods are the escape hatch whenever insert(),
select(), update() or delete() cannot express what you
need — joins, transactions, NULL values, DDL, or anything on Python 3.10+ where
the dictionary paths are unavailable.
Method summary
| Method | Returns | Commits? | Description |
|---|---|---|---|
execute(query) | None | No | Runs a statement and discards any result set. |
query(query) | list[dict] | No | Runs a statement and returns all rows as dictionaries. |
count() | int | — | The cursor's rowcount from the last statement. |
getLastId() | int | — | The cursor's lastrowid from the last statement. |
truncate(table) | None | — | Broken in this release — see below. |
resetCache() | None | — | Broken in this release — see below. |
query() — read arbitrary SQL
query() executes whatever you give it and maps the result set to a list of
dictionaries, exactly like select() does. Use it for joins, subqueries,
aggregates and anything else the select() helper cannot build.
rows = db.query("""
SELECT o.id, o.total, c.name
FROM orders o
JOIN customers c ON c.id = o.customer_id
WHERE o.status = 'paid'
ORDER BY o.created_at DESC
LIMIT 20
""")
for row in rows:
print(row['id'], row['name'], row['total'])
Column keys come from the cursor description, so alias your expressions
(COUNT(*) AS n) to get predictable key names.
On an empty result set query() returns []. If the statement
raises, the error is printed and query() returns None — so a
for loop over the result will fail with TypeError rather than
surfacing the real problem. Guard with rows = db.query(...) or [] if you
cannot afford that.
execute() — statements with no result set
execute() runs a statement and ignores whatever it returns. It is the right call
for DDL and for writes the dictionary helpers cannot express.
db.execute("""
CREATE TABLE IF NOT EXISTS audit_log (
id INT AUTO_INCREMENT PRIMARY KEY,
message TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
""")
# A real NULL, which insert() cannot produce
db.execute("INSERT INTO products (name, discontinued_at) VALUES ('Mouse', NULL)")
execute() does not commit
Unlike insert(), update() and delete(),
execute() leaves the transaction open. Writes made this way are rolled back
when the connection closes unless you commit them yourself. The connection object is
name-mangled and private, so the practical way to commit is a raw statement:
db.execute("INSERT INTO audit_log (message) VALUES ('imported')")
db.execute("COMMIT")
Transactions
Because execute() does not commit, it is also the only way to group several
writes into one transaction:
db.execute("START TRANSACTION")
db.execute("UPDATE accounts SET balance = balance - 100 WHERE id = 1")
db.execute("UPDATE accounts SET balance = balance + 100 WHERE id = 2")
db.execute("COMMIT")
Do not mix insert(), update() or delete() into a block
like this — each one commits on its own and would end the transaction early.
count() — rows affected or returned
count() exposes the cursor's rowcount for the most recent statement.
Read it immediately after the call you care about, since the next statement overwrites it.
db.delete('orders', "status = 'cancelled'")
print(db.count(), "rows deleted")
db.select('products', "stock = 0")
print(db.count(), "products out of stock")
getLastId() — last auto-increment ID
Returns the cursor's lastrowid. insert() already returns this value,
so getLastId() is mostly useful after an execute() that inserted a
row.
db.execute("INSERT INTO products (name, discontinued_at) VALUES ('Mouse', NULL)")
new_id = db.getLastId()
Broken helpers: truncate() and resetCache()
Both methods call execute() on the connection object rather than on
the instance. PyMySQL connections have no execute() method, so both raise
AttributeError: 'Connection' object has no attribute 'execute' on every call
in the current release.
Use raw statements instead:
# Instead of db.truncate('logs')
db.execute("TRUNCATE logs;")
# Instead of db.resetCache()
db.execute("RESET QUERY CACHE;")
Note that the MySQL query cache — and therefore RESET QUERY CACHE — was removed
in MySQL 8.0. On a modern server that statement is a syntax error, which
execute() will print rather than raise.