支援的版本:目前 (17) / 16 / 15 / 14 / 13
開發版本:devel
不支援的版本:12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0 / 8.4 / 8.3 / 8.2 / 8.1 / 8.0 / 7.4 / 7.3

44.6. 資料庫存取 #

PL/Python 語言模組會自動匯入一個名為 plpy 的 Python 模組。 此模組中的函數和常數可在 Python 程式碼中以 plpy.foo 的形式使用。

44.6.1. 資料庫存取函數 #

plpy 模組提供多個函數來執行資料庫指令

plpy.execute(query [, limit])

使用查詢字串和可選的列數限制引數呼叫 plpy.execute 會導致執行該查詢,並將結果傳回在結果物件中。

如果指定了 limit 且大於零,則 plpy.execute 最多會擷取 limit 列,這與查詢中包含 LIMIT 子句非常相似。 省略 limit 或將其指定為零將不會導致列數限制。

結果物件會模擬列表或字典物件。 可以透過列號和欄位名稱存取結果物件。 例如

rv = plpy.execute("SELECT * FROM my_table", 5)

my_table 傳回最多 5 列。 如果 my_table 有一個欄位 my_column,則可以這樣存取它

foo = rv[i]["my_column"]

傳回的列數可以使用內建的 len 函數取得。

結果物件具有下列其他方法

nrows()

傳回指令處理的列數。 請注意,這不一定與傳回的列數相同。 例如,UPDATE 指令將設定此值,但不傳回任何列(除非使用 RETURNING)。

status()

SPI_execute() 傳回值。

colnames()
coltypes()
coltypmods()

分別傳回欄位名稱列表、欄位類型 OID 列表,以及欄位特定類型修改器的列表。

從未產生結果集的指令(例如,沒有 RETURNINGUPDATEDROP TABLE)的結果物件呼叫這些方法會引發例外。 但是,在包含零列的結果集上使用這些方法是可以的。

__str__()

定義了標準的 __str__ 方法,因此例如可以使用 plpy.debug(rv) 來偵錯查詢執行結果。

結果物件可以修改。

請注意,呼叫 plpy.execute 會導致將整個結果集讀入記憶體中。 僅在確定結果集相對較小時才使用該函數。 如果您不想在擷取大型結果時冒記憶體使用過多的風險,請使用 plpy.cursor 而不是 plpy.execute

plpy.prepare(query [, argtypes])
plpy.execute(plan [, arguments [, limit]])

plpy.prepare 為查詢準備執行計畫。 呼叫時會使用查詢字串和參數類型列表(如果查詢中有參數參照)。 例如

plan = plpy.prepare("SELECT last_name FROM my_users WHERE first_name = $1", ["text"])

text 是您將為 $1 傳遞的變數類型。 如果您不想將任何參數傳遞給查詢,則第二個引數是可選的。

準備好陳述式後,您可以使用函數 plpy.execute 的變體來執行它

rv = plpy.execute(plan, ["name"], 5)

將計畫作為第一個引數傳遞(而不是查詢字串),並將要替換到查詢中的值列表作為第二個引數傳遞。 如果查詢不需要任何參數,則第二個引數是可選的。 第三個引數是與之前一樣的可選列數限制。

或者,您可以在計畫物件上呼叫 execute 方法

rv = plan.execute(["name"], 5)

查詢參數和結果列欄位會在 PostgreSQL 和 Python 資料類型之間轉換,如 第 44.2 節中所述。

當您使用 PL/Python 模組準備計畫時,它會自動儲存。 閱讀 SPI 文件(第 45 章)以了解這代表什麼。 為了在函數呼叫之間有效地使用它,需要使用其中一個持久儲存字典 SDGD(請參閱 第 44.3 節)。 例如

CREATE FUNCTION usesavedplan() RETURNS trigger AS $$
    if "plan" in SD:
        plan = SD["plan"]
    else:
        plan = plpy.prepare("SELECT 1")
        SD["plan"] = plan
    # rest of function
$$ LANGUAGE plpython3u;
plpy.cursor(query)
plpy.cursor(plan [, arguments])

plpy.cursor 函數接受與 plpy.execute 相同的引數 (除了列數限制),並傳回一個游標物件,允許您以較小的區塊處理大型結果集。如同 plpy.execute,可以使用查詢字串或預備陳述式物件以及引數列表,或者可以將 cursor 函數作為預備陳述式物件的方法來呼叫。

