Question

I asked a similar questions yesterday about postgress, and if it could inference the type from the result shape of a select statement.

Today I want to return a resultset from a query, this is the query that I have found to work :

DROP TYPE IF EXISTS topic_result_entry CASCADE;
CREATE TYPE topic_result_entry AS
(
    id            INTEGER,  
    last_post_at  TIMESTAMP WITHOUT TIME ZONE,
    is_sticky     BOOLEAN,
    is_poll       BOOLEAN,
    has_prefix    BOOLEAN,
    prefix        CHARACTER VARYING,
    title         CHARACTER VARYING,
    post_count    INTEGER,
    started_by    INTEGER,
    started_at    TIMESTAMP WITHOUT TIME ZONE
);

CREATE OR REPLACE FUNCTION get_paginated_topics(
    forum_id_ INTEGER, category_id_ INTEGER, page_number_ INTEGER, topics_per_page_ INTEGER)
RETURNS SETOF topic_result_entry as $$
DECLARE
    zero_based_index INTEGER;
    lower_offset INTEGER;
    upper_offset INTEGER;

BEGIN                    
    zero_based_index := page_number_ -1;
    lower_offset := zero_based_index * topics_per_page_;
    upper_offset := ( (topics_per_page_ * page_number_) + 1 );               

    RETURN query
    select id,last_post_at, is_sticky, is_poll, 
           has_prefix, prefix, title,post_count, 
           started_by, started_at 
    from (
        select row_number() OVER(ORDER by last_post_at desc) as rn, * 
        from forum_topics where category_id = category_id_ and forum_id= forum_id_
    ) as foo
    where rn > lower_offset and rn < upper_offset;
END;
$$ LANGUAGE plpgsql;

The shape of the resultset can be infered from the parameter list of the select + the schema definition of the source table.

Q1. Is there some syntactic sugar in 9.1, and if not is this on the roadmap ?

Q2 is there a less verbose way of doing this ?

offtopic

select id,last_post_at, is_sticky, is_poll, 
       has_prefix, prefix, title,post_count, 
       started_by, started_at 
from (
    select row_number() OVER(ORDER by last_post_at desc) as rn, * 
    from forum_topics where category_id = 72
) as foo
where rn>0 and rn<22

QUERY PLAN  
Subquery Scan on foo  (cost=0.00..492.20 rows=28 width=60)  
  Filter: ((foo.rn > 0) AND (foo.rn < 22))  
  ->  WindowAgg  (cost=0.00..409.42 rows=5519 width=156)    
        ->  Index Scan using forum_topics_last_post_at_idx1 on forum_topics  (cost=0.00..326.63 rows=5519 width=156)    
              Filter: (category_id = 72)
Was it helpful?

Solution

Syntactical sugar? I got some for you.

If ...

The shape of the resultset can be inferred from [...] the schema definition of the source table

... then you can much simplify. In PostgreSQL a table definition automatically defines a type of the same name.


CREATE OR REPLACE FUNCTION get_paginated_topics(
    _forum_id int, _category_id int, _page_number int, _topics_per_page int)
RETURNS SETOF forum_topics AS
$BODY$
DECLARE
   _lower_offset  int := (_page_number - 1) * _topics_per_page;
   _upper_offset  int := _topics_per_page * _page_number + 1;
BEGIN

   RETURN QUERY
   SELECT *
   FROM   forum_topics f
   WHERE  f.category_id = category_id_
   AND    f.forum_id = forum_id_
   ORDER  BY f.last_post_at DESC
   LIMIT  _lower_offset
   OFFSET _upper_offset;

END;
$BODY$ LANGUAGE plpgsql;

Other details:

  • Use LIMIT / OFFSET as @user272735 pointed out.

    • Then you can use SELECT *, because you got rid of the extra column.
  • You can assign variables at declaration time.

  • Short type names.

  • Removed redundant parenthesis.

  • I added a table alias and table qualification to the query, which is not necessary in this case, but good practice to avoid naming conflicts in plpgsql functions.

OTHER TIPS

PostgreSQL provides LIMIT and OFFSET that can be used to limit a result set. E.g.

select id, last_post_at, is_sticky, is_poll, 
       has_prefix, prefix, title, post_count, 
       started_by, started_at 
  from forum_topics
 where category_id = category_id_ and forum_id = forum_id_
 order by id
 limit topics_per_page_
offset (page_number_ - 1) * topics_per_page_
;

I am not an export in Postgres-specific features, but pagination is generally a little tricky in SQL. In ANSI SQL, I would typically do a SELECT COUNT(*) FROM ... to determine the size of my result set and then use a cursor to get the pages a user wants. Your query computes the size of the result set for each page, which could become inefficient. Also keep in mind that the size of the result set can change as new records are added, so pages may not properly align with each other.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top