
Python: Working with a Database, Part 1/2: Using the DB-API
- Tutorial
The Python DB-API is not a specific library, but a set of rules to which individual modules that work with specific databases obey. The individual implementation nuances for different databases may differ, but general principles allow you to use the same approach when working with different databases.

The article discusses the basic methods of DB-API, allowing you to fully work with the database. The full list can be found at the links at the end of the article.
Required Level : Basic understanding of SQL and Python syntax.
To begin, consider the most basic DB-API template, which we will use in all further examples:
When working with other databases, additional connection parameters are used, for example, for PostrgeSQL:
Please note: After receiving the result from the cursor, the second time you can’t get it without repeating the query itself - an empty result will return!
Note : If several connections are established to the database and one of them modifies the database, then the SQLite database is locked until the transaction is completed (connection method. Commit ()) or canceled (connection method. Rollback () method ).
Long queries can be divided into several lines in any order, if they are enclosed in triple quotes - single ('' '...' '') or double ("" "..." "")
Of course, in such a simple example, the breakdown does not make sense, but on complex long queries it can dramatically increase the readability of the code.
The .execute () cursor method allows you to make only one request at a time, when you try to make several through the semicolon there will be an error.
To solve this problem, you can either call the cursor method several times. execute ()
Or use the cursor method. executescript ()
This method is also convenient when our queries are stored in a separate variable or even in a file and we need to apply such a query to the database.
Important ! Never, under any circumstances, use string concatenation (+) or parameter interpolation in a string (%) to transfer variables to an SQL query. Such a query formation, if it is possible for user data to fall into it, is the gateway to SQL injection!
The correct way is to use the second argument to the .execute () method.
Two options are possible:
Note 1 : In PostgreSQL (UPD: and in MySQL) instead of the '?' used for substitution:% s
Note 2 : In this way, table names cannot be replaced, one of the possible solutions in this case is considered here: stackoverflow.com/questions/3247183/variable-table-name-in-sqlite/3247553#3247553
UPD: Note 3 : Thank Igelko for mentioning paramstyle parameter - it determines which style is used to substitute variables in this module.
Here is a link with a useful trick for working with different lookup styles .
It always returns a tuple or None. if the request is empty.
Important ! The standard cursor takes all the data from the server at once, regardless of whether we use .fetchall () or .fetchone ()
I thank paratagas for a valuable addition:
For greater program stability (especially during write operations), you can wrap the database access instructions in try-except-else blocks and use the native error object in sqlite3, for example, like this:
Thanks to KurtRotzke for the valuable addition:
Recent versions of psycopg2 allow you to do this:
Some objects in Python have __enter__ and __exit__ methods, which allows you to "cleanly" interact with them, as in the example above.
Thanks to remzalp for the valuable addition:
Using row_factory allows you to take metadata from the query and ultimately access the result, for example, by column name.
Essentially, a callback for processing data when returning a string. Yes, and the most useful cursor.description, where there is everything you need.
Example from the documentation:
The second part of the article is under development, which will discuss working with the database in Python using SQLAlchemy.

