Skip to content
Merged
Changes from 1 commit
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
974022c
gh-95271: Improve sqlite3 tutorial wording
erlend-aasland Aug 6, 2022
f21d777
Fix rest syntax
erlend-aasland Aug 6, 2022
4231b75
Improve count query comment
erlend-aasland Aug 6, 2022
e08ca43
SQL commands => SQL queries
erlend-aasland Aug 6, 2022
f9a5664
Improve transaction control paragraph
erlend-aasland Aug 6, 2022
484f341
Be more to the point
erlend-aasland Aug 6, 2022
ae0969f
Fix ref
erlend-aasland Aug 6, 2022
d1d11f4
Missing 'the'; even more to the point
erlend-aasland Aug 6, 2022
9870036
Be more accurate
erlend-aasland Aug 6, 2022
431dcb5
Remove unneeded comments; the instructions don't need to be reiterated
erlend-aasland Aug 6, 2022
ac7d870
Merge branch 'main' into sqlite-tutorial/improve-wording
erlend-aasland Aug 8, 2022
f4b66ac
Address Ezio's initial review
erlend-aasland Aug 8, 2022
09ac89d
Address second round of reviews from both Ezio and CAM
erlend-aasland Aug 9, 2022
cbfb325
Use Python version history as example database
erlend-aasland Aug 9, 2022
deb199c
move sphinx comment; it creates havoc
erlend-aasland Aug 9, 2022
d54ff8d
Address a new round of reviews from CAM
erlend-aasland Aug 9, 2022
e778012
Update the example, be more explicit
erlend-aasland Aug 10, 2022
e9e1ffa
Address review comments
erlend-aasland Aug 11, 2022
f60e3a2
Missing blank line below Sphinx comment
erlend-aasland Aug 11, 2022
5d03a4e
Address reviews from 2022-08-11
erlend-aasland Aug 12, 2022
3ec75d1
Tone: use second person singular more often
erlend-aasland Aug 12, 2022
84d798f
Adressing the last rounds of comments from CAM, Daniele, and Ezio
erlend-aasland Aug 14, 2022
d09d9f6
you will need to => you need to
erlend-aasland Aug 14, 2022
d6f4775
Address more review comments
erlend-aasland Aug 14, 2022
b46942b
Address CAM's last round of comments; use first person plural
erlend-aasland Aug 15, 2022
4a51e1e
Typos
erlend-aasland Aug 15, 2022
92fa693
Nits and a link
erlend-aasland Aug 15, 2022
d40c2a7
Adjust 'non-existent' example; adjust final query; sort links
erlend-aasland Aug 16, 2022
d2c3968
Rewrite an 'as expected'
erlend-aasland Aug 16, 2022
32a19e9
Fix string format anti-pattern
erlend-aasland Aug 17, 2022
dd38587
Clarify how we verify that the table has been created
Aug 18, 2022
f41be9b
Update Doc/library/sqlite3.rst
Aug 18, 2022
0e0398d
Update Doc/library/sqlite3.rst
Aug 18, 2022
cb41c49
Revert "Update Doc/library/sqlite3.rst"
erlend-aasland Aug 18, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Address reviews from 2022-08-11
  • Loading branch information
erlend-aasland committed Aug 12, 2022
commit 5d03a4e252f605fa41780d298f41953d1f76b602
95 changes: 55 additions & 40 deletions Doc/library/sqlite3.rst
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ This document includes four main sections:

.. We use the following practises for SQL code:
- UPPERCASE for keywords
- SnakeCase for schema
- snake_case for schema
- single quotes for string literals
- singular for table names
- if needed, use double quotes for table and column names
Expand All @@ -60,22 +60,22 @@ Tutorial
--------

In this tutorial, you will learn the basics of the :mod:`!sqlite3` API
Comment thread
erlend-aasland marked this conversation as resolved.
Outdated
by creating an on-disk movie database :file:`tutorial.db`
about Monty Python movies.
by creating a database of Monty Python movies.
It assumes a fundamental understanding of database concepts,
including `cursors`_ and `transactions`_.

