如何在 PostgreSQL 9.4+ 中将简单的 json(b) int 数组转换为整数[]

Dac*_*d3r 5 sql arrays postgresql json types

我有一个来自 json 对象的数组:[1, 9, 12]

由于它使用方括号表示法,因为它是直接从 json 对象获取的,所以我无法将其转换为,::integer[]当我尝试使用时,array_agg(jsonb_array_elements(simpleintarray))我收到一条错误消息,说我需要按 id 进行分组,但由于数组不是对象(键/值) )对,但只是简单的整数,我不知道如何以相当有效的方式实现这一点。

从 json 返回上述简单 int 数组的查询是:

SELECT node.*, elem->'permissions' AS group_node_permissions
    FROM node
    LEFT OUTER JOIN
    jsonb_array_elements(my_user_group.node_permissions) elem
    ON elem->>'id' = node.id::text
    ORDER BY node.id
Run Code Online (Sandbox Code Playgroud)

elem->'permissions'理想情况下应该以 Postgres 数组的形式返回{},以便我稍后可以在其上使用该ANY(intarray)函数。

我想避免做多余的低效解决方法,例如用大括号替换方括号,然后转换elem->'permissions'->>整数数组,将其转换为字符串,尽管这可能会起作用。

在伪代码中,我真正需要的是能够得到相同的结果:

SELECT node.*, elem->'permissions'**::integer[]** AS group_node_permissions,

...但是当然由于json 数组与 PostgreSQL 数组格式之间的[]差异{},这会导致错误。

这是我当前的(非常丑陋的解决方案,但有效):

SELECT node.*, replace(replace(elem->>'permissions', '[', '{'),']','}')::integer[] AS group_node_permissions
Run Code Online (Sandbox Code Playgroud)

它将原始的 (jsonb) 转换为(integer[])[1, 9, 12]的形式{1,9,12}

对此有更好的解决方案吗?

聚苯乙烯

是否值得从 json(b) 转换为 int 数组 ([]),您可以在其中获取数组元素jsonarray @> '12'到 Postgresinteger[]数组,您可以在其中使用12 = ANY(intarray). 有谁知道哪个性能更好并且应该更好地扩展?现在我们可以将数组放入jsonb数据类型的列中,这是否被认为是比例如更好的方式。数据integer[]类型?


扩展信息(根据埃尔文的要求):

SELECT DISTINCT ON (my_node.id) my_node.*
FROM user_group AS my_user_group,
LATERAL
(
    SELECT node.*, elem->'permissions' AS user_group_node_permissions
    FROM node
    LEFT OUTER JOIN
    jsonb_array_elements(my_user_group.node_permissions) elem
    ON elem->>'id' = node.id::text
    ORDER BY node.id
)my_node
WHERE (my_user_group.id = ANY('{2,3}')) --try also with just: ANY('{3}')) to see node 3 is excluded
AND (user_group_node_permissions @> '12' OR (user_group_node_permissions IS NULL AND 12 = ANY(my_user_group.default_node_permissions)));
Run Code Online (Sandbox Code Playgroud)

数据定义语言:

CREATE TABLE node
(
  id bigserial NOT NULL,
  path ltree,
  name character varying(255),
  node_type smallint NOT NULL,
  created_by bigint NOT NULL,
  created_date timestamp without time zone NOT NULL DEFAULT now(),
  parent_id bigint,
  CONSTRAINT node_pkey PRIMARY KEY (id)
)
WITH (
  OIDS=FALSE
);

CREATE TABLE user_group
(
  id serial NOT NULL,
  name character varying,
  alias character varying,
  node_permissions jsonb,
  section_ids jsonb,
  default_node_permissions jsonb
)
WITH (
  OIDS=FALSE
);
Run Code Online (Sandbox Code Playgroud)

数据标记语言:

节点:

INSERT INTO node VALUES (1, '1', 'root', 5, 1, '2014-10-22 16:51:00.215', NULL);

INSERT INTO node VALUES (2, '1.2', 'Home', 1, 1, '2014-10-22 16:51:00.215', 1);
INSERT INTO node VALUES (3, '1.2.3', 'Sample Page', 1, 1, '2014-10-22 16:51:00.215', 2);
INSERT INTO node VALUES (4, '1.2.3.4', 'Child Page Level 1', 1, 1, '2014-10-26 23:19:44.735', 3);
INSERT INTO node VALUES (5, '1.2.3.4.5', 'Child Page Level 2', 1, 1, '2014-10-26 23:19:44.735', 4);

