《PostgreSQL 開發指南》第 29 篇 Python 訪問 PostgreSQL

Python 是一種高級、通用的解釋型編程語言,以其優雅、準確、 簡單的語言特性,在雲計算、Web 開發、自動化運維、數據科學以及機器學習等人工智能領域獲得了廣泛應用。

Python 定義了連接和操作數據庫的標準接口 Python DB API。不同的數據庫在此基礎上實現了特定的驅動,這些驅動都實現了標準接口。

Python DB API
對於 PostgreSQL 數據庫,最常見的 Python 驅動程序就是 psycopg,它完全實現了 Python DB-API 2.0 接口規範。接下來我們介紹如何通過 psycopg 連接和操作 PostgreSQL 數據庫。

連接數據庫

首先,我們需要安裝 Python 和 psycopg 驅動。Python 可以通過官方網站下載,安裝之後可以通過以下命令查看版本信息:

PS C:\Users\dongx> python -V
Python 3.8.3

然後通過 pip 安裝最新的 psycopg:

PS C:\Users\dongx> pip install --upgrade psycopg2
Collecting psycopg2
  Using cached psycopg2-2.8.5-cp38-cp38-win_amd64.whl (1.1 MB)
Installing collected packages: psycopg2
Successfully installed psycopg2-2.8.5

爲了方便開發,我們安裝一個 IDE(集成開發環境):JetBrains 出品的 Pycharm Community Edition。在 Pycharm 中新建一個項目 PythonPostgreSQL,然後創建一個數據庫連接的配置文件 dbconfig.ini,添加以下內容:

[postgresql]
host = 192.168.56.104
port = 5432
database = hrdb
user = tony
password = tony

配置文件中存儲了數據庫的連接信息:主機、端口、數據庫、用戶以及密碼;我們需要按照自己的環境進行配置。

然後,新建一個測試數據庫連接的 Python 文件 postgresql_connection.py:

# 導入 psycopg2 模塊和 Error 對象
import psycopg2
from psycopg2 import DatabaseError
from configparser import ConfigParser

def read_db_config(filename='dbconfig.ini', section='postgresql'):
    """ 讀取數據庫配置文件,返回一個字典對象
    """
    # 創建解析器,讀取配置文件
    parser = ConfigParser()
    parser.read(filename)

    # 獲取 postgresql 部分的配置
    db = {}
    if parser.has_section(section):
        items = parser.items(section)
        for item in items:
            db[item[0]] = item[1]
    else:
        raise Exception('文件 {1} 中未找到 {0} 配置信息!'.format(section, filename))

    return db

db_config = read_db_config()
connection = None

try:
    # 使用 psycopg2.connect 方法連接 PostgreSQL 數據庫
    connection = psycopg2.connect(**db_config)

    # 創建一個遊標
    cur = connection.cursor()

    # 獲取 PostgreSQL 版本號
    cur.execute('SELECT version()')
    db_version = cur.fetchone()

    # 輸出 PostgreSQL 版本
    print("連接成功,PostgreSQL 服務器版本:", db_version)`在這裏插入代碼片`

    # 關閉遊標
    cur.close()
except (Exception, DatabaseError) as e:
    print("連接 PostgreSQL 失敗:", e)
finally:
    # 釋放數據庫連接
    if connection is not None:
        connection.close()
        print("PostgreSQL 數據庫連接已關閉。")
  • 首先,我們導入了 psycopg2 驅動和解析配置文件的 configparser 模塊;
  • 然後,創建一個讀取配置文件的 read_db_config 函數;
  • 接下來調用 psycopg2.connect 函數創建一個新的數據庫連接;
  • 然後通過連接對象的 cursor 函數創建一個新的遊標,並且執行查詢語句返回數據庫的版本;
  • 在此之後,調用遊標對象的 fetchone() 方法獲取返回結果並打印信息;
  • 最後,調用 close() 方法關閉遊標資源和數據庫連接對象。

執行以上腳本,返回的信息如下:

連接成功,PostgreSQL 服務器版本: ('PostgreSQL 12.3 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-39), 64-bit',)
PostgreSQL 數據庫連接已關閉。

