在SQLAlchemy中使用Postgres @>运算符和json_build_object

时间:2019-02-12 16:34:35

标签: python postgresql sqlalchemy

我有一个使用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

我的理解是@>运算符充当比较,但是SQLAlchemy文档中JSONB的比较方法仅引用,而不引用值< / strong>。

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

除了使用原始查询外,我不清楚如何通过SQLAlchemy设计此查询。

编辑1

Based on this answer,我尝试了以下内容。

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
         }
      })
   )

至少导致了一个不同的错误,这个错误由​​SQLAlchemy生成了一些SQL:

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

根据IljaEverilä的回答,我能够找到SQLAlchemy方法implemented in the source code,而using the sql-json method能够在附近获得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

答案和源代码的方法relies on the _FunctionGenerator都可以构建函数,但是目前尚不清楚如何在compile的过程中将某些内容附加到方法的末尾。

编辑3

NVM-答案的作者指出jsonb_build_object(...)将适合该模型而无需标记。

1 个答案:

答案 0 :(得分:1)

您已经注意到,链接的Q / A处理使用文字值的情况。解决方案是像以前尝试的那样,结合使用SQLA中的contains()和Postgresql中的jsonb_build_object()

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

我的理解是@>运算符充当比较,但是SQLAlchemy文档中JSONB的比较方法仅引用键,而不是值。

JSONB.Comparator.contains()的SQLAlchemy文档似乎写得不好。比较

  

布尔表达式。测试键(或数组)是否为参数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}})))