First, create a new database to hold the movie data,
First, you'll need to create a new database to hold the movie data
and open a database connection to allow :mod:`!sqlite3` to work with it.
Call the :func:`sqlite3.connect` function to
Comment thread
erlend-aasland marked this conversation as resolved.
Outdated
to create a connection to the database :file:`tutorial.db`,
implicitly creating it in the current working directory if it does not exist::
to create a connection to a database :file:`tutorial.db`
Comment thread
ezio-melotti marked this conversation as resolved.
Outdated
in the current working directory,
implicitly creating it if it does not exist::

import sqlite3
con = sqlite3.connect("tutorial.db")

The returned :class:`Connection` object ``con``
represents the connection to the on-disk database.
represents the connection to the database.

In order to execute SQL statements and fetch results from SQL queries,
use a database cursor.
Expand All @@ -85,33 +85,35 @@ Call :meth:`con.cursor() <Connection.cursor>` to create the :class:`Cursor`::

Now, create a database table ``movie`` with columns for title,
release year, and review score.
The `flexible typing`_ feature of SQLite makes typenames optional;
for simplicity, just use column names in the table declaration.
Execute the SQL statement
For simplicitly, just use column names in the table declaration:
thanks to the `flexible typing`_ feature of SQLite,
Comment thread
erlend-aasland marked this conversation as resolved.
Outdated
specifying the data types is optional.
Execute the ``CREATE TABLE`` statement
by calling :meth:`con.execute() <Cursor.execute>`::

cur.execute("CREATE TABLE Movie(Title, Year, Score)")
cur.execute("CREATE TABLE movie(title, year, score)")

.. Ideally, we'd use sqlite_schema instead of sqlite_master below,
but earlier versions of SQLite does not recognise that variant.
but earlier versions of SQLite do not recognise that variant.
Comment thread
erlend-aasland marked this conversation as resolved.
Outdated

You can verify that the table has been created by querying
the ever-present ``sqlite_master`` table.
It should contain an entry for the ``Movie`` table definition.
Execute the query by calling :meth:`cur.execute(...) <Cursor.execute>`,
You can verify that the new table has been created by querying
Comment thread
erlend-aasland marked this conversation as resolved.
Outdated
the ``sqlite_master`` table built-in to SQLite.
It should contain an entry for the ``movie`` table definition.
Comment thread
erlend-aasland marked this conversation as resolved.
Outdated
Execute that query by calling :meth:`cur.execute(...) <Cursor.execute>`,
store the result in a variable ``res``,
Comment thread
erlend-aasland marked this conversation as resolved.
Outdated
and call :meth:`res.fetchone() <Cursor.fetchone>` to fetch the first
(and only) row from the query::
(and only) row that was returned::

>>> res = cur.execute("SELECT name FROM sqlite_master where name='Movie'")
>>> res = cur.execute("SELECT name FROM sqlite_master")
>>> res.fetchone()
Comment thread
erlend-aasland marked this conversation as resolved.
('Movie',)
('movie',)
Comment thread
erlend-aasland marked this conversation as resolved.

The result is a one-item :class:`tuple`: one row, with one column.
As expected, the query shows the table is now created.
As an exercise, try querying ``sqlite_master`` for a bogus name ``"abc"``::
As expected, the query shows the table is now created
by returning a single :class:`tuple`: with the name of the table.
Comment thread
erlend-aasland marked this conversation as resolved.
Outdated
As an exercise, try querying ``sqlite_master``
for a non-existent table ``"abc"``::
Comment thread
erlend-aasland marked this conversation as resolved.
Outdated

>>> res = cur.execute("SELECT name FROM sqlite_master where name='abc'")
>>> res = cur.execute("SELECT name FROM sqlite_master WHERE name='abc'")
>>> res.fetchone()
>>>

Expand All @@ -122,7 +124,7 @@ by executing an ``INSERT`` statement,
once again by calling :meth:`cur.execute(...) <Cursor.execute>`::

cur.execute("""
INSERT INTO Movie VALUES
INSERT INTO movie VALUES
('Monty Python and the Holy Grail', 1975, 8.2),
('And Now for Something Completely Different', 1971, 7.5)
Comment thread
ezio-melotti marked this conversation as resolved.
""")
Expand All @@ -136,17 +138,16 @@ to commit the transaction::
con.commit()