創建和刪除表

建立數據庫連接之後,通過執行 CREATE TABLE 和 DROP TABLE 語句可以創建和刪除數據表。我們創建一個新的 Python 文件 postgresql_ddl.py:

# 導入 psycopg2 模塊和 Error 對象
import psycopg2
from psycopg2 import DatabaseError
from configparser import ConfigParser

def read_db_config(filename='dbconfig.ini', section='postgresql'):
    """ 讀取數據庫配置文件,返回一個字典對象
    """
    # 創建解析器,讀取配置文件
    parser = ConfigParser()
    parser.read(filename)

    # 獲取 postgresql 部分的配置
    db = {}
    if parser.has_section(section):
        items = parser.items(section)
        for item in items:
            db[item[0]] = item[1]
    else:
        raise Exception('文件 {1} 中未找到 {0} 配置信息!'.format(section, filename))

    return db

db_config = read_db_config()
connection = None

try:
    # 使用 psycopg2.connect 方法連接 PostgreSQL 數據庫
    connection = psycopg2.connect(**db_config)

    # 創建一個遊標
    cur = connection.cursor()

    # 定義 SQL 語句
    sql = """ create table users (
              id serial primary key,
              name character varying(10) not null unique,
              created_at timestamp not null 
            ) """

    # 執行 SQL 命令
    cur.execute(sql)

    # 關閉遊標
    cur.close()

    # 提交事務
    connection.commit()
    print("操作成功!")
except (Exception, DatabaseError) as e:
    print("操作失敗:", e)
finally:
    # 釋放數據庫連接
    if connection is not None:
        connection.close()
        print("PostgreSQL 數據庫連接已關閉。")

同樣是先連接數據庫;然後利用遊標對象的 execute() 方法執行 SQL 命令創建表;commit 方法用於提交事務修改,如果不執行該操作不會真正創建表,因爲 psycopg2 連接 PostgreSQL 默認不會自動提交(autocommit)。執行該腳本的結果如下:

操作成功!
PostgreSQL 數據庫連接已關閉。

如果 user 表已經存在,將會返回以下錯誤:

操作失敗: relation "users" already exists

我們可以將文件中的 sql 語句修改成“drop table users”,刪除 users 表並重建創建。

插入數據

PostgreSQL 使用 INSERT 語句插入數據;Python 中游標對象的 execute() 方法用於執行 SQL 語句,該方法可以接收參數實現預編譯語句。

# 導入 psycopg2 模塊和 Error 對象
import psycopg2
from psycopg2 import DatabaseError
from configparser import ConfigParser

def read_db_config(filename='dbconfig.ini', section='postgresql'):
    """ 讀取數據庫配置文件,返回一個字典對象
    """
    # 創建解析器,讀取配置文件
    parser = ConfigParser()
    parser.read(filename)

    # 獲取 postgresql 部分的配置
    db = {}
    if parser.has_section(section):
        items = parser.items(section)
        for item in items:
            db[item[0]] = item[1]
    else:
        raise Exception('文件 {1} 中未找到 {0} 配置信息!'.format(section, filename))

    return db

db_config = read_db_config()
connection = None

try:
    # 使用 psycopg2.connect 方法連接 PostgreSQL 數據庫
    connection = psycopg2.connect(**db_config)

    # 創建一個遊標
    cur = connection.cursor()

    # 定義 SQL 語句
    sql = """ insert into users(name, created_at)
              values (%s, %s) RETURNING id
          """

    # 執行 SQL 命令
    cur.execute(sql, ('tony', '2020-06-08 11:00:00'))

    # 獲取 id
    id = cur.fetchone()[0]

    # 提交事務
    connection.commit()
    print("操作成功! 用戶 id:", id)

    # 關閉遊標
    cur.close()
except (Exception, DatabaseError) as e:
    print("操作失敗:", e)
finally:
    # 釋放數據庫連接
    if connection is not None:
        connection.close()
        print("PostgreSQL 數據庫連接已關閉。")

sql 變量中的百分號(%)是佔位符,這些佔位符的值在 execute() 方法中進行替換;遊標對象的 fetchone 方法用於返回一行結果,這裏用於獲取 RETURNING id 返回的用戶 id。執行以上腳本返回的結果如下:

操作成功! 用戶 id: 1
PostgreSQL 數據庫連接已關閉。

如果想要查看插入 users 表中的數據,可以執行查詢操作。

查詢數據

遊標對象提供了三種獲取返回結果的方法:fetchone() 獲取下一行數據,fetchmany(size=cursor.arraysize) 獲取下一組數據行,fetchall() 返回全部數據行。

我們創建一個新的文件 postgresql_query.py:

# 導入 psycopg2 模塊和 Error 對象
import psycopg2
from psycopg2 import DatabaseError
from configparser import ConfigParser

def read_db_config(filename='dbconfig.ini', section='postgresql'):
    """ 讀取數據庫配置文件,返回一個字典對象
    """
    # 創建解析器,讀取配置文件
    parser = ConfigParser()
    parser.read(filename)

    # 獲取 postgresql 部分的配置
    db = {}
    if parser.has_section(section):
        items = parser.items(section)
        for item in items:
            db[item[0]] = item[1]
    else:
        raise Exception('文件 {1} 中未找到 {0} 配置信息!'.format(section, filename))

    return db

db_config = read_db_config()
connection = None

try:
    # 使用 psycopg2.connect 方法連接 PostgreSQL 數據庫
    connection = psycopg2.connect(**db_config)

    # 創建一個遊標
    cur = connection.cursor()

    # 定義 SQL 語句
    sql = """ select id, name, created_at
              from users
          """

    # 執行 SQL 命令
    cur.execute(sql)
    print("用戶數量:", cur.rowcount)

    # 獲取結果
    user = cur.fetchone()
    while user is not None:
        print(user)
        user = cur.fetchone()

    # 關閉遊標
    cur.close()
except (Exception, DatabaseError) as e:
    print("操作失敗:", e)
finally:
    # 釋放數據庫連接
    if connection is not None:
        connection.close()

遊標對象的 rowcount 屬性代表了返回的數據行數,fetchone() 方法返回一行數據或者 None,while 循環用於遍歷和打印查詢結果。由於 users 表中目前只有一行數據,執行以上文件的結果如下:

用戶數量: 1
(1, 'tony', datetime.datetime(2020, 6, 8, 11, 0))

修改數據

修改數據的流程與插入數據相同,只是需要將 INSERT 語句替換成 UPDATE 語句。我們創建一個新的文件 postgresql_update.py:

# 導入 psycopg2 模塊和 Error 對象
import psycopg2
from psycopg2 import DatabaseError
from configparser import ConfigParser

def read_db_config(filename='dbconfig.ini', section='postgresql'):
    """ 讀取數據庫配置文件,返回一個字典對象
    """
    # 創建解析器,讀取配置文件
    parser = ConfigParser()
    parser.read(filename)

    # 獲取 postgresql 部分的配置
    db = {}
    if parser.has_section(section):
        items = parser.items(section)
        for item in items:
            db[item[0]] = item[1]
    else:
        raise Exception('文件 {1} 中未找到 {0} 配置信息!'.format(section, filename))

    return db

db_config = read_db_config()
connection = None

try:
    # 使用 psycopg2.connect 方法連接 PostgreSQL 數據庫
    connection = psycopg2.connect(**db_config)

    # 創建一個遊標
    cur = connection.cursor()

    # 定義 SQL 語句
    sql = """ update users
              set name = %s
              where id = %s
          """

    # 執行 SQL 命令
    cur.execute(sql, ('tom', 1))

    # 獲取 id
    rows = cur.rowcount

    # 提交事務
    connection.commit()
    print("操作成功! 更新行數:", rows)

    # 再次查詢數據
    sql = """ select id, name, created_at
                  from users where id = 1
              """
    cur.execute(sql)
    user = cur.fetchone()
    print(user)

    # 關閉遊標
    cur.close()
except (Exception, DatabaseError) as e:
    print("操作失敗:", e)
finally:
    # 釋放數據庫連接
    if connection is not None:
        connection.close()

更新數據之後,再次執行了查詢語句,返回更新後的用戶信息。執行該文件的結果如下:

