How to create a pageable function in PostgreSQL - postgresql

I have two tables: event and location
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
)
Other tables are of lesser to no importance so they will not be shown (unless explicitly asked).
And for those tables, using cube and earthdistance pgsql extensions I've created the following function for finding all event_ids within a certain radius of a certain point.
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$
And this works as expected. Now I wish to make it pageable, and am stuck on how to get all the necessary values (the table with paged contents and total count).
So far I've created this:
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$
Here count is called only once and stored in a variable since I assumed that if I placed COUNT into the return query itself it would be called for each row.
This kind of works, but it is difficult to parse on the back-end since the result is in the form of (count, event_id), also count is needlessly repeated over all result rows. I was hoping I could simply add total as an OUT param and have the function return the table and fill the OUT variable with total count, however it seems this is not allowed. I can always have the count be a separate function but I was wondering if there is a better way to approach this issue?

No, there isn't really a better option. You want two different types of quantities so you need two queries. You can improve upon your function, however:
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;
As a LANGUAGE sql function it is more efficient than as a PL/pgSQL function, plus you can do your paging on the outside:
SELECT *
FROM eventidswithinradius(121.056, 14.582, 3000)
LIMIT 15 OFFSET 1;
Internally the query planner will resolve the function call to its underlying query and apply the paging directly to that level.
Get the total with the obvious:
SELECT count(id)
FROM eventidswithinradius(121.056, 14.582, 3000);

Related

postgis function correctly returns table with geom but not picked up by pg_featureserv

Using pg_featureserv
wrote a function that returns a table + geometry
CREATE OR REPLACE FUNCTION public.nearest_retail(lon float, lat float)
RETURNS table(id int,
dba_name varchar,
entity_name varchar,
category varchar,
address varchar,
notes varchar,
distance float,
geom geometry
)
AS $$
select t.uid,dba_name,entity_name,md2.category,address,notes,distance,st_union(shape,geom) geom
from
( select distinct on(category) category,uid,
st_distance(st_transform(geom,2263),st_transform(st_setsrid(st_makepoint(lon, lat),4326),2263)) distance,
st_shortestline(geom,st_setsrid(st_makepoint(lon, lat),4326)) shape
from merged_datasets md
where st_dwithin(st_transform(st_setsrid(st_makepoint(lon, lat),4326),2263),st_transform(geom,2263),5280)
and category is not null
)t join merged_datasets md2 using(uid);
$$ LANGUAGE sql;
calling the function in the DB itself it works as its supposed to, however I cannot get it to show up in the pg_featureserv
The doc says
Because there are usually many functions in a Postgres database, the service only publishes functions defined in the postgisftw schema.

postgresql insert with integer PK nextval