INSERT INTO node VALUES (6, '1.2.6', 'Test Page', 1, 1, '2014-12-01 11:45:16.186', 2);
INSERT INTO node VALUES (7, '1.2.7', 'Login', 1, 1, '2014-12-01 11:54:10.208', 2);
INSERT INTO node VALUES (8, '1.2.7.8', 'MySubPage', 1, 1, '2014-12-01 12:02:54.252', 7);
INSERT INTO node VALUES (9, '1.2.9', 'Yet another test page', 1, 1, '2014-12-01 12:07:29.999', 2);

INSERT INTO node VALUES (10, '1.2.10', 'Testpage 2', 1, 1, '2014-12-02 01:43:33.233', 2);
INSERT INTO node VALUES (11, '1.2.10.11', 'Test page 2 child', 1, 1, '2014-12-02 01:45:49.78', 10);
Run Code Online (Sandbox Code Playgroud)

团体:

INSERT INTO user_group VALUES (2, 'Editor', 'editor', NULL, NULL, '{1,2,3,4,5,7,9,10,12}');
INSERT INTO user_group VALUES (1, 'Administrator', 'administrator', NULL, NULL, '{1,2,3,4,5,6,7,8,9,10,11,12}');
INSERT INTO user_group VALUES (3, 'Writer', 'writer', '[{"id": 3, "permissions": [1, 9]}, {"id": 4, "permissions": [1, 9, 12]}]', NULL, '{1,3,9,12}');
Run Code Online (Sandbox Code Playgroud)

简短的介绍:

基本上我在这里做的是这样的:

  • 用户可以有多个组(作为整数[]数据类型或jsonb数组[] - 尚未决定,但考虑到Erwin的答案,整数可能是最好的,因为它不应该容纳一个大数组。
  • 每个组都可以分配对特定节点的特定访问权限*(见下图,解释了外部左连接),从而推翻了组默认的全局权限(权限“12”是浏览节点的能力,从而将其返回查询)
  • 由于“writer”组确实具有“12”(浏览)权限,但是 id 为 3 的节点的节点权限没有“12”权限,因此仅具有“writer”组的用户将无法获取 id 3 的节点在选择查询返回的结果中。然而,如果用户还有另一个组,并且不排除该节点,那么该节点当然会被返回,因为更“强大”的组会取代“较弱”的组。

性能缓慢 - 可以优化吗?

解释分析查询性能

文本输出

(可以在浏览器中放大图片)

与上面相比,一个简单的 SELECT * FROM 节点执行时间为 0.046ms(再次使用 EXPLAIN ANALYZE 测量)

如果您仍然需要更多信息,请随时询问。

Erw*_*ter 4

显然,您有一个嵌套在外部 JSON 数组内的 JSON 数组:

SELECT n.*, array_agg(p)::int[] AS group_node_permissions
FROM   my_user_group u
     , jsonb_array_elements(u.node_permissions) elem
JOIN   node n ON n.id = (elem->>'id')::int
     , jsonb_array_elements_text(elem->'permissions') p
GROUP  BY n.id;  -- id being the PK
Run Code Online (Sandbox Code Playgroud)
  • 这是假设 中没有空的阿拉斯permissions。否则你需要LEFT JOIN LATERAL ... ON TRUE

  • 这应该保留 JSON 数组的原始顺序,但不能保证。如果您需要确定,请使用WITH ORDINALITY.

  • LEFT [OUTER] JOIN这是毫无意义的,因为后面对左表列的谓词[INNER] JOIN无论如何都会强制行为。

dba.SE 上的相关答案有更多详细信息和解释:

根据用例的详细信息,使用 GIN 索引支持查询可能是一个好主意:

至于你的PS,就看全图了。jsonb除了所有其他考虑因素之外,Postgres 数组通常比保存 JSON 数组更小且更快。使用 GIN 索引测试元素是否存在可以非常快:

jsonarray @> '12'
intarray @> '{12}'
Run Code Online (Sandbox Code Playgroud)

请特别注意,GIN 索引不支持12 = ANY(intarray)变体。手册中有详细说明。

  • “ORDER BY n.id”是拼写错误吗?我想你可能想写“GROUP BY n.id”。-- 数组构造函数(例如`array(select jsonb_array_elements_text(elem->'permissions')::int)`)应该比最后一个连接+聚合组合更快(并且它还解决了`permissions中的空数组` 问题) (4认同)