The article discusses the basic methods of DB-API, allowing you to fully work with the database. The full list can be found at the links at the end of the article.
Required Level : Basic understanding of SQL and Python syntax.
We prepare inventory for further comfortable work
- Python has built-in support for SQLite database, for this you do not need to install anything extra, just specify the import of the standard library in the script
import sqlite3
- Download the test database, which we will work with. This article will use the “Chinook” open test database (MIT license). You can download it at the following links:
chinookdatabase.codeplex.com
github.com/lerocha/chinook-database
We only need the binary file “Chinook_Sqlite.sqlite” to work. - For the convenience of working with the database (viewing, editing), we need a database browser program that supports SQLite. The article does not discuss working with the browser, but it will help you visually see what is happening with the database during our experiments.
Note : when making changes to the database, do not forget to apply them, since the database with unapplied changes remains locked.
You can use (the last two options are cross-platform and free):- The familiar utility for working with the database as part of your IDE;
- SQLite Database Browser
- SQLiteStudio
Python DB-API modules depending on the database
Database | DB-API module |
---|---|
Sqlite | sqlite3 |
PostgreSQL | psycopg2 |
MySQL | mysql.connector |
ODBC | pyodbc |
Connection to the base, getting the cursor
To begin, consider the most basic DB-API template, which we will use in all further examples:
# Импортируем библиотеку, соответствующую типу нашей базы данных
import sqlite3
# Создаем соединение с нашей базой данных
# В нашем примере у нас это просто файл базы
conn = sqlite3.connect('Chinook_Sqlite.sqlite')
# Создаем курсор - это специальный объект который делает запросы и получает их результаты
cursor = conn.cursor()
# ТУТ БУДЕТ НАШ КОД РАБОТЫ С БАЗОЙ ДАННЫХ
# КОД ДАЛЬНЕЙШИХ ПРИМЕРОВ ВСТАВЛЯТЬ В ЭТО МЕСТО
# Не забываем закрыть соединение с базой данных
conn.close()
When working with other databases, additional connection parameters are used, for example, for PostrgeSQL:
conn = psycopg2.connect( host=hostname, user=username, password=password, dbname=database)
Reading from the base
# Делаем SELECT запрос к базе данных, используя обычный SQL-синтаксис
cursor.execute("SELECT Name FROM Artist ORDER BY Name LIMIT 3")
# Получаем результат сделанного запроса
results = cursor.fetchall()
results2 = cursor.fetchall()
print(results) # [('A Cor Do Som',), ('Aaron Copland & London Symphony Orchestra',), ('Aaron Goldberg',)]
print(results2) # []
Please note: After receiving the result from the cursor, the second time you can’t get it without repeating the query itself - an empty result will return!
Write to the database
# Делаем INSERT запрос к базе данных, используя обычный SQL-синтаксис
cursor.execute("insert into Artist values (Null, 'A Aagrh!') ")
# Если мы не просто читаем, но и вносим изменения в базу данных - необходимо сохранить транзакцию
conn.commit()
# Проверяем результат
cursor.execute("SELECT Name FROM Artist ORDER BY Name LIMIT 3")
results = cursor.fetchall()
print(results) # [('A Aagrh!',), ('A Cor Do Som',), ('Aaron Copland & London Symphony Orchestra',)]
Note : If several connections are established to the database and one of them modifies the database, then the SQLite database is locked until the transaction is completed (connection method. Commit ()) or canceled (connection method. Rollback () method ).
Break the query into multiple lines in triple quotation marks
Long queries can be divided into several lines in any order, if they are enclosed in triple quotes - single ('' '...' '') or double ("" "..." "")
cursor.execute("""
SELECT name
FROM Artist
ORDER BY Name LIMIT 3
""")
Of course, in such a simple example, the breakdown does not make sense, but on complex long queries it can dramatically increase the readability of the code.
Combining database queries in one method call
The .execute () cursor method allows you to make only one request at a time, when you try to make several through the semicolon there will be an error.
For those who do not believe the word:
cursor.execute("""
insert into Artist values (Null, 'A Aagrh!');
insert into Artist values (Null, 'A Aagrh-2!');
""")
# sqlite3.Warning: You can only execute one statement at a time.
To solve this problem, you can either call the cursor method several times. execute ()
cursor.execute("""insert into Artist values (Null, 'A Aagrh!');""")
cursor.execute("""insert into Artist values (Null, 'A Aagrh-2!');""")
Or use the cursor method. executescript ()
cursor.executescript("""
insert into Artist values (Null, 'A Aagrh!');
insert into Artist values (Null, 'A Aagrh-2!');
""")
This method is also convenient when our queries are stored in a separate variable or even in a file and we need to apply such a query to the database.
We do a value substitution in the request
Important ! Never, under any circumstances, use string concatenation (+) or parameter interpolation in a string (%) to transfer variables to an SQL query. Such a query formation, if it is possible for user data to fall into it, is the gateway to SQL injection!
The correct way is to use the second argument to the .execute () method.
Two options are possible:
# C подставновкой по порядку на места знаков вопросов:
cursor.execute("SELECT Name FROM Artist ORDER BY Name LIMIT ?", ('2'))
# И с использованием именнованных замен:
cursor.execute("SELECT Name from Artist ORDER BY Name LIMIT :limit", {"limit": 3})
Note 1 : In PostgreSQL (UPD: and in MySQL) instead of the '?' used for substitution:% s
Note 2 : In this way, table names cannot be replaced, one of the possible solutions in this case is considered here: stackoverflow.com/questions/3247183/variable-table-name-in-sqlite/3247553#3247553
UPD: Note 3 : Thank Igelko for mentioning paramstyle parameter - it determines which style is used to substitute variables in this module.
Here is a link with a useful trick for working with different lookup styles .
We do multiple insertion of rows passing through the collection using the cursor method. executemany ()
# Обратите внимание, даже передавая одно значение - его нужно передавать кортежем!
# Именно по этому тут используется запятая в скобках!
new_artists = [
('A Aagrh!',),
('A Aagrh!-2',),
('A Aagrh!-3',),
]
cursor.executemany("insert into Artist values (Null, ?);", new_artists)
We get the results one by one using the cursor method. fetchone ()
It always returns a tuple or None. if the request is empty.
cursor.execute("SELECT Name FROM Artist ORDER BY Name LIMIT 3")
print(cursor.fetchone()) # ('A Cor Do Som',)
print(cursor.fetchone()) # ('Aaron Copland & London Symphony Orchestra',)
print(cursor.fetchone()) # ('Aaron Goldberg',)
print(cursor.fetchone()) # None
Important ! The standard cursor takes all the data from the server at once, regardless of whether we use .fetchall () or .fetchone ()
Cursor as an iterator
# Использование курсора как итератора
for row in cursor.execute('SELECT Name from Artist ORDER BY Name LIMIT 3'):
print(row)
# ('A Cor Do Som',)
# ('Aaron Copland & London Symphony Orchestra',)
# ('Aaron Goldberg',)
UPD: Improving Code Stability
I thank paratagas for a valuable addition:
For greater program stability (especially during write operations), you can wrap the database access instructions in try-except-else blocks and use the native error object in sqlite3, for example, like this:
try:
cursor.execute(sql_statement)
result = cursor.fetchall()
except sqlite3.DatabaseError as err:
print("Error: ", err)
else:
conn.commit()
UPD: Using with in psycopg2
Thanks to KurtRotzke for the valuable addition:
Recent versions of psycopg2 allow you to do this:
with psycopg2.connect("dbname='habr'") as conn:
with conn.cursor() as cur:
Some objects in Python have __enter__ and __exit__ methods, which allows you to "cleanly" interact with them, as in the example above.
UPD: Using row_factory
Thanks to remzalp for the valuable addition:
Using row_factory allows you to take metadata from the query and ultimately access the result, for example, by column name.
Essentially, a callback for processing data when returning a string. Yes, and the most useful cursor.description, where there is everything you need.
Example from the documentation:
import sqlite3
def dict_factory(cursor, row):
d = {}
for idx, col in enumerate(cursor.description):
d[col[0]] = row[idx]
return d
con = sqlite3.connect(":memory:")
con.row_factory = dict_factory
cur = con.cursor()
cur.execute("select 1 as a")
print(cur.fetchone()["a"])
Additional materials (in English)
- A short free online course - Udacity - Intro to Relational Databases - Explains the syntax and principles of SQL, Python DB-API - and theory and practice in one package. Highly recommend for beginners!
- Advanced SQLite Usage in Python
- SQLite Python Tutorial at tutorialspoint.com
- A thorough guide to SQLite database operations in Python
- UPD: The Novice's Guide to the Python 3 DB-API
- SQLite Online Guides:
The second part of the article is under development, which will discuss working with the database in Python using SQLAlchemy.
I invite you to a discussion:
- If I made a mistake somewhere or didn’t take into account something important - write in the comments, important comments will be later added to the article indicating your authorship.
- If some points are not clear and clarification is required - write your questions in the comments - or I or other readers will give an answer, and practical questions with answers will be later added to the article.