操作成功! 更新行數: 1
(1, 'tom', datetime.datetime(2020, 6, 8, 11, 0))

刪除數據

將 UPDATE 語句替換成 DELETE 語句,就可以刪除表中的數據。我們創建一個新的文件 postgresql_delete.py:

# 導入 psycopg2 模塊和 Error 對象
import psycopg2
from psycopg2 import DatabaseError
from configparser import ConfigParser

def read_db_config(filename='dbconfig.ini', section='postgresql'):
    """ 讀取數據庫配置文件,返回一個字典對象
    """
    # 創建解析器,讀取配置文件
    parser = ConfigParser()
    parser.read(filename)

    # 獲取 postgresql 部分的配置
    db = {}
    if parser.has_section(section):
        items = parser.items(section)
        for item in items:
            db[item[0]] = item[1]
    else:
        raise Exception('文件 {1} 中未找到 {0} 配置信息!'.format(section, filename))

    return db

db_config = read_db_config()
connection = None

try:
    # 使用 psycopg2.connect 方法連接 PostgreSQL 數據庫
    connection = psycopg2.connect(**db_config)

    # 創建一個遊標
    cur = connection.cursor()

    # 定義 SQL 語句
    sql = """ delete from users where id = %s
          """

    # 執行 SQL 命令
    cur.execute(sql, (1,))
    rows = cur.rowcount

    # 提交事務
    connection.commit()
    print("操作成功! 刪除行數:", rows)

    # 關閉遊標
    cur.close()
except (Exception, DatabaseError) as e:
    print("操作失敗:", e)
finally:
    # 釋放數據庫連接
    if connection is not None:
        connection.close()

執行該文件,返回以下結果:

操作成功! 刪除行數:1

管理事務

在前面的示例中,需要使用 connection.commit() 提交對 PostgreSQL 數據庫執行的修改,這是因爲 psycopg2 默認沒有打開自動提交功能。我們也可以利用連接對象的 autocommit 屬性設置是否自動提交。

將上文中的 postgresql_insert.py 修改如下:

# 導入 psycopg2 模塊和 Error 對象
import psycopg2
from psycopg2 import DatabaseError
from configparser import ConfigParser

def read_db_config(filename='dbconfig.ini', section='postgresql'):
    """ 讀取數據庫配置文件,返回一個字典對象
    """
    # 創建解析器,讀取配置文件
    parser = ConfigParser()
    parser.read(filename)

    # 獲取 postgresql 部分的配置
    db = {}
    if parser.has_section(section):
        items = parser.items(section)
        for item in items:
            db[item[0]] = item[1]
    else:
        raise Exception('文件 {1} 中未找到 {0} 配置信息!'.format(section, filename))

    return db

db_config = read_db_config()
connection = None

try:
    # 使用 psycopg2.connect 方法連接 PostgreSQL 數據庫
    connection = psycopg2.connect(**db_config)

    # 打印和設置自動提交
    print('默認 autocommit:', connection.autocommit)
    connection.autocommit = True
    print('新的 autocommit:', connection.autocommit)

    # 創建一個遊標
    cur = connection.cursor()

    # 定義 SQL 語句
    sql = """ insert into users(name, created_at)
              values (%s, %s) RETURNING id
          """

    # 執行 SQL 命令
    cur.execute(sql, ('tony', '2020-06-08 11:00:00'))

    # 獲取 id
    id = cur.fetchone()[0]

    print("操作成功! 用戶 id:", id)

    # 關閉遊標
    cur.close()
except (Exception, DatabaseError) as e:
    print("操作失敗:", e)
finally:
    # 釋放數據庫連接
    if connection is not None:
        connection.close()
        print("PostgreSQL 數據庫連接已關閉。")

通過 connection.autocommit 設置了自動提交,所以 INSERT 語句插入數據之後不需要再執行 commit 操作。

默認 autocommit: False
新的 autocommit: True
操作成功! 用戶 id: 2
PostgreSQL 數據庫連接已關閉。

如果一個事務中包含多個數據庫操作,還是應該在事務的最後統一執行提交,並且在異常處理部分通過連接對象的 rollback() 方法回滾部分完成的事務。