So, I have the following table definition with the id as integer PK using a nextval seq as shown below.
'''
CREATE TABLE public.fi_raisedalarms
(
id integer NOT NULL DEFAULT nextval('fi_raisedalarms_id_seq'::regclass),
equipid integer,
alid integer,
isset boolean,
tstamp timestamp without time zone,
create_uid integer,
create_date timestamp without time zone,
write_uid integer,
write_date timestamp without time zone,
CONSTRAINT fi_raisedalarms_pkey PRIMARY KEY (id),
CONSTRAINT fi_raisedalarms_alid_fkey FOREIGN KEY (alid)
REFERENCES public.fi_alarms (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE SET NULL,
CONSTRAINT fi_raisedalarms_create_uid_fkey FOREIGN KEY (create_uid)
REFERENCES public.res_users (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE SET NULL,
CONSTRAINT fi_raisedalarms_write_uid_fkey FOREIGN KEY (write_uid)
REFERENCES public.res_users (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE SET NULL
)
'''
I then want to use a function as shown below to insert omitting the id since it should pull the default value but I am getting the following error. Not sure where to go from here....
'''
CREATE OR REPLACE FUNCTION public.setequipmentalarm(
equipmentid integer,
alarmid integer,
isset boolean,
tstamp timestamp without time zone)
RETURNS integer
LANGUAGE 'plpgsql'
COST 100
VOLATILE
AS $BODY$
DECLARE
var integer;
BEGIN
INSERT INTO fi_raisedalarms VALUES(equipmentid, alarmid, isset, tstamp) RETURNING equipmentid into var;
RETURN var;
END;
$BODY$;
ALTER FUNCTION public.setequipmentalarm(integer, integer, boolean, timestamp without time zone)
OWNER TO postgres;
'''
ERROR: column "alid" is of type integer but expression is of type boolean
LINE 1: ...INTO fi_raisedalarms VALUES(equipmentid, alarmid, isset, tst...
^
HINT: You will need to rewrite or cast the expression.
QUERY: INSERT INTO fi_raisedalarms VALUES(equipmentid, alarmid, isset, tstamp) RETURNING equipmentid
CONTEXT: PL/pgSQL function setequipmentalarm(integer,integer,boolean,timestamp without time zone) line 5 at SQL statement
SQL state: 42804
You need to specify column names if you are not setting a value for each table column:
INSERT INTO fi_raisedalarms(equipid, alid , isset, tstamp) VALUES ...
Alternatively, you can insert DEFAULT in place of the column to explicitly choose the default value. But specifying the columns is preferable.

Returned type bigint does not match expected type integer in column 3

Below is my table structure for sold_quantity (Migration File)
alter table public.invoice_item add column sold_quantity int4 default 1;
Below is the function for execution
CREATE OR REPLACE FUNCTION sold_quantity()
RETURNS TABLE(
invoiceid BIGINT,
itemid BIGINT,
sum_sold_quantity INT)
AS $$
BEGIN
RETURN QUERY SELECT
invoice_id as invoiceid, item_id as itemid, sum(sold_quantity) as
sum_sold_quantity
FROM
invoice_item
WHERE
status='sold'
GROUP BY
invoice_id, item_id;
END; $$
What is the wrong in my code, Please help me solve this Error
Returned type bigint does not match expected type integer in column 3
sum() returns a bigint, not necessarily the type of the column that is being summed.
If you are 100% sure your sum never exceeds the range for an integer, you can fix this using a cast in your query: sum(sold_quantity)::int as sum_sold_quantity
But it would be better to adjust the signature of the function:
CREATE OR REPLACE FUNCTION sold_quantity()
RETURNS TABLE(
invoiceid BIGINT,
itemid BIGINT,
sum_sold_quantity BIGINT)

using aggregation function while creating function in psql

Hi guys i have little problem, i have tables like those
CREATE TABLE client(
regon VARCHAR NOT NULL,
title VARCHAR,
phone VARCHAR,
PRIMARY KEY(regon));
CREATE TABLE commodity(
id_com INT NOT NULL,
title VARCHAR,
PRIMARY KEY(id_com));
CREATE TABLE supply(
regon VARCHAR NOT NULL REFERENCES klient(regon),
id_supply INT NOT NULL,
id_com INT NOT NULLREFERENCES commodity(id_com),
quantity INT,
price DEC(5,2),
PRIMARY KEY(regon, id_supply, id_com));
and i have to create function which would returns value of all supplies (qantity*price)
and i made function like this:
CREATE OR REPLACE FUNCTION value1(out id int, out war double precision)as $$
select (quantity*price) as value from supply;
$$
language 'plpgsql';
but it only shows the first supply with id of first commodity with its value but not all commodities
maybe
you know how to do this?
thanks
Change this select (quantity*price) as value from supply;
to this:
select sum(quantity*price) from supply
group by id_com
order by id_com
I had to do query like this:
select sum(quantity*price) from supply
group by id_com,quantity,price
order by id_com
and it is also showing me id_com double times like i have two commodities with id 1 and one is 400 and second is 30, and i think i should maybe sum this up

Does postgres postgis ST_makeline have a max number of points it can create a line from?

My database has a table with tons of geometry(PointZ,4326) I am doing a lot of my processing on the database side and I've noticed that when I use the ST_MakeLine I seem to be hitting a cap on the number of points it will make a line from. My table and function/query is below.
It works as long as the number of track_points returned from the sub query is less than 97. I know this because the insert puts data in the table for all columns when there are 96 points or fewer. For all records where there are 97 or more points all it inserts is the track_id, start_time and end_time.
I'm wondering if this is a bug in the ST_makeLine function of postgis or is it a setting in postgres that I need to modify.
CREATE TABLE track_line_strings(
track_id bigint NOT NULL,
linestring geometry(LINESTRINGZ,4326),
start_time bigint NOT NULL,
end_time bigint NOT NULL,
CONSTRAINT track_line_strings_pk PRIMARY KEY (track_id)
);
CREATE OR REPLACE FUNCTION create_track_line_string() RETURNS trigger
LANGUAGE plpgsql
AS $$
DECLARE
TRACKITEMID bigint := new.track_item_id;
TRACKID bigint := track_id from track_item ti where ti.id = TRACKITEMID;
STARTTIME bigint := MIN(ti.item_time) from track_item ti where ti.track_id = TRACKID;
ENDTIME bigint := MAX(ti.item_time) from track_item ti where ti.track_id = TRACKID;
BEGIN
IF EXISTS (SELECT track_id from track_line_strings where track_id = TRACKID)
THEN
UPDATE track_line_strings
SET start_time = STARTTIME, end_time = ENDTIME, linestring = (
SELECT ST_Makeline(e.trackPosition) FROM
(
Select track_id, tp.track_position AS trackPosition
FROM track_point tp JOIN track_item ti ON tp.track_item_id = ti.id
where ti.track_id = TRACKID ORDER BY ti.item_time ASC
) E )
WHERE track_id = TRACKID;
ELSE
INSERT INTO track_line_strings(track_id, linestring, start_time, end_time)
SELECT TRACKID, ST_Makeline(e.trackPosition), STARTTIME, ENDTIME FROM
(
Select track_id, tp.track_position AS trackPosition
FROM track_point tp JOIN track_item ti ON tp.track_item_id = ti.id
where ti.track_id = TRACKID ORDER BY ti.item_time ASC
)E;
END IF;
RETURN new;
END;
$$;
The database limits are pretty high, 1 GB data worth of geometry data in a field. It depends on what kind of point geometry, but it will be on the order of tens of millions of point geometries that can be used to construct a LineString.
You will see a proper error message with something about "exceeded size" if it is a limitation.
Apparent empty or missing data with pgAdminIII is a common question, but not related to database limitations:
http://postgis.net/2013/10/05/tip_pgAdmin_shows_no_data
http://postgis.net/docs/manual-dev/PostGIS_FAQ.html#pgadmin_shows_no_data_in_geom
There doesnt appear to be a limit. I was viewing results in pgAdminIII and there must be a limit on the number of characters the data output can handle for each column. I only realized this by copy pasting the results into a text file to see that it did infact return a value for the lines that have more than 96 points.