如何在PostgreSQL中创建可分页函数

如何在PostgreSQL中创建可分页函数,postgresql,Postgresql,我有两张表:事件表和位置表 CREATE TABLE location ( location_id bigint NOT NULL, version bigint NOT NULL, active boolean NOT NULL, created timestamp without time zone NOT NULL, latitude double precision NOT NULL, longitude double precision NOT NULL,

我有两张表:事件表和位置表

CREATE TABLE location
(
  location_id bigint NOT NULL,
  version bigint NOT NULL,
  active boolean NOT NULL,
  created timestamp without time zone NOT NULL,
  latitude double precision NOT NULL,
  longitude double precision NOT NULL,
  updated timestamp without time zone,
  CONSTRAINT location_pkey PRIMARY KEY (location_id)
)

CREATE TABLE event
(
  event_id bigint NOT NULL,
  version bigint NOT NULL,
  active boolean NOT NULL,
  created timestamp without time zone NOT NULL,
  end_date date,
  entry_fee numeric(19,2),
  location_id bigint NOT NULL,
  organizer_id bigint NOT NULL,
  start_date date NOT NULL,
  timetable_id bigint,
  updated timestamp without time zone,
  CONSTRAINT event_pkey PRIMARY KEY (event_id),
  CONSTRAINT fk_organizer FOREIGN KEY (organizer_id)
      REFERENCES "user" (user_id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION,
  CONSTRAINT fk_timetable FOREIGN KEY (timetable_id)
      REFERENCES timetable (timetable_id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION,
  CONSTRAINT fk_location FOREIGN KEY (location_id)
      REFERENCES location (location_id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION
)
其他表的重要性较低或不重要,因此不会显示它们(除非明确要求)

对于这些表,我使用
cube
earthdistance
pgsql扩展创建了以下函数,用于查找特定点的特定半径内的所有事件ID

CREATE OR REPLACE FUNCTION eventidswithinradius(
    lat double precision,
    lng double precision,
    radius double precision)
  RETURNS SETOF bigint AS
$BODY$
BEGIN
    RETURN QUERY SELECT event.event_id 
    FROM event 
    INNER JOIN location ON location.location_id = event.location_id
    WHERE earth_box( ll_to_earth(lat, lng), radius) @> ll_to_earth(location.latitude, location.longitude);
END;
$BODY$
这一点与预期相符。现在,我希望使它可以分页,并且一直在研究如何获取所有必要的值(包含分页内容和总计数的表)

到目前为止,我已经创建了以下内容:

CREATE OR REPLACE FUNCTION pagedeventidswithinradius(
    IN lat double precision,
    IN lng double precision,
    IN radius double precision,
    IN page_size integer,
    IN page_offset integer)
  RETURNS TABLE( total_size integer , event_id bigint ) AS
$BODY$
DECLARE total integer;
BEGIN   
    SELECT COUNT(location.*) INTO total FROM location WHERE earth_box( ll_to_earth(lat, lng), radius) @> ll_to_earth(location.latitude, location.longitude);

    RETURN QUERY SELECT total, event.event_id as event_id
    FROM event
    INNER JOIN location ON location.location_id = event.location_id
    WHERE earth_box( ll_to_earth(lat, lng), radius) @> ll_to_earth(location.latitude, location.longitude)
    ORDER BY event_id   
    LIMIT page_size OFFSET page_offset;
END;
$BODY$
这里count只被调用一次并存储在一个变量中,因为我假设如果我将
count
放入返回查询本身,它将为每一行调用。
这种方法可以工作,但很难在后端进行解析,因为结果的形式是
(count,event\u id)
,而且count在所有结果行上都是不必要的重复。我希望我可以简单地添加
total
作为
OUT
参数,让函数返回表并用total count填充OUT变量,但这似乎是不允许的。我总是可以将计数作为一个单独的函数,但我想知道是否有更好的方法来解决这个问题

不,没有更好的选择了。您需要两种不同类型的数量,因此需要两个查询。但是,您可以改进您的功能:

CREATE FUNCTION eventidswithinradius(lat float8, long float8, radius float8) RETURNS SETOF bigint AS $BODY$
  SELECT event.event_id 
  FROM event 
  JOIN location l USING (location_id)
  WHERE earth_box(ll_to_earth(lat, lng), radius) @> ll_to_earth(l.latitude, l.longitude);
$BODY$ LANGUAGE sql STRICT;
作为
语言sql
函数,它比作为PL/pgSQL函数更高效,而且您可以在外部进行分页:

SELECT *
FROM eventidswithinradius(121.056, 14.582, 3000)
LIMIT 15 OFFSET 1;
在内部,查询计划器将解析对其基础查询的函数调用,并将分页直接应用于该级别

用明显的数字得出总数:

SELECT count(id)
FROM eventidswithinradius(121.056, 14.582, 3000);

你为什么要翻页?由于您只返回一个
bigint
每行的资源消耗在这里几乎不成问题。是的,但是这些bigint表示将使用ORM获取完整数据的id。ORM无法调用函数/sp,因此这是一种解决方法,可以获取分页id,然后使用ORM正确映射这些项目,尽管函数缺少它的
返回
声明。感谢您向我展示了
LANGUAGE sql
选项,以及使用
而不是
进行联接。