在 Postgres 中使用路径子集进行查询
Query using subset of path in Postgres
鉴于此 table:
id | points (path) |
----+------------------------------------------+
1 | ((1,2),(3,4),(5,6),(7,8)) |
是否可以使用单个几何运算符和路径参数(包含路径的顺序子集)实现以下目标,如((3,4),(5,6))
?
select * from things where points @> '(3,4)' and points @> '(5,6)';
也许只是将其转换为字符串并使用 LIKE
进行匹配(您需要 double 因为路径已关闭):
select points::text from things
where (points::text || points::text) like '%(3,4),(5,6)%';
如果你有很多东西值得为路径建立索引,这将在like查询中使用(你需要trgm extension)
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE INDEX thing_text_paths ON things USING gin ( (points::text || points::text) gin_trgm_ops);
你可以通过运行看到
SET enable_seqscan = OFF;
EXPLAIN select points::text from things
where (points::text || points::text) like '%(3,4),(5,6)%';
Postgres 中没有可以满足这些假设的本地几何运算符。您可以使用点数组而不是路径,但是它需要一些准备工作并以超级用户身份访问。
您需要为类型 point
创建一个运算符 class 以允许比较此类型的值。 post 中描述了整个过程: Here you have a copy of my code I needed in one of my projects: Postgres class point_ops.
数组的解决方案需要一个简单的函数,您可以使用它来代替运算符:
create or replace function is_subpath(point[], point[])
returns boolean language plpgsql as $$
begin
for p in 1..cardinality() loop
if [p] = [1] then
for s in 2..cardinality() loop
p:= p+ 1;
if [p] <> [s] then
return false;
end if;
return true;
end loop;
end if;
end loop;
return false;
end $$;
drop table if exists things;
create table things(
id int,
points point[]
);
insert into things values
(1, '{"(3,4)","(1,2)","(5,6)","(7,8)"}'),
(2, '{"(1,2)","(3,4)","(5,6)","(7,8)"}');
select *
from things
where is_subpath(points, '{"(3,4)","(5,6)"}'::point[]);
id | points
----+-----------------------------------
2 | {"(1,2)","(3,4)","(5,6)","(7,8)"}
(1 row)
我认为问题是由您的数据库设计造成的。您应该使用两个 table 而不是一个。
如果你遵循第三范式。您的设计将与 child table 具有一对多关系。
child table 将有四个记录。
如果您遵循 Sql 的工作规范,这将不是问题。当您在同一个字段中放置多个数据值时,Sql 无法很好地处理,它限制了您使用杂乱无章的解决方案。
鉴于此 table:
id | points (path) |
----+------------------------------------------+
1 | ((1,2),(3,4),(5,6),(7,8)) |
是否可以使用单个几何运算符和路径参数(包含路径的顺序子集)实现以下目标,如((3,4),(5,6))
?
select * from things where points @> '(3,4)' and points @> '(5,6)';
也许只是将其转换为字符串并使用 LIKE
进行匹配(您需要 double 因为路径已关闭):
select points::text from things
where (points::text || points::text) like '%(3,4),(5,6)%';
如果你有很多东西值得为路径建立索引,这将在like查询中使用(你需要trgm extension)
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE INDEX thing_text_paths ON things USING gin ( (points::text || points::text) gin_trgm_ops);
你可以通过运行看到
SET enable_seqscan = OFF;
EXPLAIN select points::text from things
where (points::text || points::text) like '%(3,4),(5,6)%';
Postgres 中没有可以满足这些假设的本地几何运算符。您可以使用点数组而不是路径,但是它需要一些准备工作并以超级用户身份访问。
您需要为类型 point
创建一个运算符 class 以允许比较此类型的值。 post 中描述了整个过程:
数组的解决方案需要一个简单的函数,您可以使用它来代替运算符:
create or replace function is_subpath(point[], point[])
returns boolean language plpgsql as $$
begin
for p in 1..cardinality() loop
if [p] = [1] then
for s in 2..cardinality() loop
p:= p+ 1;
if [p] <> [s] then
return false;
end if;
return true;
end loop;
end if;
end loop;
return false;
end $$;
drop table if exists things;
create table things(
id int,
points point[]
);
insert into things values
(1, '{"(3,4)","(1,2)","(5,6)","(7,8)"}'),
(2, '{"(1,2)","(3,4)","(5,6)","(7,8)"}');
select *
from things
where is_subpath(points, '{"(3,4)","(5,6)"}'::point[]);
id | points
----+-----------------------------------
2 | {"(1,2)","(3,4)","(5,6)","(7,8)"}
(1 row)
我认为问题是由您的数据库设计造成的。您应该使用两个 table 而不是一个。
如果你遵循第三范式。您的设计将与 child table 具有一对多关系。
child table 将有四个记录。
如果您遵循 Sql 的工作规范,这将不是问题。当您在同一个字段中放置多个数据值时,Sql 无法很好地处理,它限制了您使用杂乱无章的解决方案。