jsonb_array_elements() 失败并显示“错误:无法从对象中提取元素”

bob*_*obb 8 postgresql json json-path jsonb

使用:Postgres 14.2。
目标:获取表中所有不同国家/地区的列表。

该列address是一种JSONB列类型,包含一个结构如下的数组:

{
   "address":[
      {
         "types":["route"],
         "long_name":"20203 113B Ave",
         "short_name":"20203 113B Ave"
      },
      {
         "types":["locality","political"],
         "long_name":"Maple Ridge",
         "short_name":"Maple Ridge"
      },
      {
         "types":["administrative_area_level_3","political"],
         "long_name":"Maple Ridge",
         "short_name":"Maple Ridge"
      },
      {
         "types":["administrative_area_level_2","political"],
         "long_name":"Greater Vancouver",
         "short_name":"Greater Vancouver"
      },
      {
         "types":["administrative_area_level_1","political"],
         "long_name":"British Columbia",
         "short_name":"BC"
      },
      {
         "types":["country","political"],
         "long_name":"Canada",
         "short_name":"CA"
      },
      {
         "types":["postal_code"],
         "long_name":"V2X 0Z1",
         "short_name":"V2X 0Z1"
      }
   ]
}
Run Code Online (Sandbox Code Playgroud)

如何过滤这个对象数组,使其仅返回数组索引(如果包含)的值"long_name"(例如) ?Canadatypes"country"

我正在尝试类似的事情,但显然,我只想返回国家/地区,而不是整个品牌。

SELECT * from brand
where address::text ilike ANY (ARRAY['%country%'::text]);
Run Code Online (Sandbox Code Playgroud)

此查询失败并显示:

{
   "address":[
      {
         "types":["route"],
         "long_name":"20203 113B Ave",
         "short_name":"20203 113B Ave"
      },
      {
         "types":["locality","political"],
         "long_name":"Maple Ridge",
         "short_name":"Maple Ridge"
      },
      {
         "types":["administrative_area_level_3","political"],
         "long_name":"Maple Ridge",
         "short_name":"Maple Ridge"
      },
      {
         "types":["administrative_area_level_2","political"],
         "long_name":"Greater Vancouver",
         "short_name":"Greater Vancouver"
      },
      {
         "types":["administrative_area_level_1","political"],
         "long_name":"British Columbia",
         "short_name":"BC"
      },
      {
         "types":["country","political"],
         "long_name":"Canada",
         "short_name":"CA"
      },
      {
         "types":["postal_code"],
         "long_name":"V2X 0Z1",
         "short_name":"V2X 0Z1"
      }
   ]
}
Run Code Online (Sandbox Code Playgroud)
SELECT * from brand
where exists (
   select from jsonb_array_elements(address) e
   where (e ->> 'types')::text = 'country'
   );
Run Code Online (Sandbox Code Playgroud)

显然,这在 JS 中是微不足道的:

address.filter((part) => part.types.includes('country'))[0].long_name
Run Code Online (Sandbox Code Playgroud)

但我需要我的数据库来处理它。出了什么问题?

Erw*_*ter 12

顾名思义,需要取消嵌套jsonb_array_elements()JSON数组。但是,根据您的错误消息,至少一行包含顶层带有JSON对象的jsonb值。(除数组之外的任何内容都会触发错误。) 测试类型并排除违规行:address
jsonb_typeof()

SELECT DISTINCT x.address ->> 'long_name' AS country_name
FROM  (
    SELECT jsonb_array_elements(b.address) AS address
    FROM   brand b
    WHERE  jsonb_typeof(b.address) = 'array'            -- !!!
   ) x
WHERE  x.address ->> 'types' ILIKE ANY (ARRAY['%country%'::text]);
Run Code Online (Sandbox Code Playgroud)

更短的等价物:

SELECT DISTINCT x.adr->>'long_name' AS country_name
FROM   brand b, jsonb_array_elements(b.address) x(adr)
WHERE  jsonb_typeof(b.address) = 'array'
AND    (x.adr->>'types') ~* 'country';
Run Code Online (Sandbox Code Playgroud)

短的等价物jsonb_path_query()

SELECT DISTINCT jsonb_path_query(address, '$[*] ? (@.types[*] == "country").long_name')
FROM   brand;
Run Code Online (Sandbox Code Playgroud)

Postgres 12 中添加了SQL/JSON的原始功能。一开始有点令人困惑,但功能强大。甚至可以使用索引。看:

我相信您确实想测试该types数组是否与“国家/地区”完全匹配(就像您的 JS 代码所建议的那样),比您的 SQL 查询更严格。

罪魁祸首?

如果您没有预料到该错误,您可能需要仔细查看违规行......

SELECT * FROM brand
WHERE  jsonb_typeof(address) IS DISTINCT FROM 'array';
Run Code Online (Sandbox Code Playgroud)

null价值观很好。其余的则不然。

db<>在这里摆弄