PostgreSQL jsonb_path_query移除结果而不是返回空值

jyztefdp  于 2023-03-17  发布在  PostgreSQL
关注(0)|答案(4)|浏览(180)

在示例表中:

CREATE TABLE example (
    id   SERIAL PRIMARY KEY,
    data JSON NOT NULL );
INSERT INTO example (id, data) VALUES
  (1, '[{"key": "1", "value": "val1"}, {"key": "2", "value": "val2"}]')
, (2, '[{"key": "1", "value": "val1"}]')
, (3, '[{"key": "1", "value": "val1"}, {"key": "2", "value": "val2"}]');

我想查询key = 2的数据列中的值字段。我当前使用的查询如下:

SELECT id,
       jsonb_path_query(
               TO_JSONB(data),
               '$[*] ? (@.key == "2").value'::JSONPATH
           )::VARCHAR AS values
FROM example

我希望结果是:
| 身份证|价值观|
| - ------|- ------|
| 1个|“val2”|
| 第二章|零|
| 三个|“val2”|
但实际结果是:
| 身份证|价值观|
| - ------|- ------|
| 1个|“val2”|
| 三个|“val2”|
jsonb_path_query()的空输出被省略是否有原因?
我如何让它按照我所期望的方式工作?

ct3nt3jp

ct3nt3jp1#

标量函数 * 处理 * 一个值,并且可以使它无效,但是一个返回集合的函数 * 生成 * 值,所以它可能最终根本不生成值。

SELECT id, jsonb_path_query_first(data::jsonb, '$[*]?(@.key=="2").value')
FROM example;

或者,您可以保留SRF,即返回集合的函数jsonb_path_query()→ setof jsonb,方法是将其 Package 在标量子查询中或替换其隐式内部联接。这两种方法都将使用空值指示缺少生成的值,但后者仍然可以派生并返回多个:demo

SELECT id,(SELECT jsonb_path_query(data::jsonb,'$[*]?(@.key=="2").value')LIMIT 1)
FROM example;

SELECT id, values
FROM example LEFT JOIN jsonb_path_query(data::jsonb, '$[*]?(@.key=="2").value') 
                       AS srf(values) 
             ON true;

第2行没有在该函数中生成任何行,这就是为什么您没有看到它的原因-没有可以包含null的行。选择部分不是SRF的正常位置-当应用到那里时,它最终被下推到from部分,成为inner joined。而inner join具有空集,是空集。结合implicit join行为,这就是为什么您可以阅读:

  • SELECT srf();作为实际的SELECT b.x FROM srf() AS b(x);
  • SELECT a.id, srf() FROM a;表示SELECT a.id, b.x FROM a, srf() AS b(x);
  • SELECT a.id, b.x FROM a, srf(a.v) AS b(x);SELECT a.id, srf(a.v) FROM a;的实际含义是:

SELECT a.id, b.x FROM a INNER JOIN srf(a.v) AS b(x) ON true;
这个INNER JOIN抛出了你的id,它没有生成结果dataLEFT JOIN不会。
另一个带有更明显的generate_series()“给予我这么多行”的插图SRF:demo

create table table1(id smallserial, how_many_to_generate int);
insert into table1(how_many_to_generate) values (1),(0),(2),(0),(1);

--no rows for the 2nd and 4th entry where `how_many_to_generate` is 0
--on the other hand, the 3rd entry requested and received two rows
select id, how_many_to_generate, generate_series(1, how_many_to_generate)
from table1;
-- id | how_many_to_generate | generate_series
------+----------------------+-----------------
--  1 |                    1 |               1
--  3 |                    2 |               1
--  3 |                    2 |               2
--  5 |                    1 |               1
yeotifhr

yeotifhr2#

如果需要路径表达式的结果,则需要jsonb_path_query_first()

SELECT id,
       jsonb_path_query_first(data, '$[*] ? (@.key == "2").value') AS values
FROM example

请注意,这将返回jsonb值。如果需要text值,请用途:

jsonb_path_query_first(data, '$[*] ? (@.key == "2").value') #>> '{}
mzmfm0qo

mzmfm0qo3#

根据PostgreSQL文档,过滤器充当WHERE条件
定义路径时,还可以使用一个或多个筛选表达式,其工作方式类似于SQL中的WHERE子句。筛选表达式以问号开头,并在括号中提供条件:
我使用LATERALLEFT JOIN成功实现了您所期望的目标

SELECT id,
         *
FROM example left join 
LATERAL jsonb_path_query(
        TO_JSONB(data),
        '$[*] ? (@.key == "2").value'::JSONPATH) 
    on true;