另一種管理事務的方法是使用 with 語句,這樣可以避免手動的資源管理和事務操作。我們創建一個新的文件 postgresql_transaction.py:

# 導入 psycopg2 模塊和 Error 對象
import psycopg2
from psycopg2 import DatabaseError
from configparser import ConfigParser

def read_db_config(filename='dbconfig.ini', section='postgresql'):
    """ 讀取數據庫配置文件,返回一個字典對象
    """
    # 創建解析器,讀取配置文件
    parser = ConfigParser()
    parser.read(filename)

    # 獲取 postgresql 部分的配置
    db = {}
    if parser.has_section(section):
        items = parser.items(section)
        for item in items:
            db[item[0]] = item[1]
    else:
        raise Exception('文件 {1} 中未找到 {0} 配置信息!'.format(section, filename))

    return db

db_config = read_db_config()
connection = None

try:
    # 使用 with 語句管理事務
    with psycopg2.connect(**db_config) as connection:

        # 創建一個遊標
        with connection.cursor() as cur:

            # 插入數據
            sql = """ insert into users(name, created_at)
                      values (%s, %s)
                  """
            cur.execute(sql, ('Jason', '2020-06-08 15:30:00'))

            # 更新數據
            sql = """ update users
                      set created_at = %s
                      where name = %s
                  """
            cur.execute(sql, ('2020-06-08 16:00:00', 'tony'))

            sql = """ select id, name, created_at
                      from users
                  """

            # 查詢數據
            cur.execute(sql)

            # 獲取結果
            user = cur.fetchone()
            while user is not None:
                print(user)
                user = cur.fetchone()
except (Exception, DatabaseError) as e:
    print("操作失敗:", e)
finally:
    # 釋放數據庫連接
    if connection is not None:
        connection.close()

整個事務包含插入數據、更新數據以及查詢數據三個操作。執行該腳本的結果如下:

(3, 'Jason', datetime.datetime(2020, 6, 8, 15, 30))
(2, 'tony', datetime.datetime(2020, 6, 8, 16, 0))

調用存儲函數

遊標對象的 callproc() 方法可以用於執行存儲函數。我們先創建一個返回用戶數量的函數 get_user_count:

CREATE OR REPLACE FUNCTION get_user_count()
returns int
AS $$
DECLARE
ln_count int;
BEGIN
  select count(*) into ln_count
  from users;
 
  return ln_count;
END; $$
LANGUAGE plpgsql;

接下來創建一個新的文件 postgresql_func:

# 導入 psycopg2 模塊和 Error 對象
import psycopg2
from psycopg2 import DatabaseError
from configparser import ConfigParser

def read_db_config(filename='dbconfig.ini', section='postgresql'):
    """ 讀取數據庫配置文件,返回一個字典對象
    """
    # 創建解析器,讀取配置文件
    parser = ConfigParser()
    parser.read(filename)

    # 獲取 postgresql 部分的配置
    db = {}
    if parser.has_section(section):
        items = parser.items(section)
        for item in items:
            db[item[0]] = item[1]
    else:
        raise Exception('文件 {1} 中未找到 {0} 配置信息!'.format(section, filename))

    return db

db_config = read_db_config()
connection = None

try:
    # 使用 with 語句管理事務
    with psycopg2.connect(**db_config) as connection:

        # 創建一個遊標
        with connection.cursor() as cur:

            # 調用存儲函數
            cur.callproc('get_user_count')

            row = cur.fetchone()[0]
            print('用戶總數:', row)
except (Exception, DatabaseError) as e:
    print("操作失敗:", e)
finally:
    # 釋放數據庫連接
    if connection is not None:
        connection.close()

callproc() 方法調用存儲函數也可以寫成以下等價的形式:

cur.execute('select * from get_user_count()')

執行以上腳本返回的結果如下:

用戶總數: 2

callproc() 方法不支持存儲過程,可以使用 execute() 方法調用 PostgreSQL 中的 CALL 命令執行存儲過程。更多關於[Psycopg 接口的使用和配置,可以參考 Psycopg 文檔。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章