LambdaLabTM
Databases & SQL · Class 12 · Python & MySQL Together
Pythonconnectivity⏱️ 11 min read

Insert, Update & Delete from PythonOptional for Informatics Practices

Reading is the easy half. Now the program changes the data — and one extra line, which nobody remembers the first time, decides whether those changes survive.

Who this page is for
Python-SQL connectivity is Computer Science (083) only. IP students can skip this chapter.

1Putting values in with %s

The values a program inserts come from variables, not from you typing them. Rather than gluing strings together, leave a %s for each value and pass them as a tuple second argument:

insert.py
cur.execute("insert into students values (%s, %s, %s, %s, %s, %s)",
            (5001, "Asha", 11, 77.5, "2009-04-10", "Namchi"))

Six %s and six values, in the same order. The connector puts them in for you — and, importantly, it quotes the text and dates correctly without you having to think about it.

%s is not the %s you know
This looks like Python's old string formatting, but it is the connector's placeholder, and it is used for every type — numbers included. There is no %d here. And you never put quotes round it: write values (%s, %s), never values ('%s', '%s').

Building the query by joining strings instead — "… values (" + str(id) + ", '" + name + "')" — works right up until a name contains an apostrophe, at which point the SQL breaks. Placeholders avoid the whole problem, so use them from the start.

2commit() — the line everyone forgets

Here is the demonstration. One program opens two connections: A inserts a row and does not commit, then B — a completely separate connection — counts the rows.

nocommit.py
a = open_con()
ca = a.cursor()
ca.execute("insert into students values (%s,%s,%s,%s,%s,%s)",
           (5002, "Ghost", 10, 50.0, "2010-01-01", "Namchi"))
print("inserted, rowcount:", ca.rowcount)

b = open_con()
cb = b.cursor()
cb.execute("select count(*) from students")
print("what another connection sees:", cb.fetchone())

a.rollback()
print("after rollback on A")
cb.execute("select count(*) from students")
print("what another connection sees:", cb.fetchone())
Output
inserted, rowcount: 1
what another connection sees: (12,)
after rollback on A
what another connection sees: (12,)

The insert reported rowcount: 1 — it worked. And yet the other connection still counts 12. The new row exists only inside connection A, invisible to everybody else, waiting to be confirmed or thrown away.

Without con.commit(), an insert, update or delete is never saved.

The bug this causes
Your program runs, prints no error, and reports rows affected. You check in MySQL afterwards and nothing is there. Nothing is broken — you forgot commit(), and when the program ended the unsaved change was discarded. It is the single commonest fault in Class 12 connectivity programs.

rollback() is the opposite of commit: it throws the unsaved changes away deliberately. That is why the count is still 12 at the end, and why the table is unharmed by this demonstration.

3All three operations, done properly

Insert a student, change their marks, then remove them — each followed by a commit(), and each checked:

dml.py
import mysql.connector

con = mysql.connector.connect(
    host="localhost", port=3307,
    user="student", passwd="kv@1234", database="lambdalab")
cur = con.cursor()

cur.execute("insert into students values (%s, %s, %s, %s, %s, %s)",
            (5001, "Asha", 11, 77.5, "2009-04-10", "Namchi"))
print("insert rowcount:", cur.rowcount)
con.commit()

cur.execute("select count(*) from students")
print("after insert:", cur.fetchone())

cur.execute("update students set percent = %s where id = %s", (81.0, 5001))
print("update rowcount:", cur.rowcount)
con.commit()

cur.execute("select id, name, percent from students where id = 5001")
print("the updated row:", cur.fetchone())

cur.execute("delete from students where id = %s", (5001,))
print("delete rowcount:", cur.rowcount)
con.commit()

cur.execute("select count(*) from students")
print("after delete:", cur.fetchone())
con.close()
Output
insert rowcount: 1
after insert: (13,)
update rowcount: 1
the updated row: (5001, 'Asha', 81.0)
delete rowcount: 1
after delete: (12,)

Twelve students became thirteen and then twelve again. Every step reported rowcount: 1, and the updated row shows 81.0 where 77.5 had been.

A tuple of one needs its comma
Look at the delete: (5001,). That trailing comma is what makes it a tuple. (5001) is just the number in brackets, and the connector would reject it. This catches almost everybody once.

4rowcount tells you what really happened

After a change, cur.rowcount is the number of rows affected — the same number MySQL prints at the prompt. It is the natural thing to test:

check.py
cur.execute("delete from students where id = %s", (rollno,))
con.commit()

if cur.rowcount == 0:
    print("No student with that roll number.")
else:
    print(cur.rowcount, "record(s) deleted.")

A delete that matches nothing is not an error in SQL, so without this check the program would cheerfully report success having done nothing.

5The skeleton to remember

1
connect()

Open

2
cursor()

Get a cursor

3
execute()

Run the SQL

4
commit()

Only if you changed data

5
close()

Let it go

Step 4 is the only one that is conditional: a select changes nothing, so it needs no commit. Everything else does.

6Recap

%s placeholders

One per value, values passed as a tuple. No quotes around %s.

con.commit()

Saves the change. Without it nothing is written, and no error is raised.

con.rollback()

Deliberately throws unsaved changes away.

cur.rowcount

Rows affected. 0 means the where matched nothing.

Quick Check

A program inserts a row, prints rowcount 1, and ends. Afterwards the row is not in the table. Why?

Quick Check

Which statement is written correctly?

Quick Check

After a delete, cur.rowcount is 0. What does that mean?

Quick Check

Which operation does NOT need commit()?