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

时间:2015-08-03 09:26:29

标签: 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,
  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
)

其他表格的重要性较小或不重要,因此不会显示(除非明确要求)。

对于那些使用cubeearthdistance pgsql扩展的表,我创建了以下函数来查找特定点的某个半径内的所有event_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_id)的形式,在所有结果行上也不必重复计数。我希望我可以简单地将total添加为OUT参数,并让函数返回表并用总计数填充OUT变量,但似乎不允许这样做。我总是可以将计数作为一个单独的函数,但我想知道是否有更好的方法来解决这个问题?

1 个答案:

答案 0 :(得分:1)

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

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;

作为LANGUAGE 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);