poz*_*ozs 6 postgresql postgresql-9.3 json operator
我想建立PostgreSQL中的自定义相等操作符,可以在使用GROUP BY,UNION并DISTINCT [ON]为json类型(只是为了好奇的缘故,而不是真实世界中的实现-这就是为什么即将到来的jsonb类型是不是我要找的)。
我可以编写一个函数来测试json相等性:
CREATE OR REPLACE FUNCTION json_equals(json, json)
RETURNS BOOLEAN
LANGUAGE sql
IMMUTABLE
STRICT
AS $function$
SELECT CASE f1
WHEN '{' THEN -- object
CASE f2
WHEN '{' THEN (
SELECT COALESCE(bool_and(k1 IS NOT NULL AND k2 IS NOT NULL AND json_equals(v1, v2)), TRUE)
FROM (SELECT DISTINCT ON (k1) * FROM json_each($1) AS j1(k1, v1) ORDER BY k1, row_number() OVER () DESC) AS j1
FULL JOIN (SELECT DISTINCT ON (k2) * FROM json_each($2) AS j2(k2, v2) ORDER BY k2, row_number() OVER () DESC) AS j2 ON j1.k1 = j2.k2
)
ELSE FALSE
END
WHEN '[' THEN -- array
CASE f2
WHEN '[' THEN (
SELECT COALESCE(bool_and(r1 IS NOT NULL AND r2 IS NOT NULL AND json_equals(e1, e2)), TRUE)
FROM (SELECT e1, row_number() OVER () AS r1 FROM json_array_elements($1) AS e1) AS e1
FULL JOIN (SELECT e2, row_number() OVER () AS r2 FROM json_array_elements($2) AS e2) AS e2 ON e1.r1 = e2.r2
)
ELSE FALSE
END
WHEN 'n' THEN -- null
CASE f2
WHEN 'n' THEN TRUE
ELSE FALSE
END
WHEN 't' THEN -- true
CASE f2
WHEN 't' THEN TRUE
ELSE FALSE
END
WHEN 'f' THEN -- false
CASE f2
WHEN 'f' THEN TRUE
ELSE FALSE
END
WHEN '"' THEN -- string
CASE f2
WHEN '"' THEN (CAST('[' || j1 || ']' AS json) ->> 0)
= (CAST('[' || j2 || ']' AS json) ->> 0)
ELSE FALSE
END
ELSE -- number
CASE f2
WHEN '{' THEN FALSE
WHEN '[' THEN FALSE
WHEN 'n' THEN FALSE
WHEN 't' THEN FALSE
WHEN 'f' THEN FALSE
WHEN '"' THEN FALSE
ELSE CAST(CAST('[' || j1 || ']' AS json) ->> 0 AS NUMERIC)
= CAST(CAST('[' || j2 || ']' AS json) ->> 0 AS NUMERIC)
END
END
FROM (
SELECT TRIM(LEADING E'\x20\x09\x0A\x0D' FROM CAST($1 AS text)) AS j1,
TRIM(LEADING E'\x20\x09\x0A\x0D' FROM CAST($2 AS text)) AS j2
) AS jsons,
LATERAL (
SELECT SUBSTRING(j1 FROM 1 FOR 1) AS f1,
SUBSTRING(j2 FROM 1 FOR 1) AS f2
) AS firsts
$function$;
Run Code Online (Sandbox Code Playgroud)
自定义相等运算符可能如下所示:
DROP OPERATOR IF EXISTS = (json, json) CASCADE;
CREATE OPERATOR = (
PROCEDURE = json_equals,
LEFTARG = json,
RIGHTARG = json,
COMMUTATOR = =,
RESTRICT = eqsel,
JOIN = eqjoinsel,
HASHES,
MERGES
);
Run Code Online (Sandbox Code Playgroud)
但是我在创建运算符类时遇到了麻烦。我试过:
DROP OPERATOR CLASS IF EXISTS json_ops USING hash CASCADE;
CREATE OPERATOR CLASS json_ops
DEFAULT
FOR TYPE json
USING hash AS
OPERATOR 1 =;
Run Code Online (Sandbox Code Playgroud)
还:
DROP OPERATOR CLASS IF EXISTS json_ops USING btree CASCADE;
CREATE OPERATOR CLASS json_ops
DEFAULT
FOR TYPE json
USING btree AS
OPERATOR 3 =;
Run Code Online (Sandbox Code Playgroud)
但是,示例查询仍然无法运行:
SELECT *
FROM (VALUES (json '{"a":1,"a":2}'), ('{"a":2}')) v(a)
GROUP BY a;
-- ERROR: could not implement GROUP BY
-- SQL state: 0A000
-- DETAIL: Some of the datatypes only support hashing, while others only support sorting.
Run Code Online (Sandbox Code Playgroud)
编辑:
事实证明,问题在于 DDL 语句和示例查询在同一个事务中运行(或者,每天重新启动有帮助,我对此表示怀疑)。
所以,新的错误信息是:
ERROR: could not find hash function for hash operator 33794
SQL state: XX000
Run Code Online (Sandbox Code Playgroud)
这可以通过json_hash(json)向类添加函数来轻松解决:
CREATE OPERATOR CLASS json_ops
DEFAULT
FOR TYPE json
USING hash AS
OPERATOR 1 =,
FUNCTION 1 json_hash(json);
Run Code Online (Sandbox Code Playgroud)
谢天谢地,没有必要创建一个 btree opclass,因为排序json并不是那么简单。这是我的 json 散列函数,如果有人感兴趣:
CREATE OR REPLACE FUNCTION json_hash(json)
RETURNS INTEGER
LANGUAGE sql
IMMUTABLE
STRICT
AS $function$
SELECT CASE f
WHEN 'n' THEN 0
WHEN 't' THEN 1
WHEN 'f' THEN 2
WHEN '"' THEN hashtext(CAST('[' || j || ']' AS json) ->> 0)
WHEN '[' THEN (SELECT bit_or(json_hash(e)) FROM json_array_elements($1) AS e)
WHEN '{' THEN (SELECT bit_and(hashtext(k) | json_hash(v)) FROM (SELECT DISTINCT ON (k) * FROM json_each($1) AS j(k, v) ORDER BY k, row_number() OVER () DESC) AS pairs)
ELSE hash_numeric(CAST(CAST('[' || j || ']' AS json) ->> 0 AS NUMERIC))
END
FROM (
SELECT TRIM(LEADING E'\x20\x09\x0A\x0D' FROM CAST($1 AS text)) AS j
) AS jsons,
LATERAL (
SELECT SUBSTRING(j FROM 1 FOR 1) AS f
) AS firsts
$function$;
Run Code Online (Sandbox Code Playgroud)
小智 2
你的回答/编辑对我有用。我唯一需要改变的是:
...
WHEN '[' THEN COALESCE((SELECT ...), 3)
WHEN '{' THEN COALESCE((SELECT ...), 4)
Run Code Online (Sandbox Code Playgroud)
能够处理空数组和空对象。