Postgres implicit type inferencing from select statements
-
21-06-2021 - |
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)
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.
- Then you can use
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.