Использование Postgres @> Operator и json_build_object в SQLAlchemy - PullRequest
0 голосов
/ 12 февраля 2019

У меня есть запрос, использующий несколько операций, специфичных для Postgres:

SELECT 
    a.row_id, 
    a.name
FROM
    a 
JOIN
    b
ON
    b.json_record @> json_build_object('path', json_build_object('to', a.name))::jsonb

Насколько я понимаю, оператор @> действует как сравнение, но методы сравнения для JSONB в справочнике по SQLAlchemyтолько ключи , а не значения .

https://docs.sqlalchemy.org/en/latest/dialects/postgresql.html#sqlalchemy.dialects.postgresql.JSONB.Comparator

Мне не совсем ясно, как можно разработать этот запрос с помощью SQLAlchemy, если не использоватьнеобработанный запрос.

Редактировать 1

Основываясь на этом ответе , я дал приведенную ниже попытку.

session \
   .query(A_Table) \
   .join(
      B_Table.json_record.contains({
         'path': {
            'to': A_Table.name
         }
      })
   )

Это, однако, привело кв ошибке из строки 'to': A_Table.name:

AttributeError: Neither 'BinaryExpression' object nor 'Comparator' object has an attribute 'selectable'
sqlalchemy/orm/query.py", line 2206, in join
from_joinpoint=from_joinpoint,
File "<string>", line 2, in _join

Так что я вместо этого попытался

session \
   .query(A_Table) \
   .filter(
      B_Table.json_record.contains({
         'path': {
            'to': A_Table.name
         }
      })
   )

, которая, по крайней мере, привела к другой ошибке, с некоторой генерацией SQL из SQLAlchemy:

sqlalchemy.exc.StatementError: (builtins.TypeError) 
Object of type 'InstrumentedAttribute' is not JSON serializable 
[SQL: 'SELECT a.row_id AS a_id, a.name AS a_name FROM a, b 
WHERE b.json_record @> %(json_record_1)s'] [parameters: [{}]]

Этот SQL близок к тому, к чему я стремился, и может быть приемлемым, но пример, приведенный в ответе, предполагает, что я знаю значение заранее, когда то, что я хочу сделать, - это сравнениепротив значения строки.Обычно я делал бы:

.filter([a.name == b.json_record['path']['to'].astext])

Но я также пытаюсь использовать оптимизацию из индекса gin для этого столбца JSONB, в результате чего мне нужен оператор @>.

Редактировать 2

Основываясь на ответе Ильи Эвериля, я смог отследить метод SQLAlchemy , реализованный в исходном коде , и с использованием метода sql-json удалось получить SQL почти там.

session \
   .query(A_Table) \
   .join(
      B_Table.json_record.contains({
         json_sql({'path': json_sql({
            'to': A_Table.name
         }
      })
   )

Предоставление мне SQL:

SELECT 
    a.row_id, 
    a.name
FROM
    a 
JOIN
    b
ON
    b.json_record @> json_build_object('path', json_build_object('to', a.name))

Проблема с этим выводом заключается в том, что вместо:

json_build_object(..., json_build_object(...))

Допустимый синтаксис Postgres должен быть:

json_build_object(..., json_build_object(...))::jsonb

Как в ответе, так и в подходе исходного кода опирается на _FunctionGenerator, который может построить функцию, но это не яснокак что-то может быть добавлено в конец метода во время compile при прохождении этого маршрута.

Edit 3

NVM - автор ответа указал, что jsonb_build_object(...) будет соответствовать этой моделибез флага.

1 Ответ

0 голосов
/ 13 февраля 2019

Связанный Q / A обрабатывает случай использования литеральных значений, как вы заметили.Решение состоит в том, чтобы объединить использование contains() в SQLA и jsonb_build_object() в Postgresql, как вы пытались раньше:

session.query(A_Table) \
    .filter(
        B_Table.json_record.contains(
            func.jsonb_build_object( 
                'path',
                func.jsonb_build_object('to', A_Table.name)
            )
        )
    )

Насколько я понимаю, оператор @> действует как сравнение, нометоды сравнения для JSONB в документации по SQLAlchemy ссылаются только на ключи, а не на значения.

Документация по SQLAlchemy для JSONB.Comparator.contains() выглядит немного плохо написанной.Сравните

Булево выражение.Проверьте, являются ли ключи (или массив) надмножеством / содержат ключи выражения jsonb аргумента.

к документации Postgresql для @>:

Делает левуюЗначение JSON содержит правильные записи пути / значения JSON на верхнем уровне?


Вы можете скрыть детали построения jsonb в вспомогательной функции:

def build_jsonb(obj):
    if isinstance(obj, dict):
        pairs = [(k, build_jsonb(v)) for k, v in obj.items()]
        return func.jsonb_build_object(*[arg for p in pairs for arg in p])

    elif isinstance(obj, list):
        return func.jsonb_build_array(*[build_jsonb(v) for v in obj])

    else:
        return obj

и затем используйте его в исходном запросе:

session.query(A_Table) \
    .filter(
        B_Table.json_record.contains(
            build_jsonb({'path': {'to': A_Table.name}})))

Если вы хотите использовать явный синтаксис JOIN:

session.query(A_Table).\
    join(B_Table, B_Table.json_record.contains(
        build_jsonb({'path': {'to': A_Table.name}})))
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...