Querying Data with SELECT and WHERE Clauses
Think of the sequence as create, save, read, and remove. INSERT creates rows, commit() saves the transaction to the database file, SELECT reads data, and DELETE removes matching rows.
Try it: INSERT, UPDATE and DELETE
How INSERT builds a new row from its column list (omitted columns get the generated id, their DEFAULT or NULL), how UPDATE … SET and DELETE test WHERE on every row and change only the matches — or every row when WHERE is missing — and how cur.rowcount and commit() report and keep the change.
How it works
- The statement is sent with ? placeholders and a tuple of values: cur.execute(sql, params).
- INSERT: the listed columns get the values in order; an unlisted INTEGER PRIMARY KEY gets max(id) + 1, an unlisted column gets its DEFAULT (or NULL). A listed id that already exists raises IntegrityError.
- UPDATE / DELETE: WHERE is evaluated on each row; only rows where it is TRUE match (NULL does not). No WHERE means every row matches. The same WHERE in a SELECT shows the target rows first.
- UPDATE applies every SET assignment to each matched row, reading the row's old values; DELETE removes the matched rows. Matching nothing is not an error.
- cur.rowcount is the number of rows inserted, changed or removed.
- The change is pending until conn.commit(); conn.close() without commit() rolls it back, so a new connection still sees the old table.
Default run (11 steps): UPDATE track SET plays = ? WHERE title = ? with parameters (16, 'My Way'). The track table starts with 5 rows. … conn.commit() makes the change permanent. A new connection now sees 5 rows.
Simplified: A toy relational engine in the browser, not SQLite itself: statements are built from the controls (no SQL text is parsed or executed) and tables have a few rows. Results were checked against Python's sqlite3 (SQLite 3.49). One table track(id INTEGER PRIMARY KEY, title TEXT, plays INTEGER DEFAULT 0) of up to 8 rows, one WHERE comparison, text limited to printable ASCII. The generated id is max(id) + 1, SQLite's rule for an INTEGER PRIMARY KEY without AUTOINCREMENT.
Loading the simulation…
From New Rows to Cleanup
A database operation is not complete merely because a command has run. In the music-database example, the program inserts two tracks, commits those changes, reads the tracks with SELECT, and then deletes the rows it created. Following that sequence reveals three important ideas: changes need to be committed, SELECT can retrieve only the columns you need, and WHERE controls which rows are affected.
Think of the sequence as create, save, read, and remove. INSERT creates rows, commit() saves the transaction to the database file, SELECT reads data, and DELETE removes matching rows.
Saving Inserted Rows
INSERT adds rows to a database table. In the source program, two tracks are inserted into the Track table. The INSERT operations use parameterized queries: question-mark placeholders represent values, and the actual values are supplied separately to execute(). This separates the query structure from the values being inserted.
Inserting and Committing Two Tracks
Trace what happens when the program inserts Thunderstruck with 20 plays and My Way with 15 plays.
- 1Insert
The program executes two INSERT statements for the Track table. Each statement uses question-mark placeholders, with the track values supplied separately.
- 2Transaction buffer
After INSERT runs, the new rows exist in the transaction buffer. They have not yet been permanently written to the database file.
- 3Commit
The program calls
commit(). This forces the inserted rows to be permanently saved to the database file. - 4Failure before commit
If the program crashes before
commit()is called, the inserted rows are lost.
After commit(), the two inserted tracks are permanently saved in the database file.
Selecting Columns with a Cursor
SELECT retrieves data from a table. The source program selects the title and plays columns from the Track table, so each returned row contains those two values. The cursor then supplies rows during iteration. It does not immediately load the entire result set into memory; instead, it reads data on demand as the loop proceeds.
For the source program, the cursor returns each row as a tuple of two values. A printed row can appear as ('Thunderstruck', 20), where the first value is the track title and the second is the play count. The next row contains the title My Way and the play count 15.
Filtering Results and Deletions
A SELECT operation identifies the columns to retrieve and the table to read. A WHERE clause identifies rows that match a condition. The same filtering idea is used by DELETE: DELETE removes only rows that satisfy its WHERE condition. Without a WHERE clause, DELETE can remove all data, so the condition should be chosen deliberately.
Deleting Tracks Below the Play Limit
Determine which rows are removed when DELETE uses the condition plays < 100.
- 1Check Thunderstruck
Thunderstruck has 20 plays. Since 20 is less than 100, this row matches the WHERE condition.
- 2Check My Way
My Way has 15 plays. Since 15 is less than 100, this row also matches the WHERE condition.
- 3Delete matches
DELETE removes both matching rows.
- 4Commit deletion
The program calls
commit()after DELETE so the removals are permanently written to the database file.
Both source-program tracks are deleted, leaving the table empty so the program can be run again without accumulating duplicate data.
Safe Query Habits
Use parameterized queries with question-mark placeholders instead of building SQL through string concatenation. The source identifies parameterized queries as the safer approach because they help prevent SQL injection vulnerabilities.
Common mistake Assuming INSERT permanently saves a row without commit().
What it looks likeThe program inserts rows and then stops before commit() runs.
Why it failsThe rows remain in the transaction buffer and are not permanently written to the database file. A crash before
commit()causes the inserted rows to be lost.Do this insteadCall commit() after the INSERT operations when the changes should be permanently saved.
Common mistake Assuming SELECT loads every result row into memory immediately.
What it looks likeTreating the cursor as though it already contains the entire result set.
Why it failsThe cursor reads rows on demand during iteration rather than loading all rows at once.
Do this insteadUnderstand the loop as incremental cursor reading: the next row is obtained as iteration proceeds.
Common mistake Running DELETE without a WHERE clause.
What it looks likeDeleting from the table without identifying a condition.
Why it failsThe operation can remove all data from the table.
Do this insteadUse a WHERE clause that identifies the rows intended for removal, then call commit() to persist the deletion.
Common mistake Using string concatenation instead of parameterized queries.
What it looks likeCombining input values directly into a query string.
Why it failsThe source identifies this practice as a risk because it can lead to SQL injection vulnerabilities.
Do this insteadUse question-mark placeholders and pass the actual values separately to execute().
Trace the Operations
Trace the source program in order. Explain what happens to the Track table after the two INSERT operations, after commit(), after SELECT iterates through the rows, after DELETE uses plays < 100, and after the final commit(). Then explain why the cursor approach is more memory-efficient than loading every result row at once.
Hints
- Track the two rows separately: Thunderstruck has 20 plays and My Way has 15.
- Distinguish the transaction buffer from the permanently saved database file.
- For DELETE, test the WHERE condition against each row.
- Remember that the cursor reads during iteration.
Key Takeaways
- 1INSERT adds rows, but
commit()is required to permanently save those changes to the database file. - 2SELECT can retrieve specific columns, such as title and plays, from the Track table.
- 3A cursor reads result rows on demand during iteration instead of loading the entire result set into memory at once.
- 4DELETE removes rows that match its WHERE condition, and a WHERE clause is essential to avoid deleting all data.
- 5Parameterized queries with question-mark placeholders should be used instead of string concatenation.
At a glance
- INSERT creates rows, while
commit()makes the changes permanent in the database file. - SELECT chooses the columns to retrieve, and a cursor supplies result rows incrementally during iteration.
- WHERE identifies the rows that match a condition and therefore controls which rows DELETE removes.
- Parameterized queries are safer than string concatenation, and DELETE should always include a deliberate WHERE clause.