为 DISTINCT 调用为 PostgreSQL 类型(点)创建自定义 "equality operator"

Creating custom "equality operator" for PostgreSQL type (point) for DISTINCT calls

在我的一个 table 中,我有一个定义为 PostgreSQL 类型 point. I use this for the earthdistance 模块的列——具体来说,<@> 距离运算符。 (是的,我知道 PostGIS,但它比我的需要复杂得多,它只是给出一个 table 和 lat/long 对,按距离排序 table提供 lat/long.)

但是,point 似乎没有实现相等性,因此对 table 的任何 DISTINCT 调用(如 SELECT DISTINCT * FROM mytable)都会导致以下错误:

ERROR: could not identify an equality operator for type point

虽然通常不建议修补内置类型,但在这种情况下我不介意这样做,我尝试为 point 创建自己的 = 运算符:

CREATE OR REPLACE FUNCTION compare_points_equality(point1 POINT, point2 POINT)
  RETURNS BOOLEAN AS $$
  SELECT point1[0] = point2[0] AND point1[1] = point1[1];
$$ LANGUAGE SQL IMMUTABLE;

CREATE OPERATOR = (
  LEFTARG = POINT,
  RIGHTARG = POINT,
  PROCEDURE = compare_points_equality,
  COMMUTATOR = =,
  NEGATOR = !=,
  HASHES,
  MERGES
);

但是即使在创建这个之后,我也会得到同样的错误。如果不创建 =,我应该如何创建 "equality operator"?

到select 不同的值 Postgres 必须能够对列进行排序。 您需要创建一个完整的 btree operator class for type point, i.e. five operators (<, <=, =, >=, >) and a function comparing two points and returning integer, as it is described in the documentation.

对于运算符 = 您可以使用现有函数 point_eq(point, point):

create operator = (leftarg = point, rightarg = point, procedure = point_eq, commutator = =);

运算符的示例定义 <:

create function point_lt(point, point)
returns boolean language sql immutable as $$
    select [0] < [0] or [0] = [0] and [1] < [1]
$$;

create operator < (leftarg = point, rightarg = point, procedure = point_lt, commutator = >);

以类似的方式定义运算符 <==>>。拥有所有五个运算符,创建一个函数:

create function btpointcmp(point, point)
returns integer language sql immutable as $$
    select case 
        when  =  then 0
        when  <  then -1
        else 1
    end
$$;

最后:

create operator class point_ops
    default for type point using btree as
        operator 1 <,
        operator 2 <=,
        operator 3 =,
        operator 4 >=,
        operator 5 >,
        function 1 btpointcmp(point, point);

使用 class point_ops 定义,您可以 select 不同的点值并按点类型的列对行进行排序,例如:

with q(p) as (
    values 
        ('(1,1)'::point),
        ('(1,2)'::point),
        ('(2,1)'::point),
        ('(1,1)'::point))
select distinct *
from q
order by 1 desc;

   p   
-------
 (2,1)
 (1,2)
 (1,1)
(3 rows)    

您还可以在点列上创建(唯一)索引。


更新。

Where the function point_eq(point, point) comes from? Why does it already exist?

Postgres 有 2800 多个辅助函数,支持运算符、索引、标准函数等。您可以通过查询 pg_proc, 列出它们,例如:

select format('%s(%s)', proname, pg_get_function_arguments(oid))
from pg_proc
where pronamespace::regnamespace = 'pg_catalog'
and proname like 'point%'

函数point_eq(point, point)用于实现一些geometric functions and operators.