Примечание: вышла PostgreSQL 19 Beta 1
PostgreSQL 19 Beta 1 вышла 4 июня 2026 года, поэтому запросы к графам свойств SQL/PGQ уже можно попробовать до финального релиза, ожидаемого позднее в 2026 году. В Beta 1 добавлена поддержка запросов к графам свойств в стандартном синтаксисе SQL поверх существующих реляционных таблиц — без нового движка хранения и расширений.
Кратко: PostgreSQL 19 добавляет SQL/PGQ (Property Graph Queries, запросы к графам свойств) на основе стандарта SQL:2023. Графовые структуры можно определить поверх уже существующих реляционных таблиц и запрашивать их синтаксисом сопоставления с образцом. Не нужны новый движок хранения, расширения или миграция данных.
Введение в SQL/PGQ
Графовые БД, например Neo4j, удобны для запросов к связям: «найти друзей друзей» или «обнаружить циклические зависимости» естественно выражаются на графовом языке запросов. Но отдельная графовая БД рядом с PostgreSQL означает дублирование данных, сложность синхронизации и ещё одну систему, которую нужно сопровождать.
PostgreSQL 19 добавляет графовые запросы непосредственно в SQL через стандарт SQL/PGQ (ISO/IEC 9075-16:2023). Ключевое решение здесь такое: граф свойств задаётся как представление над существующими реляционными таблицами. Данные остаются на месте; PostgreSQL нужно лишь указать, какие таблицы соответствуют вершинам, а какие — рёбрам.
Определение графа свойств
Граф свойств состоит из двух типов элементов: таблиц вершин (узлов) и таблиц рёбер (связей). Определение графа сопоставляет их с уже существующими таблицами.
Пример таблиц
В примерах ниже используется небольшая схема социальной сети: пользователи, публикации, подписки и отметки «нравится». Создайте таблицы и добавьте несколько строк — тогда будет на чём выполнять запросы.
-- Обычные таблицы PostgreSQLCREATE TABLE users ( id SERIAL PRIMARY KEY, name TEXT NOT NULL, email TEXT, joined_at DATE DEFAULT CURRENT_DATE);CREATE TABLE follows ( id SERIAL PRIMARY KEY, follower_id INT NOT NULL REFERENCES users(id), followed_id INT NOT NULL REFERENCES users(id), created_at TIMESTAMP DEFAULT now());CREATE TABLE posts ( id SERIAL PRIMARY KEY, author_id INT NOT NULL REFERENCES users(id), title TEXT NOT NULL, created_at TIMESTAMP DEFAULT now());CREATE TABLE likes ( id SERIAL PRIMARY KEY, user_id INT NOT NULL REFERENCES users(id), post_id INT NOT NULL REFERENCES posts(id), created_at TIMESTAMP DEFAULT now());-- Добавим данныеINSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com'), ('Bob', 'bob@example.com'), ('Charlie', 'charlie@example.com'), ('Diana', 'diana@example.com');INSERT INTO follows (follower_id, followed_id) VALUES (1, 2), (1, 3), (2, 3), (3, 4), (4, 1);INSERT INTO posts (author_id, title) VALUES (1, 'Getting Started with PostgreSQL'), (2, 'Graph Queries in SQL'), (3, 'Why I Switched from Neo4j');INSERT INTO likes (user_id, post_id) VALUES (2, 1), (3, 1), (4, 2), (1, 3), (2, 3);
Создание графа
Определение графа сопоставляет каждой таблице метку вершины или ребра. Таблицы вершин становятся узлами графа; для таблиц рёбер указываются столбцы, которые ссылаются на исходную и конечную вершины.
CREATE PROPERTY GRAPH social_graph VERTEX TABLES ( users LABEL person PROPERTIES (id, name, email, joined_at), posts LABEL post PROPERTIES (id, title, created_at) ) EDGE TABLES ( follows SOURCE KEY (follower_id) REFERENCES users (id) DESTINATION KEY (followed_id) REFERENCES users (id) LABEL follows PROPERTIES (created_at), posts AS post_edges SOURCE KEY (author_id) REFERENCES users (id) DESTINATION KEY (id) REFERENCES posts (id) LABEL wrote, likes SOURCE KEY (user_id) REFERENCES users (id) DESTINATION KEY (post_id) REFERENCES posts (id) LABEL liked PROPERTIES (created_at) );
Данные при этом не копируются. Создаётся определение графа, похожее на представление: PostgreSQL использует его, чтобы преобразовать графовый запрос в реляционные операции над существующими таблицами. Таблица posts участвует в графе дважды: как таблица вершин post и как источник рёбер wrote, связывающих автора с публикацией. Псевдоним post_edges нужен, чтобы различить эти два использования одной таблицы в определении графа. Это не дублирование строк, а два разных представления одной таблицы в модели графа.
Запросы с GRAPH_TABLE и MATCH
Функция GRAPH_TABLE принимает имя графа и шаблон MATCH, а возвращает обычный набор строк:
На кого подписана Alice
Это обход на один переход: начинаем с Alice, идём по исходящим рёбрам follows и выводим имя пользователя в конечной вершине.
SELECT * FROM GRAPH_TABLE (social_graph MATCH (a IS person WHERE a.name = 'Alice') -[f IS follows]->(b IS person) COLUMNS (b.name AS followed_name));
followed_name--------------- Bob Charlie
Стрелка -[f IS follows]-> обозначает направленное ребро типа follows. Выражение (a IS person) сопоставляется с вершинами, имеющими метку person.
Друзья друзей
Соедините в шаблоне два ребра follows, чтобы выполнить обход на два перехода. Переменные b и c привязываются соответственно к промежуточной и конечной вершинам и затем выводятся как именованные столбцы.
SELECT * FROM GRAPH_TABLE (social_graph MATCH (a IS person WHERE a.name = 'Alice') -[IS follows]->(b IS person) -[IS follows]->(c IS person) COLUMNS ( b.name AS friend, c.name AS friend_of_friend ));
friend | friend_of_friend---------+------------------ Bob | Charlie Charlie | Diana
Шаблон с несколькими переходами записывается цепочкой рёбер. Каждая стрелка означает ровно один переход.
Замечание переводчика: о глубине обхода. В этом запросе глубина обхода равна двум, потому что в шаблоне указаны две стрелки. PostgreSQL 19 пока не умеет записать «повторяй переход, пока есть связь» одним графовым шаблоном. Если нужны связи ровно через три перехода, добавьте третью стрелку; если глубина заранее неизвестна, используйте рекурсивный CTE. Это ограничение не мешает запросам с известной глубиной, но важно при поиске всех зависимостей или всех путей между двумя вершинами.
Кто поставил «нравится» публикациям Alice
В определении social_graph выше таблица posts также задаёт ребро wrote: от автора к его публикации. Поэтому можно сначала найти Alice, перейти к её публикациям по wrote, а затем пройти по входящим рёбрам liked к пользователям, поставившим отметку «нравится».
SELECT * FROM GRAPH_TABLE (social_graph MATCH (author IS person WHERE author.name = 'Alice') -[authorship IS wrote]->(p IS post) <-[IS liked]-(liker IS person) COLUMNS ( liker.name AS liked_by, p.title AS post_title ));
liked_by | post_title----------+----------------------------------- Bob | Getting Started with PostgreSQL Charlie | Getting Started with PostgreSQL
Синтаксис <-[IS liked]- разворачивает направление ребра: он сопоставляет входящие связи. Вся цепочка читается слева направо: «Alice — написала → публикация ← понравилась — пользователь».
Разберём шаблон по шагам:
-
(author IS person WHEREauthor.name= 'Alice')находит вершину пользователя Alice. -
-[authorship IS wrote]->(p IS post)переходит к публикациям, для которых Alice — автор. -
<-[IS liked]-(liker IS person)идёт от каждой такой публикации к пользователям, поставившим «нравится».
Замечание переводчика: как читать направление. Направление ребра задано в
CREATE PROPERTY GRAPH, а не тем, как удобнее прочесть запрос. Дляlikesисходная вершина — пользователь (user_id), конечная — публикация (post_id). Поэтому, двигаясь от публикации к пользователю, вMATCHнужна стрелка влево:<-[IS liked]-. Ошибка в направлении особенно легко возникает, когда одна и та же таблица участвует в нескольких связях.
Совмещение графовых запросов с обычным SQL
GRAPH_TABLE возвращает обычный набор строк, поэтому его можно использовать везде, где допустимы подзапрос или таблица:
-- Найти пользователей, на которых подписано более двух человекSELECT followed_name, count(*) AS follower_countFROM GRAPH_TABLE (social_graph MATCH (a IS person)-[IS follows]->(b IS person) COLUMNS (b.name AS followed_name))GROUP BY followed_nameHAVING count(*) > 1ORDER BY follower_count DESC;
Это одно из главных преимуществ перед отдельной графовой БД: графовые запросы естественно сочетаются с агрегациями, оконными функциями, CTE и остальными возможностями SQL.
Что происходит внутри
При выполнении запроса с GRAPH_TABLE переписыватель запросов PostgreSQL преобразует графовый шаблон в обычные реляционные операции над базовыми таблицами: соединения (JOIN), фильтры и проекции, то есть выбор нужных столбцов. План выполнения остаётся обычным планом PostgreSQL: оптимизатор может применять индексы, параллельное выполнение и привычные стратегии.
Проверить это можно с помощью EXPLAIN:
EXPLAIN SELECT * FROM GRAPH_TABLE (social_graph MATCH (a IS person WHERE a.name = 'Alice') -[IS follows]->(b IS person) COLUMNS (b.name AS followed_name));
В плане будут соединения таблиц users и follows, использующие доступные индексы. Отдельного движка выполнения графовых запросов здесь нет.
Управление графами свойств
Графы свойств — объекты схемы, как представления. Их можно вывести, изменить и удалить стандартными командами.
Просмотр списка графов
В psql используйте метакоманду \dG. Если вывод нужен в скрипте, обращайтесь непосредственно к системным каталогам графов свойств.
-- В psql\dG-- Метаданные графов свойств размещены в нескольких системных каталогах.-- Обычно достаточно этих каталогов:SELECT * FROM pg_propgraph_element; -- таблицы вершин и рёберSELECT * FROM pg_propgraph_label; -- меткиSELECT * FROM pg_propgraph_label_property; -- соответствие «метка → свойство»SELECT * FROM pg_propgraph_property; -- выражения свойств
Изменение графа
ALTER PROPERTY GRAPH позволяет добавлять, удалять и изменять таблицы вершин и рёбер без пересоздания графа.
-- Добавить новый тип ребраALTER PROPERTY GRAPH social_graph ADD EDGE TABLE messages SOURCE KEY (sender_id) REFERENCES users (id) DESTINATION KEY (receiver_id) REFERENCES users (id) LABEL sent_message;
Удаление графа
DROP PROPERTY GRAPH удаляет только определение графа.
DROP PROPERTY GRAPH social_graph;
Удаление графа не затрагивает таблицы, на которых он основан.
Практические сценарии
Ниже — несколько случаев, где графовое представление существующих таблиц позволяет выразить запрос понятнее, чем многоуровневые самосоединения.
Отслеживание зависимостей
Представьте пакеты вершинами, а таблицу связей depends_on — рёбрами. Так удобно искать прямые зависимости и, если заранее известна глубина, зависимости в несколько переходов. Для зависимостей произвольной глубины в PostgreSQL 19 по-прежнему нужен рекурсивный CTE.
CREATE TABLE packages ( id SERIAL PRIMARY KEY, name TEXT NOT NULL, version TEXT);CREATE TABLE depends_on ( id SERIAL PRIMARY KEY, package_id INT REFERENCES packages(id), dependency_id INT REFERENCES packages(id));CREATE PROPERTY GRAPH dep_graph VERTEX TABLES (packages LABEL pkg PROPERTIES (id, name, version)) EDGE TABLES ( depends_on SOURCE KEY (package_id) REFERENCES packages(id) DESTINATION KEY (dependency_id) REFERENCES packages(id) LABEL depends );-- Найти прямые зависимостиSELECT * FROM GRAPH_TABLE (dep_graph MATCH (a IS pkg WHERE a.name = 'my-app') -[IS depends]->(b IS pkg) COLUMNS (b.name AS dependency, b.version));
Иерархия в организации
Связи подчинённости образуют ориентированный граф над одной таблицей сотрудников. Шаблон из двух переходов возвращает сотрудника, его руководителя и руководителя руководителя одним запросом.
-- Найти цепочки подчинённости из двух уровнейSELECT * FROM GRAPH_TABLE (org_graph MATCH (emp IS employee) -[IS reports_to]->(mgr IS employee) -[IS reports_to]->(dir IS employee) COLUMNS ( emp.name AS employee, mgr.name AS manager, dir.name AS director ));
Выявление мошенничества
Обходы по общим атрибутам помогают находить учётные записи, которые используют те же контактные данные, что и уже помеченная запись. Шаблон «помеченная учётная запись → телефон → другая учётная запись» находит такие связи, которые неудобно записывать обычными JOIN.
-- Найти учётные записи с тем же номером телефона, что у помеченной записиSELECT * FROM GRAPH_TABLE (fraud_graph MATCH (flagged IS account WHERE flagged.status = 'flagged') -[IS uses]->(phone IS phone_number) <-[IS uses]-(other IS account) COLUMNS ( flagged.id AS flagged_account, phone.number, other.id AS connected_account ));
Текущие ограничения
В первой реализации SQL/PGQ в PostgreSQL 19 есть несколько намеренных ограничений:
Нет путей переменной длины: нельзя записать шаблон наподобие -[IS follows]->+ (один или более переходов) или -[IS follows]->{2,5} (от двух до пяти переходов). Каждый переход нужно указать явно. Поэтому рекурсивные обходы, например поиск кратчайшего пути или транзитивного замыкания, по-прежнему требуют рекурсивных CTE.
Нет квантифицированных шаблонов путей: квантификаторы *, + и {m,n} планируются в последующем патче, но в первый релиз не вошли.
Только фиксированная глубина: запросы вида «найти все пути между A и B независимо от длины» средствами одного лишь SQL/PGQ в PostgreSQL 19 невозможны. Для них используйте рекурсивные CTE.
Замечание переводчика: когда выбирать рекурсивный CTE. Фиксированная глубина означает, что число переходов известно при написании запроса: например, «руководитель руководителя» — это два перехода. Переменная глубина означает, что нужно идти по связям до конца цепочки или пока не найден нужный узел: например, «все зависимости пакета». Во втором случае в PostgreSQL 19 используйте
WITH RECURSIVE; SQL/PGQ здесь пока не заменяет рекурсию.
Для запросов со связями фиксированной глубины — «друзья друзей», шаблоны на 2–3 перехода, поиск прямых зависимостей — SQL/PGQ уже подходит. Поддержка путей переменной длины ожидается в одном из будущих выпусков PostgreSQL.
SQL/PGQ и другие решения для графов
|
|
SQL/PGQ (PG 19) |
Apache AGE |
Neo4j |
|---|---|---|---|
|
Язык |
Стандарт SQL (ISO 2023) |
Cypher через расширение |
Cypher |
|
Хранение |
Существующие таблицы |
Хранилище расширения |
Собственная графовая БД |
|
Пути переменной длины |
Пока нет |
Да |
Да |
|
Кратчайший путь |
Пока нет |
Ограниченно |
Да |
|
Установка |
Встроено |
Нужно расширение |
Отдельная БД |
|
Основано на стандарте |
Да (SQL:2023) |
Нет |
Нет |
|
Индексы |
Использует существующие индексы PostgreSQL |
Собственные индексы |
Собственные индексы |
|
Полная поддержка SQL |
Да: |
Ограниченная интеграция |
Нет SQL |
Главное преимущество SQL/PGQ: он работает с уже существующими таблицами и индексами. Не требуются миграция данных, установка расширения и сопровождение отдельной системы.
Итоги
SQL/PGQ добавляет в PostgreSQL стандартизированные графовые запросы без миграции данных, расширений и отдельной БД. Первая реализация поддерживает сопоставление шаблонов фиксированной глубины, чего достаточно для многих распространённых графовых запросов. Поддержка путей переменной длины ожидается в будущих выпусках.
Источники
ссылка на оригинал статьи https://habr.com/ru/articles/1059810/