PL/Python
PL/Python позволяет выполнять код Python внутри SQL-функций. В этой статье описывается, как установить расширение PL/Python и работать с ним.
Обзор PL/Python
PL/Python — процедурный язык, который позволяет реализовывать пользовательские функции на Python. Он предоставляет доступ к среде выполнения и стандартным модулям Python из SQL-функций.
Greengage DB поддерживает два варианта PL/Python:
-
plpythonu(plpython2u) — использует Python 2.7. Это расширение включено в Greengage DB по умолчанию.plpythonuв данной версии Greengage DB всегда относится к реализации на основе Python 2, независимо от того, какая версия Python считается в операционной системе версией "по умолчанию" (например, даже если /usr/bin/python указывает на Python 3). -
plpython3u— использует Python 3.9 или более позднюю версию. Чтобы использовать это расширение, перед сборкой Greengage DB из исходного кода задайте переменной окруженияPYTHONпуть к требуемому исполняемому файлу python3. Узнайте подробнее в разделе Сборка Greengage DB с поддержкой Python 3.
Ограничения PL/Python в Greengage DB
-
Триггеры PL/Python не поддерживаются.
-
Обновляемые курсоры (
UPDATE … WHERE CURRENT OFиDELETE … WHERE CURRENT OF) не поддерживаются. -
В рамках одной сессии Greengage DB все функции PL/Python должны использовать один и тот же вариант PL/Python (
plpythonuилиplpython3u). Чтобы вызвать функцию, созданную с другим вариантом, необходимо открыть новую сессию. -
PL/Python доступен только как недоверенный язык.
ПРИМЕЧАНИЕЧтобы выполнять пользовательские функции PL/Python в изолированной и контролируемой среде, рассмотрите возможность использования расширения PL/Container.
Включение поддержки PL/Python
Проверка окружения Python
Перед использованием PL/Python убедитесь, что все хосты кластера используют один и тот же интерпретатор Python.
Используйте утилиту gpssh для проверки пути к интерпретатору и его версии на всех хостах:
-
Проверьте, какой исполняемый файл Python используется:
$ gpssh -s -f hostfile_all_hosts which python3Файл hostfile_all_hosts содержит имена всех хостов кластера. Пример вывода:
[sdw1] /usr/bin/python3 [smdw] /usr/bin/python3 [ mdw] /usr/bin/python3 [sdw2] /usr/bin/python3
-
Затем убедитесь, что все хосты используют одинаковую версию Python:
$ gpssh -s -f hostfile_all_hosts python3 --versionПример вывода:
[sdw1] Python 3.10.12 [sdw2] Python 3.10.12 [ mdw] Python 3.10.12 [smdw] Python 3.10.12
-
Если вы планируете устанавливать дополнительные пакеты Python, установите pip на всех хостах.
-
Для установки пакета на всех хостах запустите интерактивную сессию
gpssh:$ gpssh -s -f hostfile_all_hostsКогда будет показано приглашение
=>, введите команду для установки и нажмитеEnter. Например, следующая команда устанавливает NumPy на всех хостах:pip install numpy==2.2.6Пример вывода:
... [sdw1] Successfully installed numpy-2.2.6 ... [ mdw] Successfully installed numpy-2.2.6 ... [sdw2] Successfully installed numpy-2.2.6 ... [smdw] Successfully installed numpy-2.2.6 ...
Чтобы завершить интерактивную сессию, введите
exitи нажмитеEnter:exit
Сборка Greengage DB с поддержкой Python 3
По умолчанию Greengage DB собирается с поддержкой Python 2 (plpythonu).
Чтобы включить вариант с Python 3 (plpython3u), необходимо собрать Greengage DB из исходного кода с указанным интерпретатором Python 3.
Полная процедура сборки описана в разделе Сборка и установка Greengage DB.
Перед сборкой PL/Python с поддержкой Python 3 установите пакет разработки Python 3, обеспечивающий доступ к нужным заголовочным файлам и библиотекам. Например, в Ubuntu с Python 3.10 выполните установку пакета следующим образом:
$ sudo apt install python3.10-dev
Перед компиляцией Greengage DB из исходного кода задайте переменной окружения PYTHON путь к требуемому исполняемому файлу python3:
$ PYTHON=/usr/bin/python3 ./configure \
--with-perl \
--with-python \
--with-pythonsrc-ext \
--with-libxml \
--with-uuid=e2fs \
--with-openssl \
--with-gssapi \
--with-ldap \
--enable-ic-proxy \
--enable-orafce \
--enable-mapreduce \
--prefix=/usr/local/gpdb
Проверка доступных расширений Python
Прежде чем включить PL/Python, проверьте, какие варианты этого языкового расширения доступны в вашем окружении. Подключитесь к базе данных с помощью psql и выполните следующий запрос:
SELECT *
FROM pg_available_extensions
WHERE name LIKE 'plpython%';
Пример вывода:
name | default_version | installed_version | comment ------------+-----------------+-------------------+------------------------------------------- plpythonu | 1.0 | | PL/PythonU untrusted procedural language plpython2u | 1.0 | | PL/Python2U untrusted procedural language plpython3u | 1.0 | | PL/Python3U untrusted procedural language (3 rows)
Столбец installed_version показывает, установлено ли расширение в текущей базе данных.
В приведенном выше выводе все значения пусты, что означает, что ни один из вариантов PL/Python еще не установлен.
Установка расширения PL/Python
Для создания пользовательских функций PL/Python необходимо установить расширение plpythonu или plpython3u в каждой базе данных, которая будет его использовать.
Например, для установки plpython3u:
CREATE EXTENSION plpython3u;
Ожидаемый вывод:
CREATE EXTENSION
Если использование PL/Python в базе данных больше не требуется, вы можете удалить расширение с помощью команды DROP EXTENSION.
Укажите опцию CASCADE, чтобы автоматически удалить все зависимые объекты, включая функции, созданные с использованием PL/Python:
DROP EXTENSION plpython3u CASCADE;
Создание функции PL/Python
После включения PL/Python в вашей базе данных вы можете использовать язык plpython3u для создания функций.
Пример ниже создает функцию, которая возвращает приветственное сообщение и основную версию используемого Python-интерпретатора:
CREATE FUNCTION greet_from_python(name TEXT)
RETURNS TEXT
AS
$$
import sys
return f"Hello, {name}, from Python {sys.version_info[0]}!"
$$ LANGUAGE plpython3u;
В PL/Python тело функции представляет собой стандартный скрипт Python.
Аргументы, указанные в сигнатуре SQL-функции, доступны в скрипте как стандартные переменные Python.
Результат функции возвращается с помощью return для одиночного значения или с помощью yield для функций, возвращающих множества.
Вызовите созданную функцию:
SELECT greet_from_python('Alice');
Результат должен выглядеть так:
greet_from_python ------------------------------ Hello, Alice, from Python 3! (1 row)
Преобразование типов данных
Сопоставление типов
Аргументы функции
При вызове функции PL/Python ее аргументы преобразуются из типа данных Greengage DB в соответствующий тип Python. В таблице ниже приведено сопоставление типов аргументов функции.
| Тип Greengage DB | Тип Python |
|---|---|
BOOLEAN |
bool |
SMALLINT, INT, BIGINT, oid |
int |
REAL, DOUBLE |
float |
NUMERIC |
decimal |
BYTEA |
bytes |
NULL |
None (см. NULL-значения) |
ARRAY |
list (см. Массивы) |
Другие примитивные типы |
string |
Составные типы |
Аргументы составного типа передаются в функцию как сопоставления Python (см. Передача составных значений) |
Возвращаемые значения
Результат функции PL/Python приводится к объявленному типу возвращаемого значения Greengage DB по правилам, описанным в таблице ниже.
| Тип Greengage DB | Правила преобразования |
|---|---|
BOOLEAN |
Возвращаемое значение оценивается в соответствии с правилами Python.
То есть для |
BYTEA |
Возвращаемое значение будет преобразовано в тип Python |
NULL |
None (см. NULL-значения) |
ARRAY |
list (см. Массивы) |
Другие примитивные типы |
Возвращаемое значение преобразуется в строку с помощью встроенной функции Python |
Составные типы |
Результат составного типа может возвращаться как последовательность, сопоставление или объект.
Также можно использовать функции с параметрами |
Логические типы
Функции PL/Python, возвращающие логические значения, используют правила истинности Python.
Например, 0 и пустая строка оцениваются как False, тогда как большинство других значений, включая непустые строки (например, 'f'), оцениваются как True.
Создайте функцию, проверяющую, является ли число положительным:
CREATE FUNCTION is_positive(value INT)
RETURNS BOOLEAN
AS
$$
if value > 0:
return True
else:
return False
$$ LANGUAGE plpython3u;
Вызовите функцию:
SELECT is_positive(-20);
Ожидаемый результат:
is_positive ------------- f (1 row)
NULL-значения
Когда SQL-значение NULL передается в функцию, в Python оно будет интерпретироваться как None.
Вы можете добавить ключевое слово STRICT в определение функции, чтобы СУБД автоматически возвращала NULL без вызова функции, если какой-либо из аргументов равен NULL.
В качестве альтернативы можно обрабатывать значения NULL прямо в теле функции:
CREATE FUNCTION get_max_value(value1 INT, value2 INT)
RETURNS INT
AS
$$
if value1 is None or value2 is None:
return None
return value1 if value1 > value2 else value2
$$ LANGUAGE plpython3u;
Вызовите функцию:
SELECT get_max_value(NULL, 15);
Функция возвращает NULL:
get_max_value --------------- (1 row)
Массивы
Значения SQL-массивов передаются в PL/Python как списки Python. Чтобы функция PL/Python возвращала массив SQL, верните список Python.
Например, следующая команда создает функцию, генерирующую массив целых чисел:
CREATE FUNCTION get_int_array(max_value INT)
RETURNS INT[]
AS
$$
if max_value is None:
return None
return list(range(1, max_value + 1))
$$ LANGUAGE plpython3u;
Вызовите функцию:
SELECT get_int_array(5);
Результат должен выглядеть следующим образом:
get_int_array
---------------
{1,2,3,4,5}
(1 row)
Многомерные массивы
PL/Python поддерживает многомерные SQL-массивы как вложенные списки Python. Каждый внутренний список соответствует одному измерению SQL-массива. При возврате многомерного массива все внутренние списки на каждом уровне должны быть одинакового размера для формирования корректного SQL-массива.
Следующая функция демонстрирует, как двумерный массив передается в PL/Python и возвращается без изменений:
CREATE FUNCTION get_two_dim_array(arr INT[])
RETURNS INT[]
AS
$$
plpy.info(arr, type(arr))
return arr
$$ LANGUAGE plpython3u;
Вызовите функцию:
SELECT get_two_dim_array(ARRAY [[1,2,3],[10,20,30]]);
Результат должен выглядеть так:
get_two_dim_array
----------------------
{{1,2,3},{10,20,30}}
(1 row)
Особенности работы со строками
В Python строки — это последовательности, что может привести к непредвиденному поведению при их возврате в виде массивов. Создайте следующую функцию:
CREATE FUNCTION get_str_array(input_text TEXT)
RETURNS VARCHAR[]
AS
$$
return input_text
$$ LANGUAGE plpython3u;
Вызовите функцию:
SELECT get_str_array('Hello!');
Результат выглядит следующим образом:
get_str_array
---------------
{H,e,l,l,o,!}
(1 row)
Вы можете исправить это поведение, явно обернув строку в список, чтобы PL/Python возвращал массив с одним элементом:
CREATE FUNCTION get_str_array_fixed(input_text TEXT)
RETURNS VARCHAR[]
AS
$$
return [input_text]
$$ LANGUAGE plpython3u;
Составные типы
Передача составных значений
CREATE TABLE sales
(
id INT,
date DATE,
amount DECIMAL(10, 2),
category TEXT
)
WITH (appendoptimized = true)
DISTRIBUTED BY (id);
INSERT INTO sales(id, date, amount, category)
VALUES (1, '2025-08-01', 100.00, 'Books'),
(2, '2025-08-02', 200.00, 'Electronics'),
(3, '2025-08-03', 150.00, 'Books'),
(4, '2025-08-03', 300.00, 'Furniture');
Аргументы составного типа передаются в функцию как сопоставления Python.
Имена атрибутов составного типа являются именами элементов сопоставления.
Например, следующая функция классифицирует продажи на основе поля amount:
CREATE FUNCTION categorize_sale(s sales)
RETURNS text
AS
$$
amount = s["amount"]
if amount < 150:
return 'Low'
elif amount < 250:
return 'Medium'
else:
return 'High'
$$ LANGUAGE plpython3u;
Вызовите функцию:
SELECT id, amount, categorize_sale(sales) AS category_level
FROM sales;
Результат должен выглядеть следующим образом:
id | amount | category_level ----+--------+---------------- 3 | 150.00 | Medium 4 | 300.00 | High 1 | 100.00 | Low 2 | 200.00 | Medium (4 rows)
Возврат составных значений
Функции PL/Python могут возвращать строки или составные типы несколькими способами.
В приведенных ниже примерах используется составной тип http_status, определенный следующим образом:
CREATE TYPE http_status AS
(
code INT,
message TEXT
);
Существует несколько способов вернуть составной результат из функции PL/Python, как описано ниже.
Последовательность
Последовательность, возвращаемая функцией PL/Python, должна содержать столько же элементов, сколько содержится полей в составном типе.
Элемент с индексом 0 присваивается первому полю составного типа, элемент с индексом 1 — второму полю и так далее.
Например:
CREATE FUNCTION create_http_status(code INT, message TEXT)
RETURNS http_status
AS
$$
return code, message
$$ LANGUAGE plpython3u;
CREATE FUNCTION create_http_status(code INT, message TEXT)
RETURNS http_status
AS
$$
return [code, message]
$$ LANGUAGE plpython3u;
Вызовите функцию:
SELECT * FROM create_http_status(200, 'OK');
Результат должен выглядеть следующим образом:
code | message ------+--------- 200 | OK (1 row)
Сопоставление
Значение для каждого столбца результирующего типа извлекается из сопоставления по имени столбца в качестве ключа. Пример:
CREATE FUNCTION create_http_status(code INT, message TEXT)
RETURNS http_status
AS
$$
return {"code": code, "message": message}
$$ LANGUAGE plpython3u;
Вызовите функцию:
SELECT * FROM create_http_status(301, 'Moved Permanently');
Ожидаемый результат:
code | message ------+------------------- 301 | Moved Permanently (1 row)
Объект
PL/Python также позволяет возвращать из функции объекты, представляющие составной тип. Атрибуты объекта сопоставляются с полями составного SQL-типа аналогично тому, как работает сопоставление в Python. Например:
CREATE FUNCTION create_http_status(code INT, message TEXT)
RETURNS http_status
AS
$$
class HttpStatus:
def __init__(self, c, m):
self.code = c
self.message = m
return HttpStatus(code, message)
$$ LANGUAGE plpython3u;
CREATE FUNCTION create_http_status(code INT, message TEXT)
RETURNS http_status
AS
$$
class HttpStatus: pass
HttpStatus.code = code
HttpStatus.message = message
return HttpStatus
$$ LANGUAGE plpython3u;
Вызовите функцию:
SELECT * FROM create_http_status(404, 'Not Found');
Ожидаемый результат:
code | message ------+----------- 404 | Not Found (1 row)
OUT-параметры
Вы можете использовать функции с выходными параметрами:
CREATE FUNCTION create_http_status(
code_in INT,
message_in TEXT,
OUT code INT,
OUT message TEXT
)
AS
$$
return code_in, message_in
$$ LANGUAGE plpython3u;
Вызовите функцию:
SELECT * FROM create_http_status(501, 'Not Implemented');
Ожидаемый результат:
code | message ------+----------------- 501 | Not Implemented (1 row)
Функции, возвращающие множества
Функции PL/Python могут возвращать наборы значений как скалярных, так и составных типов. Возвращаемый функцией объект автоматически преобразуется в итератор, что позволяет функции возвращать несколько строк.
В приведенных ниже примерах используется следующий составной тип:
CREATE TYPE user_event AS
(
event_type TEXT,
user_name TEXT
);
Последовательность
Создайте функцию:
CREATE FUNCTION generate_user_events_sequence(event_type TEXT)
RETURNS SETOF user_event
AS
$$
return (
[event_type, "alice"],
[event_type, "bob"],
[event_type, "charlie"]
)
$$ LANGUAGE plpython3u;
Вызовите функцию:
SELECT * FROM generate_user_events_sequence('login');
Ожидаемый результат:
event_type | user_name ------------+----------- login | alice login | bob login | charlie (3 rows)
Итератор
Создайте функцию:
CREATE FUNCTION get_user_events_iterator(event_type TEXT)
RETURNS SETOF user_event
AS
$$
class EventProducer:
def __init__(self, event_type, user):
self.event_type = event_type
self.users = user
self.ndx = -1
def __iter__(self):
return self
def __next__(self):
self.ndx += 1
if self.ndx >= len(self.users):
raise StopIteration
return self.event_type, self.users[self.ndx]
return EventProducer(event_type, ["alice", "bob", "charlie"])
$$ LANGUAGE plpython3u;
Вызовите функцию:
SELECT * FROM get_user_events_iterator('logout');
Ожидаемый результат:
event_type | user_name ------------+----------- logout | alice logout | bob logout | charlie (3 rows)
Генератор
Создайте функцию:
CREATE FUNCTION get_user_events_generator(event_type TEXT)
RETURNS SETOF user_event
AS
$$
for user_name in ["alice", "bob", "charlie"]:
yield event_type, user_name
$$ LANGUAGE plpython3u;
Вызовите функцию:
SELECT * FROM get_user_events_generator('password_change');
Ожидаемый результат:
event_type | user_name -----------------+----------- password_change | alice password_change | bob password_change | charlie (3 rows)
Анонимные блоки кода
PL/Python поддерживает анонимные блоки кода, вызываемые с помощью оператора DO.
Анонимный блок кода не принимает аргументов, а любое возвращаемое им значение игнорируется.
В остальном он работает как код функции.
Пример:
DO $$
for user in ['alice', 'bob', 'charlie']:
plpy.notice(f'User {user} is active')
$$ LANGUAGE plpython3u;
Ожидаемый результат:
NOTICE: User alice is active CONTEXT: PL/Python anonymous code block NOTICE: User bob is active CONTEXT: PL/Python anonymous code block NOTICE: User charlie is active CONTEXT: PL/Python anonymous code block
Доступ к базе данных
PL/Python автоматически импортирует встроенный модуль Python plpy.
Этот модуль предоставляет функции для выполнения SQL-команд, подготовки планов выполнения и взаимодействия с базой данных из функции PL/Python.
Часто используемые функции включают:
-
plpy.execute()— выполняет SQL-команду и возвращает результат. -
plpy.prepare()— подготавливает план выполнения запроса. -
plpy.cursor()— открывает курсор для поэтапного получения результата.
Кроме того, PL/Python предоставляет полезные функции, не описанные в этой статье, такие как plpy.debug(), plpy.log() и plpy.quote_literal().
Дополнительную информацию см. в документации PostgreSQL.
CREATE TABLE sales
(
id INT,
product_name TEXT,
category TEXT,
price NUMERIC(8, 2),
sale_date DATE
)
WITH (appendoptimized = true)
DISTRIBUTED BY (id);
INSERT INTO sales(id, product_name, category, price, sale_date)
VALUES (1, 'Laptop', 'Electronics', 1200.00, '2025-01-03'),
(2, 'Headphones', 'Electronics', 150.00, '2025-01-05'),
(3, 'Coffee Maker', 'Home', 85.00, '2025-01-07'),
(4, 'T-Shirt', 'Clothing', 25.00, '2025-01-10'),
(5, 'Desk Chair', 'Home', 200.00, '2025-01-15'),
(6, 'Smartphone', 'Electronics', 800.00, '2025-01-22'),
(7, 'Blender', 'Home', 55.00, '2025-01-28'),
(8, 'Jacket', 'Clothing', 60.00, '2025-02-03'),
(9, 'Laptop', 'Electronics', 1200.00, '2025-02-07'),
(10, 'T-Shirt', 'Clothing', 25.00, '2025-02-10');
Выполнение запроса
Команда plpy.execute() выполняет указанный запрос с необязательным параметром limit и возвращает результирующий объект.
В следующем примере возвращается заданное количество строк из таблицы sales:
CREATE FUNCTION fetch_sales(limit_count INT)
RETURNS SETOF sales
AS
$$
query = "SELECT id, product_name, category, price, sale_date FROM sales"
rows = plpy.execute(query, limit_count)
return rows
$$ LANGUAGE plpython3u;
Вызовите функцию:
SELECT * FROM fetch_sales(3);
Результат должен выглядеть следующим образом:
id | product_name | category | price | sale_date ----+--------------+-------------+---------+------------ 1 | Laptop | Electronics | 1200.00 | 2025-01-03 2 | Headphones | Electronics | 150.00 | 2025-01-05 9 | Laptop | Electronics | 1200.00 | 2025-02-07 (3 rows)
Объект, возвращаемый plpy.execute(), ведет себя как список строк, где каждая строка работает аналогично словарю Python.
Строки можно получать по индексу, а значения столбцов — по их именам, обращаясь к ним по имени, как к ключам словаря.
Следующая функция демонстрирует, как перебирать строки результата и форматировать их данные для вывода в текстовом виде:
CREATE FUNCTION fetch_sales(limit_count INT)
RETURNS TEXT
AS
$$
rows = plpy.execute(
"SELECT id, product_name, price, sale_date FROM sales ORDER BY id",
limit_count
)
lines = [
f"{row['id']}: {row['product_name']}, ${row['price']} on {row['sale_date']}"
for row in rows
]
return "\n".join(lines)
$$ LANGUAGE plpython3u;
Вызовите функцию:
SELECT fetch_sales(4);
Ожидаемый результат:
fetch_sales --------------------------------------- 1: Laptop, $1200.00 on 2025-01-03 + 2: Headphones, $150.00 on 2025-01-05 + 3: Coffee Maker, $85.00 on 2025-01-07+ 4: T-Shirt, $25.00 on 2025-01-10 (1 row)
Подготовка и выполнение запроса
Когда функция выполняет один и тот же SQL-запрос несколько раз, эффективнее один раз подготовить запрос, а затем выполнять его с разными параметрами.
Функция plpy.prepare() создает подготовленный план выполнения.
Возвращенный объект плана можно многократно использовать при вызовах внутри одного выполнения функции.
В следующем примере подготавливается запрос, который фильтрует строки по категории, а затем выполняет его с переданными аргументами:
CREATE FUNCTION fetch_sales_by_category(category TEXT, limit_count INT)
RETURNS TEXT
AS
$$
plan = plpy.prepare(
"SELECT id, product_name, price, sale_date "
"FROM sales "
"WHERE category = $1 "
"ORDER BY id",
["text"]
)
rows = plpy.execute(plan, [category], limit_count)
lines = [
f"{row['id']}: {row['product_name']} - ${row['price']} on {row['sale_date']}"
for row in rows
]
return "\n".join(lines)
$$ LANGUAGE plpython3u;
Вызовите функцию:
SELECT fetch_sales_by_category('Electronics', 5);
Ожидаемый результат:
fetch_sales_by_category --------------------------------------- 1: Laptop - $1200.00 on 2025-01-03 + 2: Headphones - $150.00 on 2025-01-05+ 6: Smartphone - $800.00 on 2025-01-22+ 9: Laptop - $1200.00 on 2025-02-07 (1 row)
PL/Python предоставляет специальный словарь SD, который сохраняется на протяжении всего времени жизни функции.
Это позволяет кешировать объекты, такие как подготовленные планы, чтобы их не нужно было создавать заново при каждом вызове функции.
Это особенно полезно для часто вызываемых функций, где отказ от повторных вызовов plpy.prepare() может улучшить производительность.
В следующем примере подготовленный план при первом выполнении функции сохраняется в SD и повторно используется при последующих вызовах:
CREATE FUNCTION fetch_sales_by_category_cached(cat TEXT, limit_count INT)
RETURNS TEXT
AS
$$
if "sales_plan" in SD:
plan = SD["sales_plan"]
else:
plan = plpy.prepare(
"SELECT id, product_name, price, sale_date "
"FROM sales "
"WHERE category = $1 "
"ORDER BY id",
["text"]
)
SD["sales_plan"] = plan
rows = plpy.execute(plan, [cat], limit_count)
lines = [
f"{row['id']}: {row['product_name']} - ${row['price']} on {row['sale_date']}"
for row in rows
]
return "\n".join(lines)
$$ LANGUAGE plpython3u;
Вызовите функцию:
SELECT fetch_sales_by_category_cached('Electronics', 5);
Ожидаемый результат:
fetch_sales_by_category_cached --------------------------------------- 1: Laptop - $1200.00 on 2025-01-03 + 2: Headphones - $150.00 on 2025-01-05+ 6: Smartphone - $800.00 on 2025-01-22+ 9: Laptop - $1200.00 on 2025-02-07 (1 row)
Получение результатов частями
Для больших результирующих наборов часто неэффективно или невозможно загружать все строки в память сразу.
Функция plpy.cursor() позволяет выполнить запрос и получить данные частями, извлекая лишь ограниченное число строк за один раз.
Курсор PL/Python представляет собой серверный курсор, управляемый серверной частью базы данных.
Каждый вызов cursor.fetch(n) извлекает до n дополнительных строк и продвигает позицию курсора вперед при каждом запросе.
Следующая функция демонстрирует, как читать результаты запроса порциями и выводить базовую информацию о каждой порции в лог:
CREATE FUNCTION fetch_sales_in_batches(batch_size INT)
RETURNS VOID
AS
$$
cursor = plpy.cursor(
"SELECT id, product_name, price, sale_date FROM sales ORDER BY id"
)
batch = 1
while True:
rows = cursor.fetch(batch_size)
if not rows:
break
plpy.info(f"Batch #{batch}, rows returned: {len(rows)}")
batch += 1
$$ LANGUAGE plpython3u;
Вызовите функцию:
SELECT fetch_sales_in_batches(3);
Ожидаемый результат:
INFO: Batch #1, rows returned: 3 CONTEXT: PL/Python function "fetch_sales_in_batches" INFO: Batch #2, rows returned: 3 CONTEXT: PL/Python function "fetch_sales_in_batches" INFO: Batch #3, rows returned: 3 CONTEXT: PL/Python function "fetch_sales_in_batches" INFO: Batch #4, rows returned: 1 CONTEXT: PL/Python function "fetch_sales_in_batches"
Явные подтранзакции
PL/Python поддерживает явные подтранзакции, позволяющие выполнять блоки кода, которые можно откатывать независимо от основной транзакции. Это удобно для обработки ошибок или исключений внутри функции без прерывания выполнения транзакции. Подробности и примеры см. в разделе Explicit Subtransactions.
Использование модулей Python
PL/Python может импортировать модули, установленные в системном окружении Python, используемом базой данных. Инструкцию по установке модулей смотрите в разделе Проверка окружения Python.
В следующем примере показано, как использовать NumPy для вычисления формы массива, переданного функции в виде текстового литерала:
CREATE FUNCTION get_array_shape(arr TEXT)
RETURNS TEXT
AS
$$
import numpy
import ast
input_array = numpy.array(ast.literal_eval(arr))
return f"Array shape: {input_array.shape}"
$$ LANGUAGE plpython3u;
Вызовите функцию:
SELECT get_array_shape('[[1,2,3],[4,5,6]]');
Ожидаемый результат:
get_array_shape --------------------- Array shape: (2, 3) (1 row)