web-dev-qa-db-ja.com

等しいためのPostgreSQLカスタム演算子

PostgreSQLでカスタムの等値演算子を作成します。これは、UNION型の_GROUP BY_、jsonおよび_DISTINCT [ON]_で使用できます(好奇心のために) 、実際の実装ではありません-そのため、次の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$;
_

カスタムの等値演算子は次のようになります。

_DROP OPERATOR IF EXISTS = (json, json) CASCADE;

CREATE OPERATOR = (
  PROCEDURE   = json_equals,
  LEFTARG     = json,
  RIGHTARG    = json,
  COMMUTATOR  = =,
  RESTRICT    = eqsel,
  JOIN        = eqjoinsel,
  HASHES,
  MERGES
);
_

しかし、演算子クラスを作成するのに苦労しています。私は試した:

_DROP OPERATOR CLASS IF EXISTS json_ops USING hash CASCADE;

CREATE OPERATOR CLASS json_ops
  DEFAULT
  FOR TYPE json
  USING hash AS
  OPERATOR 1 =;
_

また:

_DROP OPERATOR CLASS IF EXISTS json_ops USING btree CASCADE;

CREATE OPERATOR CLASS json_ops
  DEFAULT
  FOR TYPE json
  USING btree AS
  OPERATOR 3 =;
_

ただし、サンプルクエリは実行できません。

_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.
_

[〜#〜]編集[〜#〜]

結局のところ、問題はDDLステートメントが同じトランザクションで実行されることでした(または、毎日の再起動が役立ちました。 )。

したがって、新しいエラーメッセージは次のとおりです。

_ERROR: could not find hash function for hash operator 33794
SQL state: XX000
_

これは、json_hash(json)関数をクラスに追加することで簡単に解決できます。

_CREATE OPERATOR CLASS json_ops
  DEFAULT
  FOR TYPE json
  USING hash AS
  OPERATOR 1 =,
  FUNCTION 1 json_hash(json);
_

jsonのソートはそれほど簡単ではないため、ありがたいことにbtree opclassを作成する必要はありませんでした。誰かが興味を持っている場合、これは私の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$;
_
6
pozs

あなたの答え/編集は私のために働いた。変更する必要があるのは、次の点のみです。

...
WHEN '[' THEN COALESCE((SELECT ...), 3)
WHEN '{' THEN COALESCE((SELECT ...), 4)

空の配列と空のオブジェクトを処理できるようにします。

2
leafs242