结果

id | id |                              data                              | jsonb_path_query 
----+----+----------------------------------------------------------------+------------------
  1 |  1 | [{"key": "1", "value": "val1"}, {"key": "2", "value": "val2"}] | "val2"
  2 |  2 | [{"key": "1", "value": "val1"}]                                | 
  3 |  3 | [{"key": "1", "value": "val1"}, {"key": "2", "value": "val2"}] | "val2"
(3 rows)
rbpvctlc

rbpvctlc4#

关于原问题
...为什么省略jsonb_path_query的空输出?
以及在tim-mccurrach的奖金中添加的问题:
...为什么在空结果的情况下从结果中省略整行。
两者都建立在一个微妙的错误假设上,即会有一个“空输出”或“空结果”。但是,一开始,实际上只有 “无行”。这就造成了所有的差异。相关:

  • 如果未找到记录,则返回值

jsonb_path_query()是一个返回集合的函数(“SRF”)。它不返回1个结果,而是返回0-n个结果行
标准SQL不允许在SELECT列表中使用SRF,只允许在FROM子句中使用SRF。但是Postgres(像其他一些RDBMS一样)允许使用SRF。传统上,这会导致奇怪的情况。最终,Postgres 10解决了确切的行为。请参见:

  • SELECT子句中多个返回集合的函数的预期行为是什么?

返回集合的 single 函数,如示例所示:

SELECT id, jsonb_path_query(...) AS foo FROM example;

实际上几乎1等于:

SELECT id, foo FROM example CROSS JOIN LATERAL jsonb_path_query(...) foo;

(隐式)连接消除了SRF为空(“no row”!)的输入行。
OTOH它在SRF产生多个结果行的地方乘以输入行。
手册中有一个示例在“返回集合的SQL函数”一章中准确地解释了这种情况:
在最后一个SELECT中,请注意,Child2Child3没有输出行出现。这是因为listchildren为这些参数返回了一个空集,所以没有生成结果行。这与我们在使用LATERAL语法时从函数结果的内部连接中获得的行为相同。
PostgreSQL在查询的选择列表中的集合返回函数的行为几乎与在LATERAL FROM-子句项中编写的集合返回函数完全相同。

SELECT x, generate_series(1,5) AS g FROM tab;

几乎相当于

SELECT x, g FROM tab, LATERAL generate_series(1,5) AS g;

粗体强调是我的。

还有更多,包括上面“几乎“1的细节。阅读它。

溶液

直接使用jsonb来修剪投射json时产生的噪波。
最初的问题忽略了JSON数组中的多个对象都可以符合条件的事实。
要像原始查询一样获取**所有匹配项,并保留没有匹配项的输入行,请将隐式CROSS JOIN替换为LEFT JOIN ... ON true。(然后“no row”将转换为null!):

SELECT e.id
     , d.val #>> '{}' AS any_value
FROM   example e
LEFT   JOIN LATERAL jsonb_path_query(e.data, '$[*] ? (@.key == "2").value') d(val) ON true;

参见:

  • PostgreSQL中的横向连接和子查询有什么区别?

仅获取第一个匹配,同时保留所有输入行:

SELECT id
     , jsonb_path_query_first(data, '$[*] ? (@.key == "2").value') #>> '{}' AS first_value
FROM   example;

或者获取所有匹配作为JSON数组,同时保留所有输入行:

SELECT id
     , jsonb_path_query_array(data, '$[*] ? (@.key == "2").value') AS all_values
FROM   example;

fiddle-添加示例行,生成两个匹配项以传达要点。

正确转换为text

我的两个查询都使用相同的“技巧”将JSON值转换为a_horse已经显示的text#>> '{}'。下面是一个出色的解释:
JSON #>>操作符提取指定路径下的JSON子对象text。该路径以Postgres文本数组text[]的形式提供。'{}'是一个空数组。因此,我们得到根对象text

  • 提取JSON字符串内容的替代方法

将JSON数组转换为text[]稍微复杂一些:

SELECT e.id
     , ARRAY(SELECT jsonb_array_elements_text(jsonb_path_query_array(e.data, '$[*] ? (@.key == "2").value'))) AS all_values_txt
     , jsonb_array_to_text_array(jsonb_path_query_array(e.data, '$[*] ? (@.key == "2").value')) AS all_values_txt2
FROM   example e;

这两个表达式都将JSON数组转换为Postgres text[]。第二个表达式将其封装在一个函数中,如下所示:

相关问题