Execute a query to verify that the data was inserted correctly.
Use the now familiar :meth:`con.execute(...) <Cursor.execute>` to
Use the now-familiar :meth:`con.execute(...) <Cursor.execute>` to
store the result in ``res``,
Comment thread
erlend-aasland marked this conversation as resolved.
Outdated
and call :meth:`res.fetchall() <Cursor.fetchall>` to fetch all rows::

>>> res = cur.execute("SELECT Score FROM Movie")
>>> res = cur.execute("SELECT score FROM movie")
>>> res.fetchall()
[(8.2,), (7.5,)]

The result is a :class:`list` with two rows of data,
each a one-item :class:`!tuple`,
containing the value from the single column queried.
The result is a :class:`list` of two :class:`!tuple`\s, one per row,
each containing a the ``score`` from the query.
Comment thread
erlend-aasland marked this conversation as resolved.
Outdated

Now, insert three more rows by calling
:meth:`cur.executemany(...) <Cursor.executemany>`::
Expand All @@ -156,35 +157,45 @@ Now, insert three more rows by calling
("Monty Python's The Meaning of Life", 1983, 7.5),
("Monty Python's Life of Brian", 1979, 8.0),
]
cur.executemany("INSERT INTO Movie VALUES(?, ?, ?)", data)
cur.executemany("INSERT INTO movie VALUES(?, ?, ?)", data)
con.commit() # Remember to commit the transaction after executing INSERT.

Notice that ``?`` placeholders are used to bind ``data`` to the query.
Always use placeholders instead of :ref:`string formatting <tut-formatting>`
to bind Python values to SQL statements,
to avoid `SQL injection attacks`_.
See :ref:`sqlite3-placeholders` for more details.
to avoid `SQL injection attacks`_
(see :ref:`sqlite3-placeholders` for more details).

Verify that data has been written to disk by
calling :meth:`con.close() <Connection.close>` to close the connection,
Verify that the new rows were inserted by executing a ``SELECT`` query,
this time iterating over the results of the query::

>>> for row in cur.execute("SELECT year, title FROM movie ORDER BY year"):
... print(row)
(1971, "And Now for Something Completely Different")
(1975, "Monty Python and the Holy Grail")
(1979, "Monty Python's Life of Brian")
(1982, "Monty Python Live at the Hollywood Bowl")
(1983, "Monty Python's The Meaning of Life")
Comment thread
erlend-aasland marked this conversation as resolved.

Each row is now a two-item :class:`tuple` of ``(year, title)``.
Comment thread
erlend-aasland marked this conversation as resolved.
Outdated

At last, verify that the database has been written to disk by
calling :meth:`con.close() <Connection.close>`
to close the existing connection,
opening a new one, creating a new cursor,
then executing a ``SELECT`` query, this time iterating over the results
of the query to read from the database::
then reusing the query from above to read from the database::

>>> con.close()
>>> con2 = sqlite3.connect("tutorial.db")
>>> cur2 = con2.cursor()
>>> for row in cur2.execute("SELECT Year, Title FROM Movie ORDER BY Year"):
>>> for row in cur.execute("SELECT year, title FROM movie ORDER BY year"):
Comment thread
erlend-aasland marked this conversation as resolved.
Outdated
... print(row)
...
(1971, "And Now for Something Completely Different")
(1975, "Monty Python and the Holy Grail")
(1979, "Monty Python's Life of Brian")
(1982, "Monty Python Live at the Hollywood Bowl")
(1983, "Monty Python's The Meaning of Life")

Each row is now a two-item :class:`tuple` of ``(Year, Title)``.

You've now created an SQLite database using the :mod:`!sqlite3` module,
inserted data and ran several SQL queries against it.
Comment thread
erlend-aasland marked this conversation as resolved.
Outdated

Expand All @@ -194,6 +205,10 @@ inserted data and ran several SQL queries against it.
.. _transactions: https://en.wikipedia.org/wiki/Database_transaction
.. _sqlite_master: https://www.sqlite.org/schematab.html

.. seealso::

* :ref:`sqlite3-howtos` for details how to handle specific tasks.
Comment thread
erlend-aasland marked this conversation as resolved.
Outdated
* :ref:`sqlite3-explanation` for in-depth background on transaction control.

.. _sqlite3-reference:

Expand Down