游標物件提供一個 fetch 方法,它接受一個整數參數並傳回一個結果物件。每次您呼叫 fetch 時,傳回的物件將包含下一批資料列,永遠不大於參數值。一旦所有資料列都被取出,fetch 開始傳回一個空的結果物件。游標物件也提供一個 迭代器介面,一次產生一行,直到所有資料列都被取出。以這種方式取出的資料不會作為結果物件傳回,而是作為字典傳回,每個字典對應到一個單一的結果列。

以下是處理大型表格資料的兩種方式的範例:

CREATE FUNCTION count_odd_iterator() RETURNS integer AS $$
odd = 0
for row in plpy.cursor("select num from largetable"):
    if row['num'] % 2:
         odd += 1
return odd
$$ LANGUAGE plpython3u;

CREATE FUNCTION count_odd_fetch(batch_size integer) RETURNS integer AS $$
odd = 0
cursor = plpy.cursor("select num from largetable")
while True:
    rows = cursor.fetch(batch_size)
    if not rows:
        break
    for row in rows:
        if row['num'] % 2:
            odd += 1
return odd
$$ LANGUAGE plpython3u;

CREATE FUNCTION count_odd_prepared() RETURNS integer AS $$
odd = 0
plan = plpy.prepare("select num from largetable where num % $1 <> 0", ["integer"])
rows = list(plpy.cursor(plan, [2]))  # or: = list(plan.cursor([2]))

return len(rows)
$$ LANGUAGE plpython3u;

游標會自動釋放。但是,如果您想明確釋放游標所持有的所有資源,請使用 close 方法。關閉後,游標就無法再提取資料。

提示

請不要將 plpy.cursor 建立的物件與 Python 資料庫 API 規範所定義的 DB-API 游標混淆。除了名稱之外,它們沒有任何共同之處。

44.6.2. 捕捉錯誤 #

存取資料庫的函數可能會遇到錯誤,這會導致它們中止並引發例外。plpy.executeplpy.prepare 都可能引發 plpy.SPIError 的子類別的實例,預設情況下這將終止函數。此錯誤可以像任何其他 Python 例外一樣處理,使用 try/except 結構。例如:

CREATE FUNCTION try_adding_joe() RETURNS text AS $$
    try:
        plpy.execute("INSERT INTO users(username) VALUES ('joe')")
    except plpy.SPIError:
        return "something went wrong"
    else:
        return "Joe added"
$$ LANGUAGE plpython3u;

引發的例外類別實際對應於導致錯誤的特定條件。請參閱表 A.1,以取得可能條件的列表。模組 plpy.spiexceptions 為每個 PostgreSQL 條件定義一個例外類別,從條件名稱派生其名稱。例如,division_by_zero 變成 DivisionByZerounique_violation 變成 UniqueViolationfdw_error 變成 FdwError,依此類推。每個例外類別都繼承自 SPIError。這種分離使得更容易處理特定錯誤,例如:

CREATE FUNCTION insert_fraction(numerator int, denominator int) RETURNS text AS $$
from plpy import spiexceptions
try:
    plan = plpy.prepare("INSERT INTO fractions (frac) VALUES ($1 / $2)", ["int", "int"])
    plpy.execute(plan, [numerator, denominator])
except spiexceptions.DivisionByZero:
    return "denominator cannot equal zero"
except spiexceptions.UniqueViolation:
    return "already have that fraction"
except plpy.SPIError as e:
    return "other error, SQLSTATE %s" % e.sqlstate
else:
    return "fraction inserted"
$$ LANGUAGE plpython3u;

請注意,由於 plpy.spiexceptions 模組中的所有例外都繼承自 SPIError,因此處理它的 except 子句將捕獲任何資料庫存取錯誤。

作為處理不同錯誤條件的替代方法,您可以捕獲 SPIError 例外,並通過查看例外物件的 sqlstate 屬性來確定 except 區塊內的特定錯誤條件。此屬性是一個字串值,包含 SQLSTATE 錯誤碼。此方法提供大致相同的功能:

提交更正

如果您在文件中看到任何不正確、與您對特定功能的體驗不符或需要進一步說明的地方,請使用此表單報告文件問題。