如何在Python/FastAPI、SqlAlchemy和PostgreSQL中查询具有深度嵌套对象的JSONB列

csga3l58  于 4个月前  发布在  PostgreSQL
关注(0)|答案(1)|浏览(84)

在PostgreSQL表“private_notion”中,我有一个JSONB列“record_map”,它可能包含也可能不包含嵌套对象,例如:

{
  "blocks": {
    "7a9abf0d-a066-4466-a565-4e6d7a960a37": {
      "name": "block1",
      "value": 1,
      "child": {
        "7a9abf0d-a066-4466-a565-4e6d7a960a37": {
          "name": "block2",
          "value": 2,
          "child": {
            "7a9abf0d-a066-4466-a565-4e6d7a960a37": {
              "name": "block3",
              "value": 3
            }
          }
        },
        "7a9abf0d-a066-4466-a565-4e6d7a960a38": {
          "name": "block4",
          "value": 4,
          "child": {
            "7a9abf0d-a066-4466-a565-4e6d7a960a39": {
              "name": "block5",
              "value": 5,
              "child": {
                "7a9abf0d-a066-4466-a565-4e6d7a960a40": {
                  "name": "block6",
                  "value": 6
                }
              }
            }
          }
        },
      }
    }
  }
}

字符串
为了检索数据,我们不知道哪个块有我们想要的数据,我们只有键。让我们假设我们正在寻找这个键为“7a 9abf 0 d-a066 -4466-a565- 4 e6 d 7a 960 a40”的对象,但我们不知道它位于父块4和块5的子块6中。另一个请求可能会寻找父块4,依此类推,我必须找到它的关键块。
整个代码看起来像这样:

async def get_private_notion_page(
        site_uuid: str, page_id: str, db_session: AsyncSession
    ) -> PrivateNotionPage:
       
        page_id_path = f"{page_id}" # page_id looks like this 7a9abf0d-a066-4466-a565-4e6d7a960a37
        path = f"$.** ? (@.{page_id_path})"

        stmt = text(
            f"""
            SELECT jsonb_path_query(record_map, {path})
            FROM private_notion
            WHERE site_id = {site_uuid}
            """
        )

        result = await db_session.execute(stmt)
        result = result.scalars().first()

        if result:
            return result
        else:
            raise PrivateNotionSiteWasNotFound


所以我想出了以下查询语句,它们使用sqlalchemy“text”方法来接受原始SQL查询,但jsonb_path_query_arrayjsonb_path_query抛出类似的错误; syntax error at or near "$"

page_id_path = f"{page_id}"
path = f"$.** ? (@.{page_id_path})"
stmt = text(
            f"""
            SELECT jsonb_path_query(record_map, {path})
            FROM private_notion
            WHERE site_id = {site_uuid}
            """
        )

Error:

sqlalchemy.exc.ProgrammingError: (sqlalchemy.dialects.postgresql.asyncpg.ProgrammingError) <class 'asyncpg.exceptions.PostgresSyntaxError'>: syntax error at or near "$"
[SQL: 
             SELECT jsonb_path_query(record_map, $.** ? (@.7a9abf0d-a066-4466-a565-4e6d7a960a37))
             FROM private_notion
             WHERE site_id = 26f52d8e-a380-46ab-9131-e6f7f62c528f
]


我后来了解到“$**运算符在SQL查询中无效,相反,您可以使用jsonb_path_query_array函数递归搜索JSONB对象的所有级别。”
显然,我在重构代码后得到了同样的错误。

page_id_path = f"{page_id}"
path = f"$[*] ? (@ like_regex {page_id_path})"
stmt = text(
            f"""
            SELECT jsonb_path_query_array(record_map -> 'block', {path})
            FROM private_notion
            WHERE site_id = {site_uuid}
            """
        )

Error:

sqlalchemy.exc.ProgrammingError: (sqlalchemy.dialects.postgresql.asyncpg.ProgrammingError) <class 'asyncpg.exceptions.PostgresSyntaxError'>: syntax error at or near "$"
[SQL: 
     SELECT jsonb_path_query_array(record_map -> 'block', $[*] ? (@ like_regex 7a9abf0d-a066-4466-a565-4e6d7a960a37))
     FROM private_notion
     WHERE site_id = 26f52d8e-a380-46ab-9131-e6f7f62c528f
]


我的问题有两个方面,这个错误是怎么回事?有没有更好的方法通过JSONB列中的键来检索嵌套对象?谢谢你的时间。

deyfvvtc

deyfvvtc1#

这将提取任何级别的整个对象,其中包含您的目标基于uuid的键:demo at db<>fiddle

SELECT jsonb_path_query(record_map, 
                        'strict $.**?(@.keyvalue().key==$target_id)',
                        jsonb_build_object('target_id',
                                           '7a9abf0d-a066-4466-a565-4e6d7a960a37'))
FROM private_notion
WHERE site_id = '45bf37be-ca0a-45eb-838b-015c7a89d47b';

字符串
| jsonb_path_query|
| --|
| {    "7a9abf0d-a066-4466-a565-4e6d7a960a37": {        "name": "block1",        "child": {            "7a9abf0d-a066-4466-a565-4e6d7a960a37": {                "name": "block2",                "child": {                    "7a9abf0d-a066-4466-a565-4e6d7a960a37": {                        "name": "block3",                        "value": 3                    }                },                "value": 2            },            "7a9abf0d-a066-4466-a565-4e6d7a960a38": {                "name": "block4",                "child": {                    "7a9abf0d-a066-4466-a565-4e6d7a960a39": {                        "name": "block5",                        "child": {                            "7a9abf0d-a066-4466-a565-4e6d7a960a40": {                                "name": "block6",                                "value": 6                            }                        },                        "value": 5                    }                },                "value": 4            }        },        "value": 1    }} |
| {    “7a9abf0d-a066-4466-a565-4e6d7a960a37”:{        “name”:“block2”,        “child”:{            “7a9abf0d-a066-4466-a565-4e6d7a960a37”:{                “name”:“block3”,                “value”:3            }        },        “value”:2    },    “7a9abf0d-a066-4466-a565-4e6d7a960a38”:{        “name”:“block4”,        “child”:{            “7a9abf0d-a066-4466-a565-4e6d7a960a39”:{                “name”:“block5”,                “child”:{                    “7a9abf0d-a066-4466-a565-4e6d7a960a40”:{                        “name”:“block6”,                        “value”:6                    }                },                “value”:5            }        },        “value”:4    }个文件夹|
| {    “7a9abf0d-a066-4466-a565-4e6d7a960a37”:{        “name”:“block3”,        “value”:3    }个文件夹|
注意通过取消嵌套的对象复制:它们既单独出现,也出现在每个匹配的父结构中。

  1. JSONPath表达式需要单引号。这可以消除语法错误:
ERROR:  syntax error at or near "$"
LINE 2:                         $.**.7a9abf0d-a066-4466-a565-4e6d7a9...
                                ^

  1. JSONPath中基于uuid的键需要用双引号括起来。这将消除表达式中的一个问题,这个问题很快就会出现:
ERROR:  trailing junk after numeric literal at or near ".7a" of jsonpath input
LINE 2:                         '$.**.7a9abf0d-a066-4466-a565-4e6d7a...
                                ^


1.使用.**访问器时,默认使用strict模式。
1.可以使用SQLAlchemy JSONPath类型传递表达式。

相关问题