Postgres
- Как использовать процедуры и функции на Pl/SQL, какие есть еще языки для работы с Postgres?
- Как активировать PL/Python, примеры функции и процедуры
- Использование Postgres в C#
- Как вызываются процедуры и функции на PL/Python из C#?
- Как передавать и парсить JSON/JSONB из C# в PL/Python функцию через Npgsql?
- Какой тип даных поля в таблице Postgres лучше использовать для хранения пакетов json, но там могут быть и другие типы, например XML или изображения?
- Запрос данных из пакетов JSON в поле таблицы
- Работа с Postgres SQL
Как использовать процедуры и функции на Pl/SQL, какие есть еще языки для работы с Postgres?
Функции (Functions)
SELECT, WHERE, JOIN).CREATE OR REPLACE FUNCTION calculate_bonus(emp_id INT, bonus_percentage NUMERIC)
RETURNS NUMERIC AS $$
DECLARE
current_salary NUMERIC;
calculated_bonus NUMERIC;
BEGIN
-- Получаем текущую зарплату сотрудника
SELECT salary INTO current_salary
FROM employees
WHERE id = emp_id;
-- Если сотрудник не найден, возвращаем 0
IF current_salary IS NULL THEN
RETURN 0;
END IF;
calculated_bonus := current_salary * (bonus_percentage / 100);
RETURN calculated_bonus;
END;
$$ LANGUAGE plpgsql;
SELECT calculate_bonus(5, 10.5);
Процедуры (Procedures)
RETURN (но могут возвращать данные через параметры INOUT или OUT), однако они поддерживают транзакции (COMMIT, ROLLBACK), в то время как функции — нет.CREATE OR REPLACE PROCEDURE transfer_funds(
sender_account INT,
receiver_account INT,
amount NUMERIC
)
LANGUAGE plpgsql
AS $$
BEGIN
-- Снимаем средства с одного счета
UPDATE accounts
SET balance = balance - amount
WHERE id = sender_account;
-- Зачисляем средства на другой счет
UPDATE accounts
SET balance = balance + amount
WHERE id = receiver_account;
-- Фиксируем транзакцию
COMMIT;
END;
$$;
CALL):CALL transfer_funds(1, 2, 500.00);
Основные управляющие конструкции PL/pgSQL
-
Условия (IF / THEN / ELSE):
SQLIF v_count > 10 THEN -- код ELSIF v_count > 0 THEN -- код ELSE -- код END IF; -
Циклы (LOOP, WHILE, FOR):
SQL-- Цикл с условием WHILE v_counter < 10 LOOP v_counter := v_counter + 1; END LOOP; -- Обход результатов запроса FOR r_row IN SELECT * FROM employees LOOP RAISE NOTICE 'Employee: %', r_row.name; END LOOP; -
Обработка ошибок (EXCEPTION):
SQLBEGIN -- рискованный код EXCEPTION WHEN division_by_zero THEN RAISE NOTICE 'Деление на ноль!'; WHEN OTHERS THEN RAISE NOTICE 'Произошла непредвиденная ошибка: %', SQLERRM; END;
SELECT — пишите функцию. Если вам нужно выполнить набор команд модификации данных с управлением транзакциями — используйте процедуру.Чем различаются языки PL/SQL у Oracle и Postgres?
DECLARE, BEGIN, EXCEPTION), между ними есть глубокие архитектурные и синтаксические различия.Основные различия
| Характеристика | Oracle (PL/SQL) | PostgreSQL (PL/pgSQL) |
| Интеграция с СУБД | Язык является родным и доминирующим для СУБД. На нем написаны даже многие внутренние системные пакеты. | Язык — один из множества поддерживаемых (plpython, plperl, pltcl), хотя и самый популярный для написания логики в БД. |
| Пакеты (Packages) | Есть. Позволяют группировать типы, функции и процедуры, а также имеют состояние сессии (общие переменные внутри пакета). | Нет в чистом виде (хотя можно эмулировать через схемы). Переменные уровня сессии требуют сторонних расширений или таблиц. |
| Типы данных и коллекции | Огромный выбор встроенных сложных типов: TABLE (ассоциативные массивы), VARRAY, NESTED TABLE, продвинутая работа с записями (ROWTYPE). |
Коллекции ограничены базовыми массивами и таблицами типов (строки таблиц, RECORD), работа с ними менее гибкая. |
| Обработка SQL (курсоры) | Поддерживает неявные курсоры с мощными атрибутами (%FOUND, %NOTFOUND, %ROWCOUNT), а также продвинутые конструкции BULK COLLECT и FORALL для массовой обработки (оптимизация контекст-переключений). |
Курсоры есть (FOR циклы по выборкам), но массовые операции (BULK) реализуются иначе, часто через конструкции вроде ANY или временные таблицы. |
| Управление транзакциями | Поддерживает автономные транзакции (PRAGMA AUTONOMOUS_TRANSACTION), позволяя фиксировать изменения в логах независимо от основной транзакции. |
Обычные функции не могут управлять транзакциями. Процедуры могут выполнять COMMIT или ROLLBACK, но автономных транзакций «из коробки» до сих пор нет (требуются костыли через внешние вызовы или dblink). |
| Обработка ошибок | Большой встроенный набор предопределенных исключений (NO_DATA_FOUND, TOO_MANY_ROWS и др.). |
Исключения обрабатываются через блок EXCEPTION, но кодов ошибок и имен меньше, часто приходится проверять коды ошибок через SQLSTATE. |
| Объектно-ориентированные возможности | Поддерживает ООП-концепции: типы объектов с методами, наследование объектов в базе данных. | Отсутствуют ООП-возможности на уровне языка процедур. |
Примеры синтаксических отличий
-
Oracle: Часто используется оператор присваивания
:=, но также активно применяетсяDEFAULTили прямая инициализация. Широко развита работа с атрибутами%TYPEи%ROWTYPE. -
PostgreSQL: Синтаксис очень похож (
:=), но синтаксис функций требует оборачивания тела в текстовые кавычки ($$ ... $$) и явного указания языкаLANGUAGE plpgsql.
-
Oracle: Если запрос в блоке PL/SQL возвращает 0 строк, автоматически возбуждается исключение
NO_DATA_FOUND. -
PostgreSQL: При использовании
SELECT ... INTOв PL/pgSQL переменные просто становятсяNULL, исключение не генерируется (если не настроено строгое поведение).
-
Oracle:
SQLCREATE OR REPLACE PACKAGE my_pack AS procedure do_something; END my_pack; -
PostgreSQL: Пакеты создать нельзя. Функции и процедуры создаются по отдельности, а для изоляции и структурирования кода используются разные схемы (schemas).
Какие еще языки могут использоваться с postgres?
1. Языки из стандартного дистрибутива
CREATE EXTENSION extension_name; (например, CREATE EXTENSION plpython3u;).-
PL/Python (
plpython3u)Один из самых популярных альтернативных языков. Позволяет использовать всю мощь Python для работы с данными, математикой, парсинга или вызова внешних API прямо из базы.
SQLCREATE FUNCTION get_max(a int, b int) RETURNS int AS $$ return max(a, b) $$ LANGUAGE plpython3u; -
PL/Perl (
plperl) Исторически один из первых языков в Postgres. Удобен для сложных манипуляций со строками, регулярных выражений и задач системного администрирования. -
PL/Tcl (
pltcl) Язык Tcl (Tool Command Language), исторически встроенный в экосистему PostgreSQL. Реже используется в новых проектах, но поддерживается «из коробки».
2. Сторонние и популярные языки (расширения)
-
PL/v8 (JavaScript / V8)Позволяет писать функции на JavaScript, используя движок V8 от Google. Идеально подходит, если нужно обрабатывать JSON-документы (которых в Postgres множество) с помощью привычного JS-кода или переиспользовать бизнес-логику из Node.js/фронтенда на стороне базы данных.
-
PL/JavaДает возможность писать код на Java, используя стандартные интерфейсы JDBC. Востребовано в энтерпрайз-среде, где вся логика монолита завязана на Java.
-
PL/RИнтеграция со статистическим языком R. Позволяет выполнять сложные математические вычисления, статистический анализ и строить модели машинного обучения прямо внутри SQL-запросов.
-
PL/LuaСверхбыстрый и легкий скриптовый язык Lua, встроенный в PostgreSQL. Часто применяется там, где критична максимальная производительность скриптов в базе.
-
PL/shПозволяет выполнять системные shell-скрипты (Bash и др.) прямо из базы данных (например, запускать утилиты ОС по триггеру). Это крайне опасный с точки зрения безопасности язык, поэтому он используется только в изолированных контурах.
Когда и что использовать?
-
Для стандартной бизнес-логики, CRUD-операций и высокой скорости работы без лишних зависимоств выбирайте PL/pgSQL.
-
Для работы с нейросетями, сложной аналитики, парсинга или интеграции с внешними библиотеками экосистемы Python незаменим PL/Python.
-
Для интенсивной работы с JSON и быстрой обработки данных через знакомый синтаксис — PL/v8 (JS).
Как активировать PL/Python, примеры функции и процедуры
postgresql-plpython3 в Linux), после чего его можно включить в нужной базе данных.1. Активация PL/Python
postgres):CREATE EXTENSION IF NOT EXISTS plpython3u;
Важная деталь: Букваuна конце расширения означает untrusted (недоверенный). Это значит, что функции на PL/Python могут обращаться к файловой системе сервера, сети и операционной системе, поэтому создавать функции на PL/Python может только суперпользователь СУБД.
2. Пример функции (Function)
json, math, requests, pandas и др.).CREATE OR REPLACE FUNCTION calculate_cart_total(cart_json text)
RETURNS numeric AS $$
import json
try:
data = json.loads(cart_json)
items = data.get("items", [])
total = 0.0
for item in items:
price = item.get("price", 0)
quantity = item.get("quantity", 1)
total += price * quantity
return round(total, 2)
except Exception as e:
# raise_notice или log позволяют отправлять сообщения в клиент/лог базы
plpy.notice(f"Ошибка при обработке JSON: {e}")
return 0.0
$$ LANGUAGE plpython3u;
SELECT calculate_cart_total('{"items": [{"price": 100.5, "quantity": 2}, {"price": 50.0, "quantity": 1}]}');
-- Вернет: 251.00
3. Пример процедуры (Procedure)
plpy.commit(), plpy.rollback()).plpy внутри PL/Python позволяет выполнять SQL-запросы внутри базы.CREATE OR REPLACE PROCEDURE cleanup_old_logs(days_old int)
AS $$
import time
# Выполняем SQL-запрос к БД с помощью plpy.execute
plan = plpy.prepare(
"SELECT id, message FROM system_logs WHERE created_at < NOW() - ($1 || ' days')::INTERVAL",
["int"]
)
result = plpy.execute(plan, [days_old])
plpy.info(f"Найдено записей для удаления: {len(result)}")
if len(result) > 0:
# Логика обработки записей на Python
for row in result:
log_id = row["id"]
# Здесь можно вызвать внешний API, сохранить резервную копию и т.д.
# Удаление записей
delete_plan = plpy.prepare(
"DELETE FROM system_logs WHERE created_at < NOW() - ($1 || ' days')::INTERVAL",
["int"]
)
plpy.execute(delete_plan, [days_old])
# Фиксация изменений
plpy.commit()
$$ LANGUAGE plpython3u;
CALL cleanup_old_logs(30);
Дополнительные возможности plpy
plpy:-
plpy.execute("SELECT ...")— выполнение произвольного SQL-запроса. -
plpy.prepare(query, [types])— подготовка SQL-запроса для многократного выполнения. -
plpy.notice(msg),plpy.info(msg),plpy.warning(msg)— вывод сообщений пользователю. -
plpy.error(msg)— генерация исключения, отменяющего транзакцию.
Возвращают ли функции PL/Python наборы строк (recordsets) которые можно использовать в SQL-запросах?
FROM SQL-запроса, как обычную таблицу или представление (View).-
Список словарей / списков / кортежей — если функция формирует и отдает все данные сразу.
-
Итератор / Генератор (
yield) — если данных много и их нужно отдавать по одной строке (ленивые вычисления), чтобы не перегружать оперативную память.
Вариант 1. Возврат всех строк через список (List of Dicts)
RETURNS TABLE(...) или RETURNS SETOF record_type. Внутри Python-кода вы возвращаете обычный список Python-словарей, где ключи соответствуют именам колонок.CREATE OR REPLACE FUNCTION generate_user_report(min_age int)
RETURNS TABLE(user_id int, full_name text, status text) AS $$
# В реальности данные могут запрашиваться из внешнего API или рассчитываться
users = [
{"user_id": 1, "full_name": "Алексей Иванов", "age": 25},
{"user_id": 2, "full_name": "Мария Сидорова", "age": 19},
{"user_id": 3, "full_name": "Иван Петров", "age": 34}
]
result = []
for user in users:
if user["age"] >= min_age:
result.append({
"user_id": user["user_id"],
"full_name": user["full_name"],
"status": "Adult" if user["age"] >= 21 else "Young"
})
return result
$$ LANGUAGE plpython3u;
Использование функции в SQL:
SELECT *
FROM generate_user_report(20)
WHERE status = 'Adult';
Вариант 2. Построчная отдача через генератор (yield)
yield.CREATE OR REPLACE FUNCTION stream_numbers(limit_num int)
RETURNS TABLE(num int, square int) AS $$
for i in range(1, limit_num + 1):
# Отдаем по одной строке за раз
yield {"num": i, "square": i * i}
$$ LANGUAGE plpython3u;
Использование в SQL-запросе:
SELECT num, square
FROM stream_numbers(5)
WHERE square > 10;
| num | square |
| 4 | 16 |
| 5 | 25 |
Вариант 3. Возврат результата встроенного SQL-запроса (plpy.execute)
plpy.execute(), возвращаемый объект PLPyResult уже является итерируемым набором строк, поэтому его можно вернуть напрямую:CREATE OR REPLACE FUNCTION get_active_users()
RETURNS TABLE(id int, email text) AS $$
# plpy.execute возвращает результат, который совместим с SETOF/TABLE
res = plpy.execute("SELECT id, email FROM users WHERE is_active = true")
return res
$$ LANGUAGE plpython3u;
Использование Postgres в C#
Как вызываются процедуры и функции на PL/Python из C#?
Вызов функции, возвращающей значение или набор строк
SELECT * FROM function_name(@param). Это универсальный подход, работающий как для скалярных значений, так и для табличных результатов (RETURNS TABLE).Пример 1: Вызов функции, возвращающей набор строк (RETURNS TABLE)
CREATE OR REPLACE FUNCTION generate_user_report(min_age int)
RETURNS TABLE(user_id int, full_name text, status text) AS $$
# PL/Python логика
$$ LANGUAGE plpython3u;
using System;
using System.Threading.Tasks;
using Npgsql;
public class PostgresExample
{
private const string ConnectionString = "Host=localhost;Database=mydb;Username=postgres;Password=secret";
public async Task ReadUserReportAsync(int minAge)
{
await using var conn = new NpgsqlConnection(ConnectionString);
await conn.OpenAsync();
// Обращаемся к функции как к обычной таблице
const string sql = "SELECT user_id, full_name, status FROM generate_user_report(@minAge)";
await using var cmd = new NpgsqlCommand(sql, conn);
cmd.Parameters.AddWithValue("minAge", minAge);
await using var reader = await cmd.ExecuteReaderAsync();
while (await reader.ReadAsync())
{
// Используем кортежи (Tuples), доступные начиная с C# 7
var (userId, fullName, status) = (
reader.GetInt32(0),
reader.GetString(1),
reader.GetString(2)
);
Console.WriteLine($"ID: {userId}, Имя: {fullName}, Статус: {status}");
}
}
}
Пример 2: Вызов скалярной функции (возвращает одно значение)
public async Task<decimal> GetCartTotalAsync(string cartJson)
{
await using var conn = new NpgsqlConnection(ConnectionString);
await conn.OpenAsync();
const string sql = "SELECT calculate_cart_total(@cartJson)";
await using var cmd = new NpgsqlCommand(sql, conn);
cmd.Parameters.AddWithValue("cartJson", cartJson);
// ExecuteScalarAsync возвращает первое значение первой строки
var result = await cmd.ExecuteScalarAsync();
return Convert.ToDecimal(result);
}
Вызов процедуры (Procedure)
CALL. В Npgsql это можно сделать двумя способами: через явный CALL или через CommandType.StoredProcedure.Пример вызова процедуры через CALL
CREATE OR REPLACE PROCEDURE cleanup_old_logs(days_old int) AS $$
# PL/Python логика
$$ LANGUAGE plpython3u;
public async Task CleanupLogsAsync(int daysOld)
{
await using var conn = new NpgsqlConnection(ConnectionString);
await conn.OpenAsync();
// Явный вызов процедуры
const string sql = "CALL cleanup_old_logs(@daysOld)";
await using var cmd = new NpgsqlCommand(sql, conn);
cmd.Parameters.AddWithValue("daysOld", daysOld);
// Используем ExecuteNonQueryAsync, так как процедура не возвращает табличный результат
await cmd.ExecuteNonQueryAsync();
}
Особенности работы с массивами (ARRAY) из C# 7
integer[] или text[]), Npgsql автоматически сопоставляет их с массивами C# (int[], string[]).public async Task ProcessIdsAsync(int[] ids)
{
await using var conn = new NpgsqlConnection(ConnectionString);
await conn.OpenAsync();
const string sql = "SELECT process_array_function(@ids)";
await using var cmd = new NpgsqlCommand(sql, conn);
// Npgsql автоматически приведёт int[] C# к integer[] в Postgres
cmd.Parameters.AddWithValue("ids", ids);
await cmd.ExecuteNonQueryAsync();
}
Как передавать и парсить JSON/JSONB из C# в PL/Python функцию через Npgsql?
jsonb, а PL/Python автоматически или с помощью стандартного модуля json превращает его в родной словарь (dict) или список (list).1. Подготовка функции в PostgreSQL (PL/Python)
jsonb (или json). В PL/Python аргументы типа json/jsonb передаются в виде обычных строк, поэтому внутри их нужно распарсить с помощью стандартного модуля json.CREATE OR REPLACE FUNCTION process_user_data(payload jsonb)
RETURNS TABLE(user_id int, status text, is_valid boolean) AS $$
import json
# PL/Python получает json/jsonb как строку
data = json.loads(payload)
users = data.get("users", [])
result = []
for user in users:
u_id = user.get("id")
email = user.get("email", "")
# Простая проверка/обработка
is_valid = "@" in email
status = "Active" if user.get("age", 0) >= 18 else "Pending"
result.append({
"user_id": u_id,
"status": status,
"is_valid": is_valid
})
return result
$$ LANGUAGE plpython3u;
2. Вызов из C# с помощью Npgsql и System.Text.Json
System.Text.Json или явное указание типа NpgsqlDbType.Jsonb.Вариант 1: Использование System.Text.Json (рекомендуемый)
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Threading.Tasks;
using Npgsql;
using NpgsqlTypes;
public class JsonPostgresExample
{
private const string ConnectionString = "Host=localhost;Database=mydb;Username=postgres;Password=secret";
public async Task ProcessDataAsync()
{
// 1. Формируем C# объект для передачи
var requestData = new
{
users = new[]
{
new { id = 101, email = "alex@example.com", age = 25 },
new { id = 102, email = "invalid_email", age = 16 }
}
};
// 2. Сериализуем объект в строку JSON
string jsonPayload = JsonSerializer.Serialize(requestData);
await using var conn = new NpgsqlConnection(ConnectionString);
await conn.OpenAsync();
const string sql = "SELECT user_id, status, is_valid FROM process_user_data(@payload)";
await using var cmd = new NpgsqlCommand(sql, conn);
// 3. Добавляем параметр с явным указанием NpgsqlDbType.Jsonb
cmd.Parameters.Add(new NpgsqlParameter("payload", NpgsqlDbType.Jsonb)
{
Value = jsonPayload
});
// 4. Читаем результат
await using var reader = await cmd.ExecuteReaderAsync();
while (await reader.ReadAsync())
{
var userId = reader.GetInt32(0);
var status = reader.GetString(1);
var isValid = reader.GetBoolean(2);
Console.WriteLine($"ID: {userId} | Статус: {status} | Валиден: {isValid}");
}
}
}
3. Обратный сценарий: Возврат JSONB из PL/Python в C#
Функция в PostgreSQL:
CREATE OR REPLACE FUNCTION get_system_stats()
RETURNS jsonb AS $$
import json
stats = {
"server_time": "2026-09-12",
"metrics": {
"cpu_usage": 42.5,
"active_connections": 12
},
"tags": ["prod", "primary"]
}
# Возвращаем JSON-строку
return json.dumps(stats)
$$ LANGUAGE plpython3u;
Код чтения в C#:
public async Task GetStatsAsync()
{
await using var conn = new NpgsqlConnection(ConnectionString);
await conn.OpenAsync();
const string sql = "SELECT get_system_stats()";
await using var cmd = new NpgsqlCommand(sql, conn);
var rawJson = (string)await cmd.ExecuteScalarAsync();
// Десериализуем JSON в C# объект
using var doc = JsonDocument.Parse(rawJson);
var cpu = doc.RootElement
.GetProperty("metrics")
.GetProperty("cpu_usage")
.GetDouble();
Console.WriteLine($"CPU Usage: {cpu}%");
}
Ключевые моменты при работе с JSON/JSONB:
-
Тип параметра в C#: При передаче строкового JSON всегда указывайте
NpgsqlDbType.Jsonb(илиNpgsqlDbType.Json), иначе Npgsql передаст строку как обычныйtext, и Postgres выдаст ошибку несоответствия типов. -
Сериализация внутри PL/Python:
-
При приеме
jsonbв Python аргумент является строкойstr— применяйтеjson.loads(arg). -
При возврате
jsonbотправляйте строку — применяйтеjson.dumps(obj).
-
Какой тип даных поля в таблице Postgres лучше использовать для хранения пакетов json, но там могут быть и другие типы, например XML или изображения?
JSON или JSONB, так как PostgreSQL начнет проверять валидность JSON-структуры и выдаст ошибку при попытке сохранить XML или бинарные данные (изображение).Подход 1: BYTEA (Бинарные данные) — Самый универсальный и производительный
BYTEA (byte array) хранит данные «как есть» в виде сырого массива байт. Ему абсолютно неважно, что именно там находится — JSON, XML, PNG/JPEG или текстовый файл.CREATE TABLE payload_store (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
content_type VARCHAR(50) NOT NULL, -- 'application/json', 'application/xml', 'image/png'
payload BYTEA NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-
Плюсы:
-
Абсолютная универсальность (принимает всё: от текстовых форматов до бинарных изображений).
-
Минимальные накладные расходы при записи/чтении (нет перекодировок в текст).
-
Корректно сохраняет изображения без раздувания размера.
-
-
Минусы:
-
Из C# нужно передавать данные как
byte[](для JSON/XML это будет байтовый массив строки UTF-8). -
Внутри PostgreSQL нельзя делать прямое обращение к полям JSON (например,
payload->>'name').
-
Подход 2: TEXT — Удобнее для отладки, но со штрафом для бинарных данных
TEXT.CREATE TABLE payload_store (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
content_type VARCHAR(50) NOT NULL,
payload TEXT NOT NULL
);
-
Плюсы:
-
Текстовые данные (JSON, XML) хранятся в читаемом виде.
-
Текст можно при необходимости привести к типу
JSONBналету:SELECT (payload::jsonb)->>'key' WHERE content_type = 'application/json'.
-
-
Минусы:
-
Изображения придется кодировать в Base64 перед сохранением из C#.
-
Кодирование в Base64 увеличивает объем хранимого файла (изображения) примерно на 33%.
-
Сравнение вариантов
| Критерий | BYTEA | TEXT (с Base64 для фото) |
| Универсальность | 100% (любые бинарники и текст) | 100% (но изображения нужно кодировать) |
| Размер изображений | Исходный (1:1) | +33% к размеру файла из-за Base64 |
| Удобство просмотра JSON/XML в СУБД | Требуется encode(payload, 'escape') |
Читается из «коробки» |
| Сложность в C# (Npgsql) | Работа через byte[] |
Работа через string |
Рекомендация
-
Если изображений много или они большие — однозначно используйте
BYTEA. -
Если хранение изображений — редкий случай, а JSON/XML нужно часто просматривать напрямую в БД — используйте
TEXT(сохраняя картинки как Base64-строки). -
Обязательно добавьте колонку
content_type(MIME-тип) илиpayload_kind— без нее приложение на C# не сможет понять, как правильно интерпретировать вычитанные данные (как раскодировать JSON/XML или как отдать картинку).
Как работать с BYTEA из C# (Npgsql):
// Для JSON/XML кодируем строку в UTF-8 байты:
byte[] jsonBytes = System.Text.Encoding.UTF8.GetBytes(jsonString);
// Для изображений берем байты файла напрямую:
byte[] imageBytes = await System.IO.File.ReadAllBytesAsync("photo.png");
// Передача в NpgsqlParameter:
cmd.Parameters.Add(new NpgsqlParameter("payload", NpgsqlTypes.NpgsqlDbType.Bytea) { Value = imageBytes });
Запрос данных из пакетов JSON в поле таблицы
Для хранения пакетов используем такую таблицу:
Пакеты хранятся в поле pack_data, они одинаковой структуры для определенного типа записи (поле type_id)
Пакет имеет такую структуру:
[
{"charsetinfo":
{"name": "utf-8"}
},
{"history":
[
{"BOARDID": "TQBR", "TRADEDATE": "2026-09-21", "SHORTNAME": "РоссЮг ао", "SECID": "MRKY", "NUMTRADES": 275, "VALUE": 1541379, "OPEN": 0.07065, "LOW": 0.0695, "HIGH": 0.0716, "LEGALCLOSEPRICE": 0.07015, "WAPRICE": 0.0704, "CLOSE": 0.07055, "VOLUME": 21890000, "MARKETPRICE2": 0.0702, "MARKETPRICE3": 0.0702, "ADMITTEDQUOTE": null, "MP2VALTRD": 766668, "MARKETPRICE3TRADESVALUE": 766668, "ADMITTEDVALUE": null, "WAVAL": 0, "TRADINGSESSION": 3, "CURRENCYID": "SUR", "TRENDCLSPR": 0.21, "TRADE_SESSION_DATE": "2026-09-21"},{"BOARDID": "TQBR", "TRADEDATE": "2026-09-21", "SHORTNAME": "Мостотрест", "SECID": "MSTT", "NUMTRADES": 162, "VALUE": 1539552, "OPEN": 69.4, "LOW": 67.9, "HIGH": 69.4, "LEGALCLOSEPRICE": 67.9, "WAPRICE": 68.5, "CLOSE": 68.35, "VOLUME": 22470, "MARKETPRICE2": 68.45, "MARKETPRICE3": 68.45, "ADMITTEDQUOTE": null, "MP2VALTRD": 1292162.5, "MARKETPRICE3TRADESVALUE": 1292162.5, "ADMITTEDVALUE": null, "WAVAL": 0, "TRADINGSESSION": 3, "CURRENCYID": "SUR", "TRENDCLSPR": -1.51, "TRADE_SESSION_DATE": "2026-09-21"},
...
]
}
]
Нам нужно соединить все пакеты и получить записи из поля "history".
Чтобы развернуть массив из массива history во всех JSON-пакетах одной SQL-выборкой, в PostgreSQL используются функции для работы с JSON/JSONB:
-
Приведение типа: Так как поле
pack_dataимеет типtext, его нужно привести кjsonb(илиjson):pack_data::jsonb. -
jsonb_array_elements: Позволяет «расплющить» (распаковать) элементы JSON-массива в отдельные строки.
SQL-запрос
Поскольку yаш JSON представляет собой массив объектов, где массив history находится во втором элементе (pack_data::jsonb -> 1 -> 'history'), запрос будет выглядеть так:
SELECT u.upload_id,
u.pack_name,
u.upload_time,
-- Извлекаем поля из объектов внутри history
history_item ->> 'SECID' AS secid,
history_item ->> 'SHORTNAME' AS shortname,
history_item ->> 'TRADEDATE' AS tradedate,
(history_item ->> 'OPEN')::numeric AS open_price,
(history_item ->> 'CLOSE')::numeric AS close_price,
(history_item ->> 'VOLUME')::bigint AS volume,
(history_item ->> 'VALUE')::numeric AS value,
history_item ->> 'BOARDID' AS boardid
FROM trading.upload_data u
WHERE u.type_id = 1
-- 1. Извлекаем массив history из второго элемента корня JSON и разворачиваем его по строкам
CROSS JOIN LATERAL jsonb_array_elements(u.pack_data::jsonb -> 1 -> 'history') AS history_item;
Как это работает:
-
u.pack_data::jsonb -> 1— берет второй элемент корневого JSON-массива (индексация с 0, поэтому1указывает на объект{"history": [...]}). -
-> 'history'— переходит непосредственно к массиву цен и сделок. -
jsonb_array_elements(...)— превращает каждый элемент массиваhistoryв отдельную строку результата. -
CROSS JOIN LATERAL— связывает каждую строку из таблицыupload_dataс каждым распакованным элементами из массиваhistory. -
history_item ->> 'FIELD_NAME'— извлекает текстовое значение конкретного ключа из JSON-объекта. -
::numeric,::bigint— сразу приводит текстовые значения JSON к правильным математическим типам PostgreSQL для последующей фильтрации или агрегации.
Альтернатива: Если порядок элементов в корневом JSON может меняться
Если элемент history не всегда гарантированно идет вторым (с индексом 1), безопаснее сначала распаковать корневой массив, а затем найти элемент с ключом history:
SELECT u.upload_id,
history_item ->> 'SECID' AS secid,
history_item ->> 'SHORTNAME' AS shortname,
(history_item ->> 'CLOSE')::numeric AS close_price
FROM trading.upload_data u
WHERE u.type_id = 1
-- Распаковываем корневой массив
CROSS JOIN LATERAL jsonb_array_elements(u.pack_data::jsonb) AS root_elem
-- Распаковываем массив history (игнорируя элементы без этого ключа, например charsetinfo)
CROSS JOIN LATERAL jsonb_array_elements(root_elem -> 'history') AS history_item;
Этот вариант гибче, так как найдет массив history в любом месте структуры пакета.
Что делает оператор: ->>
->> в PostgreSQL используется для извлечения значения из JSON или JSONB в виде обычного текста (text).| Оператор | Что делает | Возвращаемый тип | Назначение |
-> |
Извлекает поле или элемент массива | json или jsonb |
Для дальнейшего навигационного продвижения вглубь структуры JSON |
->> |
Извлекает поле или элемент массива | text |
Для вывода конечных данных, фильтрации в WHERE или приведения типов |
Примеры применения
data с содержимым {"user": {"name": "Иван", "age": 25}}.1. Извлечение поля по ключу
-- Оператор -> возвращает JSON ("Иван" в кавычках)
SELECT data -> 'user' -> 'name';
-- Результат: "Иван" (тип jsonb)
-- Оператор ->> возвращает чистый текст (без кавычек)
SELECT data -> 'user' ->> 'name';
-- Результат: Иван (тип text)
2. Извлечение элемента из JSON-массива по индексу
["apple", "banana", "cherry"]:SELECT data ->> 0;
-- Результат: apple (тип text)
3. Приведение типов для расчетов и фильтрации
->> возвращает обычный text, результат можно легко привести к числу, дате или логическому типу через ::type:-- Извлекаем возраст как число для условия в WHERE
SELECT *
FROM users_table
WHERE (data ->> 'age')::int >= 18;
-- Извлекаем дату и делаем сортировку
SELECT data ->> 'TRADEDATE' AS trade_date
FROM trading.upload_data
ORDER BY (data ->> 'TRADEDATE')::date DESC;
Чек-лист для запоминания
-
Если вы хотите продолжить цепочку погружения в JSON дальше — используйте одиночную стрелку:
data -> 'a' -> 'b'. -
На самом последнем шаге, когда нужно получить конечное значение в виде текста для вывода, используйте двойную стрелку:
data -> 'a' ->> 'b'.
Работа с Postgres SQL
Переключение текущей сессии в нужную схему.
Допустим вам нужно сделать схему main текущей.
Это достигается выполнением следующей команды:
SET search_path TO main, public;
Как это работает: Любые команды
CREATE TABLE qrtz_...без явного указания схемы теперь будут создавать таблицы строго внутриmain.
Проверьте переключение
Чтобы убедиться, что контекст изменился на main, выполните:
SHOW search_path;
SELECT current_schema();
В ответе должно быть main.
SET search_path TO main, public;, вы задаете списку схем приоритет слева направо:-
При создании объектов (
CREATE TABLE ...)
PostgreSQL всегда создает новый объект в первой схеме списка (в данном случаеmain), если вы не указали схему явно (например,CREATE TABLE public.my_table). -
При поиске и чтении данных (
SELECT * FROM my_table)
PostgreSQL ищет таблицу по очереди:
-
Сначала проверяет, есть ли
my_tableв схемеmain. -
Если не находит — ищет её в схеме
public. -
Если её нет и там — выдает ошибку
relation "my_table" does not exist.
-
-
Доступ к системным функциям и расширениям
В схемеpublicчасто располагаются стандартные расширения (extensions), служебные функции или общие справочники базы данных. Указавpublicвторым номером, вы гарантируете, что PostgreSQL сможет найти эти системные функции, даже если вы работаете вmain.
Что будет, если указать только main?
SET search_path TO main;:-
Все таблицы создадутся в
main(как и нужно). -
Но если вашей программе или функции потребуется объект или расширение из
public, PostgreSQL его не увидит и выдаст ошибку.
SET search_path TO main, public; является стандартом: новые объекты создаются в main, но старые и системные объекты из public остаются доступны для чтения.