{"id":2566,"date":"2025-10-12T17:18:18","date_gmt":"2025-10-12T09:18:18","guid":{"rendered":"http:\/\/blue.yn.cn\/?p=2566"},"modified":"2025-10-21T16:54:11","modified_gmt":"2025-10-21T08:54:11","slug":"python-and-databases","status":"publish","type":"post","link":"http:\/\/blue.yn.cn\/?p=2566","title":{"rendered":"Python and Databases"},"content":{"rendered":"<p><strong>Using Databases and SQL<\/strong><\/p>\n<h3>What is a database?<\/h3>\n<p>A database is a file that is organized for storing data. Most databases are organized like a dictionary in the sense that they map from keks to values. The biggest difference is that the database is on disk (or other permanent storage), so it persists after the program ends. Because a database is stored on permanent storage, it can store far more data then a dictionary, which is limited to the size of the memory in the computer.<\/p>\n<p>Like a dictionary, database software is designed to keep the inserting and accessing of data very fast, even for large amounts of data. Database software maintains its performance by building indexes as data is added to the database to allow the computer to jump quickly to a particular entry.<\/p>\n<p>There are many different database systems which are used for a wide variety of purposes including: Oracle, MySQL, Microsoft SQL Server, PostgreSQL, and SQLite. We focus on SQLite in this book because it is a very common database and its already built into Python. SQLite is designed to be embedded into other applications to provide database support within the application. For example, the Firefox browser also uses the SQLite database internally as do many other products.<\/p>\n<p><a href=\"http:\/\/sqlite.org\/\">http:\/\/sqlite.org\/<\/a><\/p>\n<p>SQLite is well suited to some of the data manipulation problems that we see in Informatics.<\/p>\n<h3>Database concepts<\/h3>\n<p>When you first look at a database it looks like a spreadsheet with multiple sheets. The primary data structures in a database are: tables, rows, columns.<\/p>\n<p><img decoding=\"async\" src=\"\/wp-content\/uploads\/2025\/10\/image-1760261574284.png\" alt=\"file\" \/><br \/>\nRelational Databases<\/p>\n<p>In technical descriptions of relational databases the concepts of table, row, and column are more formally referred to as relation, tuple, and attribute, respectively. We will use the less formal terms in this chapter.<\/p>\n<h3>Database Browser For SQLite<\/h3>\n<p>While this chapter will focus on using Python to work with data in SQLite database files, many operations can be done more conveniently using software called the Database Browser for SQLite which is freely available from:<\/p>\n<p><a href=\"http:\/\/sqlitebrowser.org\/\">http:\/\/sqlitebrowser.org\/<\/a><\/p>\n<p>Using the browser you can easily create tables, insert data, edit data, or run simple SQL queries on the data in the database.<\/p>\n<p>In a sense, the database browser is similar to a text editor when working with text files. When you want to do one or very few operations on a text file, you can just open it in a text editor and make the changes you want. When you have many changes that you need to do to a text file, often you will write a simple Python program. You will find the same pattern when working with databases. You will do simple operations in the database manager and more complex operations will be most conveniently done in Python.<\/p>\n<h3>Creating a database table<\/h3>\n<p>Databases require more defined structure than Python lists or dictionaries.<\/p>\n<p>When we create a database table we must tell the database in advance the name of each of the columns in the table and the type of data which we are planning to store in each column. When the database software knows the type of data in each column, it can choose the most efficient way to store and lookup the data based on the type of data.<\/p>\n<p>You can look at the various data types supported by SQLite at the following url:<\/p>\n<p><a href=\"http:\/\/www.sqlite.org\/datatypes.html\">http:\/\/www.sqlite.org\/datatypes.html<\/a><\/p>\n<p>Defining structure for your data up front may seem inconvenient at the beginning, but the payoff is fast access to your data even when the database contains a large amount of data.<\/p>\n<p>The code to create a database file and a table named <code>Track<\/code> with two coluumns in the database is as follows:<\/p>\n<pre><code class=\"language-python\">import sqlite3\n\nconn = sqlite3.connect(&#039;music.sqlite&#039;)\ncur = conn.cursor()\n\ncur.execute(&#039;DROP TABLE IF EXISTS Track&#039;)\ncur.execute(&#039;CREATE TABLE Track (title TEXT, plays INTEGER)&#039;)\n\nconn.close()<\/code><\/pre>\n<p>The <code>connect<\/code> operation makes a &quot;connection&quot; to the database stored in the file <code>music.sqlite<\/code> in the current directory. If the file does not exist, it will be created. The reason this is called a &quot;connection&quot; is that sometimes the databases is stored on a sparate &quot;database server&quot; from the server on which we are running our application. In our simple examples the database will just be a local file in the same directory as the Python code we are running.<\/p>\n<p>A curson is like a fiile handle that we can use to perform operations on the data stored in the database. Calling <code>curson()<\/code> is very similar conceptually to calling <code>open()<\/code> when dealing with text files.<\/p>\n<p><img decoding=\"async\" src=\"\/wp-content\/uploads\/2025\/10\/image-1760275663495.png\" alt=\"file\" \/><br \/>\nA Database Cursor<\/p>\n<p>Once we have the cursor, we can begin to execute commands on the contents of the database using the <code>execute()<\/code> method.<\/p>\n<p>Database commands are expressed in a special language that has been standardized across many different database vendors to allow us to learn a single database language. The database language is called Structured Query Language or SQL for short.<\/p>\n<p><a href=\"https:\/\/en.wikipedia.org\/wiki\/SQL\">https:\/\/en.wikipedia.org\/wiki\/SQL<\/a><\/p>\n<p>In our example, we are executing two SQL commands in our database. As a convention, we will show the SQL keywords in uppercase and the parts of the command that we are adding (such as the table and column names) will be shown in lowercase.<\/p>\n<p>The first SQL command removes the <code>Track<\/code> table from the database if it exists. This pattern is simply to allow us to run the same program to create the <code>Track<\/code> table over and over again without causing an error. Note that the <code>DROP TABLE<\/code> command deletes the table and all of its contents from the database (i.e., there is no &quot;undo&quot;).<\/p>\n<pre><code class=\"language-python\">cur.execute(&#039;DROP TABLE IF EXISTS Track &#039;)<\/code><\/pre>\n<p>The second command creates a table named <code>Track<\/code> with a text column name <code>title<\/code> and an integer column named <code>plays<\/code>.<\/p>\n<pre><code class=\"language-python\">cur.execute(&#039;CREATE TABLE Track (title TEXT, plays INTEGER)&#039;)<\/code><\/pre>\n<p>Now that we have created a table named <code>Track<\/code>, we can put some data into that table using the SQL <code>INSERT<\/code> operation. Again, we begin by making a connection to the database and obtaining the <code>cursor<\/code>. We can then execute SQL commands using the cursor.<\/p>\n<p>The SQL <code>INSERT<\/code> command indicates which table we are using and thehn defines a new row by listing the fields we want to include <code>(title, plays)<\/code> followed by the <code>VALUES<\/code> we want placed in the new row. We specify the values as question marks <code>(?, ?)<\/code> to indicate that the actual values are passed in as a tuple <code>(&#039;My Way&#039;, 15)<\/code> as the second parameter to the <code>execute()<\/code> call.<\/p>\n<pre><code class=\"language-python\">import sqlite3\n\nconn = sqlite3.connect(&#039;music.sqlite&#039;)\ncur = conn.cursor()\n\ncur.execute(&#039;INSERT INTO Track (title, plays) VALUES (?, ?)&#039;,\n    (&#039;Thunderstruck&#039;, 20))\ncur.execute(&#039;INSERT INTO Track (title, plays) VALUES (?, ?)&#039;,\n    (&#039;My Way&#039;, 15))\nconn.commit()\n\nprint(&#039;Track:&#039;)\ncur.execute(&#039;SELECT title, plays FROM Track&#039;)\nfor row in cur:\n     print(row)\n\ncur.execute(&#039;DELETE FROM Track WHERE plays &lt; 100&#039;)\nconn.commit()\n\ncur.close()<\/code><\/pre>\n<p>First we <code>INSERT<\/code> two rows into our table and use <code>commit()<\/code> to force the data to be written to the database file.<\/p>\n<p><img decoding=\"async\" src=\"\/wp-content\/uploads\/2025\/10\/image-1760277297444.png\" alt=\"file\" \/><br \/>\nRows in a Table<\/p>\n<p>Then we use the <code>SELECT<\/code> command to retrieve the rows we just inserted form the table. On the <code>SELECT<\/code> command, we indicate which columns we would like <code>(title, plays)<\/code> and indicate which table we want to retrieve the data from. After execute the <code>SELECT<\/code> statement, the cursor is something we can loop through in a <code>for<\/code> statement. For efficiency, the cursor dose not read all of the data from the database when we execute the <code>SELECT<\/code> statement. Instead, the data is read on demand as we loop through the rows in the <code>for<\/code> statement.<\/p>\n<p>The output of the program is as follows:<\/p>\n<pre><code class=\"language-python\">Track:\n(&#039;Thunderstruck&#039;, 20)\n(&#039;My Way&#039;, 15)<\/code><\/pre>\n<p>Our <code>for<\/code> loop finds two rows, and each row is a Python tuple with the first value as the <code>title<\/code> and the second value as the number of <code>plays<\/code>.<\/p>\n<p>At the very end of the program, we execute an SQL command to <code>DELETE<\/code> the rows we have just created so we can run the program over and over. The <code>DELETE<\/code> command shows the use of a <code>WHERE<\/code> clause that allows us to express a selection criterion so that we can ask the database to apply the command to only the rows that match the criterion. In this example the criterion happens to apply to all the rows so we empty the table out so we can run the program repeatedly. After the <code>DELETE<\/code> is performed, we also call <code>commit()<\/code> to force the data to be removed from the database.<\/p>\n<h3>Structured Query Language summary<\/h3>\n<p>So far, we have been using the Structured Query Language in our Python examples and have covered many of the basics of the SQL commands. In this section, we look at the SQL language in paticular and give an overview of SQL syntax.<\/p>\n<p>Since there are so many different database vendors, the Structured Query Language (SQL) was standardized so we could communicate in a portable manner to database systems from multiple vendors.<\/p>\n<p>A relational database is made up of tabels, rows and columns. The columns generally have a type such as text, numeric, or date data. When we create a table, we indicate the names and types of the columns:<\/p>\n<pre><code class=\"language-python\">CREATE TABLE Track (title TEXT, plays INTEGER)<\/code><\/pre>\n<p>To insert a row into a table, we use the SQL <code>INSERT<\/code> command:<\/p>\n<pre><code class=\"language-python\">INSERT INTO Track (title, plays) VALUES (&#039;My Way&#039;, 15)<\/code><\/pre>\n<p>The <code>INSERT<\/code> statement specifies the table name, then a list of the fields\/columns that you would like to set in the new row, and then the keyword <code>VALUE<\/code> and a list of corresponding values for each of the fields.<\/p>\n<p>The SQL <code>SELECT<\/code> command is used to retrieve rows and columns from a database. The <code>SELECT<\/code> statement lets you specify which columns you would like to retrieve as well as a <code>WHERE<\/code> clause to select which rows you would like to see. It also allows an optional <code>ORDER BY<\/code> clause to control the sorting of the returned rows.<\/p>\n<pre><code class=\"language-python\">SELECT * FROM Track WHERE title = &#039;My Way&#039;<\/code><\/pre>\n<p>Using <code>*<\/code> indicates that you want the database to return all of the columns for each row that matches the <code>WHERE<\/code> clause.<\/p>\n<p>Note, unlike in Python, in a SQL <code>WHERE<\/code> clause we use a single equal sign to indicate a test for equanlity rather than a double equal sign. Other logical operations allowed in a <code>WHERE<\/code> clause include <code>&lt;<\/code>, <code>&gt;<\/code>, <code>&lt;=<\/code>, <code>&gt;=<\/code>, <code>!=<\/code>, as well as <code>AND<\/code> and <code>OR<\/code> and parentheses to buil your logical expressions.<\/p>\n<p>You can request that the returned rows be sorted by one of the fields as follows:<\/p>\n<pre><code class=\"language-python\">SELECT title,plays FROM Track ORDER BY title<\/code><\/pre>\n<p>It is possible to <code>UPDATE<\/code> a column or columns within one or more rows in a table using the SQL <code>UPDATE<\/code> statement as follows:<\/p>\n<pre><code class=\"language-python\">UPDATE Track SET plays = 16 WHERE title = &#039;My Way&#039;<\/code><\/pre>\n<p>The <code>UPDATE<\/code> statement specifies a table and then a list of fields and values to change after the <code>SET<\/code> keyword and then an optional <code>WHERE<\/code> clause to select the rows that are to be updated. A single <code>UPDATE<\/code> statement will change all of  the rows that match the <code>WHERE<\/code> clause. If a <code>WHERE<\/code> clause is not specified, it performs the <code>UPDATE<\/code> on all of the rows in the table.<\/p>\n<p>To remove a row, you need a <code>WHERE<\/code> clause on an SQL <code>DELETE<\/code> statement. The <code>WHERE<\/code> clause determines which rows are to be deleted:<\/p>\n<pre><code class=\"language-python\">DELETE FROM Track WHERE title = &#039;My Way&#039;<\/code><\/pre>\n<p>These four basic SQL commands (INSERT, SELECT, UPDATE, DELETE) allow the four basic operations needed to create and maintain data. We use &quot;CRUD&quot; (Create, Read, Update, and Delete) to capture all these concepts in a single term.<\/p>\n<h3>Multiple table and basic data modeling<\/h3>\n<p>The real power of a relational database is when we create multiple tables and make links between those tables. The act of deciding how to break up your application data into multiple tables and establshing the relationships between the tables is called data modeling. The design document that shows the tables and their relationships is called a data model.<\/p>\n<p>Data modeling is a relatively sophisticated skill and we will only introduce the most basic concepts of relational data modeling in this section. For more detail on data modeling you can start with:<\/p>\n<p><a href=\"https:\/\/en.wikipedia.org\/wiki\/Relational_model\">https:\/\/en.wikipedia.org\/wiki\/Relational_model<\/a><\/p>\n<p>Lets say for our tracks database we wanted to track the name of the <code>artist<\/code> for each track in addition to the <code>title<\/code> and number of plays for each track. A simple approach might be to simply add another column to the database called <code>artist<\/code> and put the name of the artist in the column as follows:<\/p>\n<pre><code class=\"language-python\">DROP TABLE IF EXISTS Track;\nCREATE TABLE Track (title TEXT, plays INTEGER, artist TEXT);<\/code><\/pre>\n<p>Then we could insert a few tracks into our tabel.<\/p>\n<pre><code class=\"language-python\">INSERT INTO Track (title, plays, artist)\n    VALUES (&#039;My Way&#039;, 15, &#039;Frank Sinatra&#039;);\nINSERT INTO Track (title, plays, artist)\n    VALUES (&#039;New York&#039;, 25, &#039;Frank Sinatra&#039;);<\/code><\/pre>\n<p>If we were to look at our data with a <code>SELECT * FROM Track<\/code> statement, it looks like we have done a fine job.<\/p>\n<pre><code class=\"language-python\">sqlite&gt; SELECT * FROM Track;\nMy Way|15|Frank Sinatra\nNew York|25|Frank Sinatra\nsqlite&gt;<\/code><\/pre>\n<p>We have made a very bad error in our data modeling. We have violated the rules of database normalization.<\/p>\n<p><a href=\"https:\/\/en.wikipedia.org\/wiki\/Database_normalization\">https:\/\/en.wikipedia.org\/wiki\/Database_normalization<\/a><\/p>\n<p>While database normalization seems very complex on the surface and contains a lot of mathematical justifications, for now we can reduce it all into one simple rule that we will follow.<\/p>\n<p>We should never put the same string data in a column more than once. If we need the data more than once, we create a numeric key for the data and reference the actual data using this key. Especially if the multiple entries refer to the same object.<\/p>\n<p>To demonstrate the slippery slope we are going down by assigning string columns to out database model, think about how we would change the data model if we wanted to keep track of the eye color of our artist? would we do this?<\/p>\n<pre><code class=\"language-python\">DROP TABLE IF EXISTS Track;\nCREATE TABLE Track (title TEXT, plays INTEGER,\n    artist TEXT, eyes TEXT);\nINSERT INTO Track (title, plays, artist, eyes)\n    VALUES (&#039;My Way&#039;, 15, &#039;Frank Sinatra&#039;, &#039;Blue&#039;);\nINSERT INTO Track (title, plays, artist, eyes)\n    VALUES (&#039;New York&#039;, 25, &#039;Frank Sinatra&#039;, &#039;Blue&#039;);<\/code><\/pre>\n<p>Since Frank Sinatra recorded over 1200 songs, are we really going to put the string 'Blue' in 1200 rows in our <code>Track<\/code> table. And what would happen if we decided his eye color was 'Light Blue'? Something does not fell right.<\/p>\n<p>The correct solution is to create a table for the each <code>Artist<\/code> and store all the data about the artist in that table. And then somehow we need to make a connection between a row in the <code>Track<\/code> table to a row in the <code>Artist<\/code> table. Perhaps we could call this &quot;link&quot; between two &quot;tables&quot; a &quot;relationship&quot; between two tables. And that is exactly what database experts decided to all these links.<\/p>\n<p>Lets make an <code>Artist<\/code> table as follows:<\/p>\n<pre><code class=\"language-python\">DROP TABLE IF EXISTS Artist;\nCREATE TABLE Artist (name TEXT, eyes TEXT);\nINSERT INTO Artist (name, eyes)\n   VALUES (&#039;Frank Sinatra&#039;, &#039;blue&#039;);<\/code><\/pre>\n<p>Now we have a row in the table for 'Frank Sinatra' (and his eye color) and a primary key of '42' to use to link our tracks to him. So we alter our Track tabel as follows:<\/p>\n<pre><code class=\"language-python\">DROP TABLE IF EXISTS Track;\nCREATE TABLE Track (title TEXT, plays INTEGER,\n    artist_id INTEGER);\nINSERT INTO Track (title, plays, artist_id)\n    VALUES (&#039;My Way&#039;, 15, 42);\nINSERT INTO Track (title, plays, artist_id)\n    VALUES (&#039;New York&#039;, 25, 42);<\/code><\/pre>\n<p>The <code>atrist_id<\/code> column is an integer, and by naming convention is a foreign key pointing at a primary key in the <code>Artist<\/code> table. We call it foreign key because it is pointing to a row in a different table.<\/p>\n<p>Now we are following the rules of database normalization, but when we want to get data out of our database, we don't want to see the 42, we want to see the name and eye color of the artist. To do this we use the <code>JOIN<\/code> keyword in our SELECT statement.<\/p>\n<pre><code class=\"language-python\">SELECT title, plays, name, eyes\nFROM Track JOIN Artist\nON Track.artist_id = Artist.id;<\/code><\/pre>\n<p>The <code>JOIN<\/code> clause includes an <code>ON<\/code> condition that defines how the rows are to to be connected. For each row in <code>Track<\/code> add the data from <code>Artist<\/code> from the row where <code>artist_id<\/code> <code>Track<\/code> table matches the <code>id<\/code> from the <code>Artist<\/code> table.<\/p>\n<p>The output would be:<\/p>\n<pre><code class=\"language-python\">My Way|15|Frank Sinatra|blue\nNew York|25|Frank Sinatra|blue<\/code><\/pre>\n<p>While it might seem a little clunky and your instincts might tell you that it would be faster just to keep the data in one table, it turns out the the limit on database performance is how much data needs to be scanned when retrieving a query. While the details are very complex, integers are a lot smaller than strings (especially Unicode) and far quicker to to move and compare.<\/p>\n<h3>Data model diagrams<\/h3>\n<p>While our <code>Track<\/code> and <code>Artist<\/code> database design is simple with just two tables and a single one-to-many relationship, these data model can get complicated quickly and are easier to understand if we can make a graphical representation of our data model.<\/p>\n<p><img decoding=\"async\" src=\"\/wp-content\/uploads\/2025\/10\/image-1760798091880.png\" alt=\"file\" \/><br \/>\nA Verbose One-to-Many Data Model<\/p>\n<p>While there are many graphical representations of data models, we will use one of the &quot;classic&quot; approaches, called &quot;Crow's Foot Diagrams&quot; as shown in Figure. Each table is shown as a box with the name of the table and its columns. Then where there is a relationship between two tablse a line is drawn connecting the tables with a notation added to the end of each line indicating the nature of the relationship.<\/p>\n<p><a href=\"https:\/\/en.wikipedia.org\/wiki\/Entity-relationship_model\">https:\/\/en.wikipedia.org\/wiki\/Entity-relationship_model<\/a><\/p>\n<p>In this case, &quot;many&quot; tracks can be associated with each artist. So the track end is shown with the crow's foot spread out indicating it is the &quot;many&quot; end. The artist end is shown with a vertical like that indicates &quot;one&quot;. There will be &quot;many&quot; artists in general, but the important aspect is that for each artist there will be many tracks. And each of those artists may be associated with multiple tracks.<\/p>\n<p>You will note that the column that holds the foreign_key like <code>artist_id<\/code> is on the &quot;many&quot; end and the primary key is at the &quot;one&quot; end.<\/p>\n<p>Since the pattern of foreign and primary key placement is so consistent and follows the &quot;many&quot; and &quot;one&quot; ends of the lines, we never include either the primary or foreign key columns in our diagram of the data model as shown in the second diagram as shown in Figure. The columns are thought of as &quot;implementation details&quot; to capture the nature of the relationship details and not an essential part of the data being modeled.<\/p>\n<p><img decoding=\"async\" src=\"\/wp-content\/uploads\/2025\/10\/image-1760834905388.png\" alt=\"file\" \/><br \/>\nA Succinct One-to-Many Data Model<\/p>\n<h3>Automatically creating primary keys<\/h3>\n<p>In the above example, we arbitrarily assigned Frank the primary key of 42. However when we are inserting millions or rows, it is nice to have the database automatically generate the values for the id column. We do this by declaring the <code>id<\/code> column as a <code>PRIMARY KEY<\/code> and leave out the <code>id<\/code> value when inserting the row:<\/p>\n<pre><code class=\"language-python\">DROP TABLE IF EXISTS Artist;\nCREATE TABLE Artist (id INTEGER PRIMARY KEY,\n    name TEXT, eyes TEXT);\nINSERT INTO Artist (name, eyes)\n   VALUES (&#039;Frank Sinatra&#039;, &#039;blue&#039;);<\/code><\/pre>\n<p>Now we have instructed the database to auto-assign us a unique value to the Frank Sinatra row. But we then need a way to have the database tell us the <code>id<\/code> value for the recently inserted row. One way is to use a <code>SELECT<\/code> statement to retrieve data from an SQLite built-in-function called <code>last_insert_rowid()<\/code>.<\/p>\n<pre><code class=\"language-python\">sqlite&gt; DROP TABLE IF EXISTS Artist;\nsqlite&gt; CREATE TABLE Artist (id INTEGER PRIMARY KEY,\n   ...&gt;     name TEXT, eyes TEXT);\nsqlite&gt; INSERT INTO Artist (name, eyes)\n   ...&gt;    VALUES (&#039;Frank Sinatra&#039;, &#039;blue&#039;);\nsqlite&gt; select last_insert_rowid();\n1\nsqlite&gt; SELECT * FROM Artist;\n1|Frank Sinatra|blue\nsqlite&gt;<\/code><\/pre>\n<p>Once we know the <code>id<\/code> of our 'Frank Sinatra' row, we can use it when we <code>INSERT<\/code> the tracks into the <code>Track<\/code> table. As a general strategy, we add these <code>id<\/code> columns to any table we create:<\/p>\n<pre><code class=\"language-python\">sqlite&gt; DROP TABLE IF EXISTS Track;\nsqlite&gt; CREATE TABLE Track (id INTEGER PRIMARY KEY,\n   ...&gt;     title TEXT, plays INTEGER, artist_id INTEGER);<\/code><\/pre>\n<p>Note that the <code>artist_id<\/code> value is the new auto-assigned row in the <code>Artist<\/code> table and that while we added an <code>INTEGER PRIMARY KEY<\/code> to the the <code>Track<\/code> table, we did not include <code>id<\/code> in the list of fields on the <code>INSERT<\/code> statements into the <code>Track<\/code> table. Again this tells the database to choose a unique value for us for the <code>id<\/code> column.<\/p>\n<pre><code class=\"language-python\">sqlite&gt; INSERT INTO Track (title, plays, artist_id)\n   ...&gt;     VALUES (&#039;My Way&#039;, 15, 1);\nsqlite&gt; select last_insert_rowid();\n1\nsqlite&gt; INSERT INTO Track (title, plays, artist_id)\n   ...&gt;     VALUES (&#039;New York&#039;, 25, 1);\nsqlite&gt; select last_insert_rowid();\n2\nsqlite&gt;<\/code><\/pre>\n<p>You can call <code>SELECT last_insert_rowid();<\/code> after each of the inserts to retrieve the value that the database assigned to the <code>id<\/code> of each newly created row. Later when we are coding in Python, we can ask for the <code>id<\/code> value in our code and store it in a variable for later use.<\/p>\n<h3>Logical keys for fast lookup<\/h3>\n<p>If we had a table full of artist and a table full of tracks, each with a foreign key link to a row in a table full of artists and we wanted to list all the tracks that were sung by 'Frank Sinatra' as follows:<\/p>\n<pre><code class=\"language-python\">SELECT title, plays, name, eyes\nFROM Track JOIN Artist\nON Track.artist_id = Artist.id\nWHERE Artist.name = &#039;Frank Sinatra&#039;;<\/code><\/pre>\n<p>Since we have two tables and a foreign key between the two tables, our data is well-modeled, but if we are going to have millions of records in the <code>Artist<\/code> table and going to do a lot of lookups by artist name, we would benefit if we gave the database a hint about our intended use of the <code>name<\/code> column.<\/p>\n<p>We do this by adding an &quot;index&quot; to a text column that we intend to use in <code>WHERE<\/code> clauses:<\/p>\n<pre><code class=\"language-python\">CREATE INDEX artist_name ON Artist(name);<\/code><\/pre>\n<p>When the database has been told that an index is needed on a column in a table, it stores extra information to make it possible to look up a row more quickly using the indexed field (<code>name<\/code> in this example). Once you request that an index be created, there is nothing special that is needed in the SQL to access the table. The database keeps the index up to data as data is inserted, deleted, and updated, and uses it aotomatically if it will increase the performance of a database query.<\/p>\n<p>These text columns that are used to find rows based on some information in the &quot;real world&quot; like the name of an artist are called Logical keys.<\/p>\n<h3>Adding constraints to the data database<\/h3>\n<p>We can also use an index to enforce a constraint (i.e. rules) on our database operations. The most common constraint is a uniqueness constraint which insists that all of the values in a column are unique. We can add the optional <code>UNIQUE<\/code> keyword, to the <code>CREATE INDEX<\/code> statement to tell the database that we would like it to enforce the constraint on our SQL. We can drop and re-create the <code>artist_name<\/code> index with a <code>UNIQUE<\/code> constraint as follows.<\/p>\n<pre><code class=\"language-python\">DROP INDEX artist_name;\nCREATE UNIQUE INDEX artist_name ON Artist(name);<\/code><\/pre>\n<p>If we try to insert 'Frank Sinatra' a second time, it will fail with an error.<\/p>\n<pre><code class=\"language-python\">sqlite&gt; SELECT * FROM Artist;\n1|Frank Sinatra|blue\nsqlite&gt; INSERT INTO Artist (name, eyes)\n   ...&gt;    VALUES (&#039;Frank Sinatra&#039;, &#039;blue&#039;);\nRuntime error: UNIQUE constraint failed: Artist.name (19)\nsqlite&gt;<\/code><\/pre>\n<p>We can tell the database to ignore any duplicate key errors by adding the <code>IGNOR<\/code> keyword to the <code>INSERT<\/code> statement as follows:<\/p>\n<pre><code class=\"language-python\">sqlite&gt; INSERT OR IGNORE INTO Artist (name, eyes)\n   ...&gt;     VALUES (&#039;Frank Sinatra&#039;, &#039;blue&#039;);\nsqlite&gt; SELECT id FROM Artist WHERE name=&#039;Frank Sinatra&#039;;\n1\nsqlite&gt;<\/code><\/pre>\n<p>By combining an <code>INSERT OR IGNORE<\/code> and a <code>SELECT<\/code> we can insert a new record if the name is not already there and whether or not the record is already there, retrieve the primary key of the record.<\/p>\n<pre><code class=\"language-python\">sqlite&gt; INSERT OR IGNORE INTO Artist (name, eyes)\n   ...&gt;      VALUES (&#039;Elvis&#039;, &#039;blue&#039;);\nsqlite&gt; SELECT id FROM Artist WHERE name=&#039;Elvis&#039;;\n2\nsqlite&gt; SELECT * FROM Artist;\n1|Frank Sinatra|blue\n2|Elvis|blue\nsqlite&gt;<\/code><\/pre>\n<p>Since we have not added a uniqueness constraint to the eye color column, there is no problem having multiple 'Blue' values in the <code>eye<\/code> column.<\/p>\n<p><img decoding=\"async\" src=\"\/wp-content\/uploads\/2025\/10\/image-1760860811929.png\" alt=\"file\" \/><br \/>\nTracks, Albums, and Artist<\/p>\n<h3>Sample multi-table application<\/h3>\n<p>A sample application called <code>tracks_csv.py<\/code> shows how these ideas can be combined to parse textual data and load it into several tables using a proper data model with relational connections between the tables.<\/p>\n<p>This application reads and pases a comma-separated file <code>track.csv<\/code> based on an export from Dr.Chunk's iTunes library.<\/p>\n<pre><code class=\"language-python\">Another One Bites The Dust,Queen,Greatest Hits,55,100,217103\nAsche Zu Asche,Rammstein,Herzeleid,79,100,231810\nBeauty School Dropout,Various,Grease,48,100,239960\nBlack Dog,Led Zeppelin,IV,109,100,296620\n...<\/code><\/pre>\n<p>The columns in this file are: title, artist, album, number of plays, rating(0-100) and length in milliseconds.<\/p>\n<p>Our data model is shown in Figure and described in SQL as follows:<\/p>\n<pre><code class=\"language-python\">DROP TABLE IF EXISTS Artist;\nDROP TABLE IF EXISTS Album;\nDROP TABLE IF EXISTS Track;\n\nCREATE TABLE Artist (\n    id INTEGER PRIMARY KEY,\n    name TEXT UNIQUE\n);\n\nCREATE TABLE Album (\n    id INTEGER PRIMARY KEY,\n    artist_id  INTEGER,\n    title TEXT UNIQUE\n);\n\nCREATE TABLE Track (\n    id INTEGER PRIMARY KEY,\n    title TEXT UNIQUE,\n    album_id INTEGER,\n    len INTEGER, rating INTEGER, count INTEGER\n);<\/code><\/pre>\n<p>We are adding the <code>UNIQUE<\/code> keyword to <code>TEXT<\/code> columns that we would like to have a uniqueness constraint that we will use in <code>INSERT IGNORE<\/code> statements. This is more succinct that separate <code>CREATE INDEX<\/code> statements but has the same effect.<\/p>\n<pre><code class=\"language-python\">import sqlite3\n\n# \u8fde\u63a5\u6570\u636e\u5e93\uff08\u82e5\u4e0d\u5b58\u5728\u5219\u521b\u5efa trackdb.sqlite \u6587\u4ef6\uff09\nconn = sqlite3.connect(&#039;trackdb.sqlite&#039;)\n# \u521b\u5efa\u6e38\u6807\u5bf9\u8c61\uff08\u7528\u4e8e\u6267\u884c SQL \u8bed\u53e5\uff09\ncur = conn.cursor()\n\n# \u5b9a\u4e49\u8981\u6267\u884c\u7684 SQL \u8bed\u53e5\uff08\u521b\u5efa\u8868\u7ed3\u6784\uff09\nsql_statements = &quot;&quot;&quot;\nDROP TABLE IF EXISTS Artist;\nDROP TABLE IF EXISTS Album;\nDROP TABLE IF EXISTS Track;\n\nCREATE TABLE Artist (\n    id INTEGER PRIMARY KEY,\n    name TEXT UNIQUE\n);\n\nCREATE TABLE Album (\n    id INTEGER PRIMARY KEY,\n    artist_id  INTEGER,\n    title TEXT UNIQUE\n);\n\nCREATE TABLE Track (\n    id INTEGER PRIMARY KEY,\n    title TEXT UNIQUE,\n    album_id INTEGER,\n    len INTEGER, rating INTEGER, count INTEGER\n);\n&quot;&quot;&quot;\n\n# \u6267\u884c SQL \u8bed\u53e5\uff08\u4f7f\u7528 executescript \u4e00\u6b21\u6027\u6267\u884c\u591a\u6761\u8bed\u53e5\uff09\ncur.executescript(sql_statements)\n\n# \u63d0\u4ea4\u4e8b\u52a1\uff08\u786e\u4fdd\u6240\u6709\u64cd\u4f5c\u88ab\u5199\u5165\u6570\u636e\u5e93\u6587\u4ef6\uff09\nconn.commit()\n\n# \u5173\u95ed\u6e38\u6807\u548c\u8fde\u63a5\ncur.close()\nconn.close()\n\nprint(&quot;\u6570\u636e\u5e93\u8868\u7ed3\u6784\u5df2\u521b\u5efa\u5e76\u4fdd\u5b58\u5230 trackdb.sqlite&quot;)<\/code><\/pre>\n<p>With these tables in place, we write the following code <code>tracks_csv.py<\/code> to parse the data and insert it into the tables:<\/p>\n<pre><code class=\"language-python\">import sqlite3\n\nconn = sqlite3.connect(&#039;trackdb.sqlite&#039;)\ncur = conn.cursor()\n\nhandle = open(&#039;tracks.csv&#039;)\n\nfor line in handle:\n    line = line.strip();\n    pieces = line.split(&#039;,&#039;)\n    if len(pieces) != 6 : continue\n\n    name = pieces[0]\n    artist = pieces[1]\n    album = pieces[2]\n    count = pieces[3]\n    rating = pieces[4]\n    length = pieces[5]\n\n    print(name, artist, album, count, rating, length)\n\n    cur.execute(&#039;&#039;&#039;INSERT OR IGNORE INTO Artist (name)\n        VALUES ( ? )&#039;&#039;&#039;, ( artist, ) )\n    cur.execute(&#039;SELECT id FROM Artist WHERE name = ? &#039;, (artist, ))\n    artist_id = cur.fetchone()[0]\n\n    cur.execute(&#039;&#039;&#039;INSERT OR IGNORE INTO Album (title, artist_id)\n        VALUES ( ?, ? )&#039;&#039;&#039;, ( album, artist_id ) )\n    cur.execute(&#039;SELECT id FROM Album WHERE title = ? &#039;, (album, ))\n    album_id = cur.fetchone()[0]\n\n    cur.execute(&#039;&#039;&#039;INSERT OR REPLACE INTO Track\n        (title, album_id, len, rating, count)\n        VALUES ( ?, ?, ?, ?, ? )&#039;&#039;&#039;,\n        ( name, album_id, length, rating, count ) )\n\n    conn.commit()<\/code><\/pre>\n<p>You can see that we are repeating the pattern of <code>INSERT OR IGNORE<\/code> followed by a <code>SELECT<\/code> to get the appropriate <code>artist_id<\/code> and <code>album_id<\/code> for use in later <code>INSERT<\/code> statement. We start from <code>Artist<\/code> because we need <code>artist_id<\/code> to insert the <code>Album<\/code> and need the <code>album_id<\/code> to insert the <code>Track<\/code>.<\/p>\n<p>If we look at the <code>Album<\/code> table, we can see that the entries were added and assigned a primary key as necessary as the data was parsed. We can also see the foreign key pointing to a row in the <code>Artist<\/code> table for each <code>Album<\/code> row.<\/p>\n<pre><code class=\"language-python\">sqlite&gt; .mode column\nsqlite&gt; SELECT * FROM Album LIMIT 5;\nid  artist_id  title\n--  ---------  -----------------\n1   1          Greatest Hits\n2   2          Herzeleid\n3   3          Grease\n4   4          IV\n5   5          The Wall [Disc 2]<\/code><\/pre>\n<p>We can reconstruct all of the <code>Track<\/code> data, following all the relations using <code>JOIN \/ ON<\/code> clauses. You can see both ends of each of the (2) relational connections in each row in the output below:<\/p>\n<pre><code class=\"language-python\">sqlite&gt; .mode line\nsqlite&gt; SELECT * FROM Track\n   ...&gt; JOIN Album ON Track.album_id = Album.id\n   ...&gt; JOIN Artist ON Album.artist_id = Artist.id\n   ...&gt; LIMIT 2;\n       id = 1\n    title = Another One Bites The Dust\n album_id = 1\n      len = 217103\n   rating = 100\n    count = 55\n       id = 1\nartist_id = 1\n    title = Greatest Hits\n       id = 1\n     name = Queen\n\n       id = 2\n    title = Asche Zu Asche\n album_id = 2\n      len = 231810\n   rating = 100\n    count = 79\n       id = 2\nartist_id = 2\n    title = Herzeleid\n       id = 2\n     name = Rammstein<\/code><\/pre>\n<p>This example shows three tables and two one-to-many relationships between the tables. It also shows how to use indexes and uniqueness constraints to programmatically construct the tables and their relationships.<\/p>\n<p><a href=\"https:\/\/en.wikipedia.org\/wiki\/One-to-many_(data_model)\">https:\/\/en.wikipedia.org\/wiki\/One-to-many_(data_model)<\/a><\/p>\n<p>Up next we will look at the many-to-many relationships in data models.<\/p>\n<h3>Many to many relationships in database<\/h3>\n<p>Some data relationships cannot be modeled by a simple on-to-many relationship. For example, lets say we are going to build a data model for a course management system. There will be courses, users, and rosters. A user can be on the roster for many courses and a course will have many users on its roster.<\/p>\n<p>It is pretty simple to draw a manyj-to-many relationship as shown if Figure. We simply draw two tables and connect them with a line that has the &quot;many&quot; indicator on both ends of the lines. The problem is how to implement the relationship using primary keys and foreign keys.<\/p>\n<p>Before we explore how we implement many-to-many relationships, let's see if we could hack something up by extending a one-to-many relationship.<\/p>\n<p><img decoding=\"async\" src=\"\/wp-content\/uploads\/2025\/10\/image-1760963603765.png\" alt=\"file\" \/><br \/>\nA Many to Many Relationship<\/p>\n<p>If SQL supported the notion of arrays, we might try to define this:<\/p>\n<pre><code class=\"language-python\">CREATE TABLE Course (\n    id     INTEGER PRIMARY KEY,\n    title  TEXT UNIQUE\n    student_ids ARRAY OF INTEGER;\n);<\/code><\/pre>\n<p>Sadly, while this is a tempting idea, SQL does not support arrays.<\/p>\n<p>Or we could just make long string and concatenate all the <code>User<\/code> primary keys into a long string separated by commas.<\/p>\n<pre><code class=\"language-python\">CREATE TABLE Course (\n    id     INTEGER PRIMARY KEY,\n    title  TEXT UNIQUE\n    student_ids ARRAY OF INTEGER;\n);\n\nINSERT INTO Course (title, student_ids)\nVALUES( &#039;si311&#039;, &#039;1,3,4,5,6,9,14&#039;);<\/code><\/pre>\n<p>This would be very inefficient because as the course roster grows in size and the number of courses increases it becomes quite expensive to figure out which courses have student 14 on their roster.<\/p>\n<p><img decoding=\"async\" src=\"\/wp-content\/uploads\/2025\/10\/image-1760965106610.png\" alt=\"file\" \/><br \/>\nA Many to Many Connector Table<\/p>\n<p>Instead of either of these approches, we model a many-to-many relationship using an additional table that we call a &quot;junction table&quot;, &quot;through table&quot;, &quot;connector table&quot;, or &quot;join table&quot; as shown in Figure. The purpose of this table is to capture the connection between a course and a student.<\/p>\n<p>In a sense the table sits between the <code>Course<\/code> and <code>User<\/code> table and has a one-to-many relationship to both tables. By using an intermediate table we break a many-to-many relationship into two one-to-many relationships. Databases are very good at modeling and processing one-to-many relationships.<\/p>\n<p>An example <code>Member<\/code> table would be as follows:<\/p>\n<pre><code class=\"language-python\">CREATE TABLE User (\n    id     INTEGER PRIMARY KEY,\n    name   TEXT UNIQUE\n);\n\nCREATE TABLE Course (\n    id     INTEGER PRIMARY KEY,\n    title  TEXT UNIQUE\n);\n\nCREATE TABLE Member (\n    user_id     INTEGER,\n    course_id   INTEGER,\n    PRIMARY KEY (user_id, course_id)\n);<\/code><\/pre>\n<p>Following our naming convention, <code>Member.user_id<\/code> and <code>Member.course_id<\/code> are foreign keys pointing at the corresponding rows in the <code>User<\/code> and <code>Course<\/code> tables. Each entry in the member table links a row in the <code>User<\/code> table to a row in the <code>Course<\/code> table by going through the <code>Member<\/code> table.<\/p>\n<p>We indicate that the combination of <code>course_id<\/code> and <code>user_id<\/code> is the <code>PRIMARY KEY<\/code> for the <code>Member<\/code> table, also creating an uniqueness constraint for a <code>course_id<\/code> \/ <code>user_id<\/code> combination.<\/p>\n<p>Now lets say we need to insert a number of students into the rosters of a number of courses. Lets assume the data comes to us in a JSON-formatted file with records like this:<\/p>\n<pre><code class=\"language-json\">[\n  [ &quot;Charley&quot;, &quot;si110&quot;],\n  [ &quot;Mea&quot;, &quot;si110&quot;],\n  [ &quot;Hattie&quot;, &quot;si110&quot;],\n  [ &quot;Keziah&quot;, &quot;si110&quot;],\n  [ &quot;Rosa&quot;, &quot;si106&quot;],\n  [ &quot;Mea&quot;, &quot;si106&quot;],\n  [ &quot;Mairin&quot;, &quot;si106&quot;],\n  [ &quot;Zendel&quot;, &quot;si106&quot;],\n  [ &quot;Honie&quot;, &quot;si106&quot;],\n  [ &quot;Rosa&quot;, &quot;si106&quot;],\n...\n]<\/code><\/pre>\n<p>We could write code as follows to read the JSON file and insert the members of each course roster into the database using the following code:<\/p>\n<pre><code class=\"language-python\">import json\nimport sqlite3\n\nconn = sqlite3.connect(&#039;rosterdb.sqlite&#039;)\ncur = conn.cursor()\n\nstr_data = open(&#039;roster_data_sample.json&#039;).read()\njson_data = json.loads(str_data)\n\nfor entry in json_data:\n\n    name = entry[0]\n    title = entry[1]\n\n    print((name, title))\n\n    cur.execute(&#039;&#039;&#039;INSERT OR IGNORE INTO User (name)\n        VALUES ( ? )&#039;&#039;&#039;, ( name, ) )\n    cur.execute(&#039;SELECT id FROM User WHERE name = ? &#039;, (name, ))\n    user_id = cur.fetchone()[0]\n\n    cur.execute(&#039;&#039;&#039;INSERT OR IGNORE INTO Course (title)\n        VALUES ( ? )&#039;&#039;&#039;, ( title, ) )\n    cur.execute(&#039;SELECT id FROM Course WHERE title = ? &#039;, (title, ))\n    course_id = cur.fetchone()[0]\n\n    cur.execute(&#039;&#039;&#039;INSERT OR REPLACE INTO Member\n        (user_id, course_id) VALUES ( ?, ? )&#039;&#039;&#039;,\n        ( user_id, course_id ) )\n\n    conn.commit()<\/code><\/pre>\n<p>Like in a previous example, we first make sure that we have an entry in the <code>User<\/code> table and know the primary key of the entry as well as an entry in the <code>Course<\/code> table and know its primary key. We use the 'INSERT OR IGNORE' and 'SELECT' pattern so our code works regardless of whether the record is in the table or not.<\/p>\n<p>Our insert into the <code>Member<\/code> table is simply inserting the two integers as a new or existing row depending on the constraint to make sure wo do not end up with duplicate entries in the <code>Member<\/code> table for a particular <code>user_id<\/code> \/ <code>course_id<\/code> combination.<\/p>\n<p>To reconstruct our data across all three tables, we again use <code>JOIN<\/code> \/ <code>ON<\/code> to construct a <code>SELECT<\/code> query;<\/p>\n<pre><code class=\"language-python\">sqlite&gt; SELECT * FROM Course\n   ...&gt; JOIN Member ON Course.id = Member.course_id\n   ...&gt; JOIN User ON Member.user_id = User.id;\n+----+-------+---------+-----------+----+---------+\n| id | title | user_id | course_id | id |  name   |\n+----+-------+---------+-----------+----+---------+\n| 1  | si110 | 1       | 1         | 1  | Charley |\n| 1  | si110 | 2       | 1         | 2  | Mea     |\n| 1  | si110 | 3       | 1         | 3  | Hattie  |\n| 1  | si110 | 4       | 1         | 4  | Lyena   |\n| 1  | si110 | 5       | 1         | 5  | Keziah  |\n| 1  | si110 | 6       | 1         | 6  | Ellyce  |\n| 1  | si110 | 7       | 1         | 7  | Thalia  |\n| 1  | si110 | 8       | 1         | 8  | Meabh   |\n| 2  | si106 | 2       | 2         | 2  | Mea     |\n| 2  | si106 | 10      | 2         | 10 | Mairin  |\n| 2  | si106 | 11      | 2         | 11 | Zendel  |\n| 2  | si106 | 12      | 2         | 12 | Honie   |\n| 2  | si106 | 9       | 2         | 9  | Rosa    |\n+----+-------+---------+-----------+----+---------+\nsqlite&gt;<\/code><\/pre>\n<p>You can see the three tables from left to right - <code>Course<\/code>, <code>Member<\/code>, and <code>User<\/code> and you can see the connections between the primary keys and foreign keys in each row of output.<\/p>\n<h3>Modeling data at the many-to-many connection<\/h3>\n<p>While we have presented the &quot;join table&quot; as having two foreign keys making a connection between rows in two tables, this is the simplest form of a join table. It is quite common to want to add some data to the connection itself.<\/p>\n<p>Continuing with our example of users, courses, and rosters to model a simple learning management system, we will also need to understand the role that each user is assigned in each course.<\/p>\n<p>If we first try to solve this by adding a &quot;instructor&quot; flag to the <code>User<\/code> table, we will find that this does not work because a user can be a instructor in one course and a student in another course. If we add an <code>instructor_id<\/code> to the <code>Course<\/code> table it will not work because a course can have multiple instructors. And there is no one-to-many hack that can deal with the fact that the number of roles will expand into roles like Teaching Assistant or Parent.<\/p>\n<p>But if we simply add a <code>role<\/code> column to the <code>Member<\/code> table - we can represent a wide range of roles, role combinations, etc.<\/p>\n<p>Lets change our member table as follows:<\/p>\n<pre><code class=\"language-python\">DROP TABLE Member;\n\nCREATE TABLE Member (\n    user_id     INTEGER,\n    course_id   INTEGER,\n    role        INTEGER,\n    PRIMARY KEY (user_id, course_id)\n);<\/code><\/pre>\n<p>For simplicity, we will decide that zero in the role means &quot;student&quot; and one in the role means instructor. Lets assume our JSON data is augmented with the role as follows:<\/p>\n<pre><code class=\"language-json\">[\n  [ &quot;Charley&quot;, &quot;si110&quot;, 1],\n  [ &quot;Mea&quot;, &quot;si110&quot;, 0],\n  [ &quot;Hattie&quot;, &quot;si110&quot;, 0],\n  [ &quot;Keziah&quot;, &quot;si110&quot;, 0],\n  [ &quot;Rosa&quot;, &quot;si106&quot;, 0],\n  [ &quot;Mea&quot;, &quot;si106&quot;, 1],\n  [ &quot;Mairin&quot;, &quot;si106&quot;, 0],\n  [ &quot;Zendel&quot;, &quot;si106&quot;, 0],\n  [ &quot;Honie&quot;, &quot;si106&quot;, 0],\n  [ &quot;Rosa&quot;, &quot;si106&quot;, 0],\n...\n]<\/code><\/pre>\n<p>We could alter the <code>roster.py<\/code> program above to incorporate role as follows:<\/p>\n<pre><code class=\"language-python\">for entry in json_data:\n\n    name = entry[0]\n    title = entry[1]\n    role = entry[2]\n\n    ...\n\n    cur.execute(&#039;&#039;&#039;INSERT OR REPLACE INTO Member\n        (user_id, course_id, role) VALUES ( ?, ?, ? )&#039;&#039;&#039;,\n        ( user_id, course_id, role ) )<\/code><\/pre>\n<p>In a real system, we would probably build a <code>Role<\/code> table and make the <code>role<\/code> column in <code>Member<\/code> a foreign key into the Role table as follows:<\/p>\n<pre><code class=\"language-python\">DROP TABLE Member;\n\nCREATE TABLE Member (\n    user_id     INTEGER,\n    course_id   INTEGER,\n    role_id     INTEGER,\n    PRIMARY KEY (user_id, course_id, role_id)\n);\n\nCREATE TABLE Role (\n    id          INTEGER PRIMARY KEY,\n    name        TEXT UNIQUE\n);\n\nINSERT INTO Role (id, name) VALUES (0, &#039;Student&#039;);\nINSERT INTO Role (id, name) VALUES (1, &#039;Instructor&#039;);<\/code><\/pre>\n<p>Notice that because we declared the <code>id<\/code> column in the <code>Role<\/code> table as a <code>PRIMARY KEY<\/code>, we could omit it in the <code>INSERT<\/code> statement. But we can also choose the <code>id<\/code> value as long as the value is not already in the <code>id<\/code> column and does not vialate the implied <code>UNIQUE<\/code> constraint on primary keys.<\/p>\n<h3>Summary<\/h3>\n<p>This chapter has covered a lot of ground to give you an overview of the basics of using a database in Python. It is more complicated to write the code to use a database to store data than Python dictionaries or flat files so there is little reason to use a database unless your application truly needs the capabilities of a database. The situations where a database can be quite usefull are:<\/p>\n<ol>\n<li>\n<p>when your application needs to make many small random updates within a large data set<\/p>\n<\/li>\n<li>\n<p>when your data is so large it cannot fit in a dictionary and you need to look up information repeatedly, or<\/p>\n<\/li>\n<li>\n<p>when you have a long-running process that you want to be able to stop and restart and retain the data from one run to the next.<\/p>\n<\/li>\n<\/ol>\n<p>You can build a simple database with a single table to suit many application needs, but most problems will require several tables and links\/relationships between rows in different tables. When you start making links between tables, it is important to do some thoughtful design and follow the rules of database normalization to make the best use of the database's capabilities. Since the primary motivation for using a database is that you have a large amount of data to deal withi, it is important to model your data efficiently so your programs run as fast as possible.<\/p>\n<h3>Debugging<\/h3>\n<p>One common pattern when you are developing a Python program to connect to an SQLite database will be to run a Python program and check the results using the Database Browser for SQLite. The browser allows you to quickly check to see if your program is working properly.<\/p>\n<p>You must be careful because SQLite takes care to keep two programs from changing the same data at the same time. For example, if you open a database in the browser and make a change to the database and have not yet pressed the &quot;save&quot; button in the browser, the browser &quot;locks&quot; the database file and keeps any other program from accessing the file. In particular, your Python program will not be able to access the file if it is locked.<\/p>\n<p>So a solution is to make sure to either close the database browser or use the file menu to close the database in the browser before you attempt to access the database from Python to avoid the problem of your Python code failing because the database is locked.<\/p>\n<h3>Glossary<\/h3>\n<p><strong>attribute<\/strong><br \/>\nOne of the values within a tuple. More commonly called a &quot;column&quot; or &quot;field&quot;.<\/p>\n<p><strong>constraint<\/strong><br \/>\nWhen we tell the database to enforce a rule on a field or a row in a table. A common constraint is to insist that there can be no duplicate values in a particular field (i.e., all the values must be unique).<\/p>\n<p><strong>cursor<\/strong><br \/>\nA cursor allows you to execute SQL commands in a database and retrieve data from the database. A cursor is similar to a socket or file handle for network connections and files, respectively.<\/p>\n<p><strong>database browser<\/strong><br \/>\nA piece of software that allows you to directly connect to a database and manipulate the database directly without writing a program.<\/p>\n<p><strong>foreign key<\/strong><br \/>\nA numeric key that points to the primary key of a row in another table. Foreign keys establish relationships between rows stored in different tables.<\/p>\n<p><strong>index<\/strong><br \/>\nAdditional data that the database software maintains as rows and inserts into a table to make lookups very fast.<\/p>\n<p><strong>logical key<\/strong><br \/>\nA key that the &quot;outside world&quot; uses to look up a particular row. For example in a table of user accounts, a person's email address might be a good candidate as the logical key for the user's data.<\/p>\n<p><strong>normalization<\/strong><br \/>\nDesigning a data model so that no data is replicated. We store each item of data at one place in the database and reference it elsewhere using a foreign key.<\/p>\n<p><strong>primary key<\/strong><br \/>\nA numeric key assigned to each row that is used to refer to one row in a table from another table. Often the database is configured to automatically assign primary keys as rows are inserted.<\/p>\n<p><strong>relation<\/strong><br \/>\nAn area within a database that contains tuples and attributes. More typically called a &quot;table&quot;.<\/p>\n<p><strong>tuple<\/strong><br \/>\nA single entry in a database table that is a set of attributes. More typically called &quot;row&quot;.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Using Databases and SQL What is a database? A database &#8230; &raquo; <a class=\"read-more-link\" href=\"http:\/\/blue.yn.cn\/?p=2566\">\u9605\u8bfb\u5168\u6587<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[24],"tags":[],"class_list":["post-2566","post","type-post","status-publish","format-standard","hentry","category-python"],"_links":{"self":[{"href":"http:\/\/blue.yn.cn\/index.php?rest_route=\/wp\/v2\/posts\/2566","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/blue.yn.cn\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/blue.yn.cn\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/blue.yn.cn\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/blue.yn.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=2566"}],"version-history":[{"count":11,"href":"http:\/\/blue.yn.cn\/index.php?rest_route=\/wp\/v2\/posts\/2566\/revisions"}],"predecessor-version":[{"id":2586,"href":"http:\/\/blue.yn.cn\/index.php?rest_route=\/wp\/v2\/posts\/2566\/revisions\/2586"}],"wp:attachment":[{"href":"http:\/\/blue.yn.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=2566"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/blue.yn.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=2566"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/blue.yn.cn\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=2566"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}