Passing table name as an argument in PostGIS function - postgresql

How can I pass my table name as an argument of a function?
CREATE OR REPLACE
FUNCTION public.countries_name1(
z integer, x integer , y integer,
name_prefix text default 'B' )
RETURNS bytea
AS $$
-- Convert tile coordinates to a bounding box
WITH
bounds AS (
SELECT ST_TileEnvelope(z, x, y) AS geom,
(CASE
when z >= 16 then 5
when z = 15 then 50
when z = 14 then 100
when z = 13 then 150
when z = 12 then 200
when z <= 11 then 300
--when z <= 10 then 500
--when z = 9 then 700
--when z = 8 then 800
--when z = 7 then 900
--when z <= 6 then 1000
ELSE 1 END
) as simplify_tolerance
),
-- Convert raw geometry into MVT geometry
-- Pull just the name in addition to the geometry
-- Apply the name_prefix parameter to the WHERE clause
mvtgeom AS (
SELECT ST_AsMVTGeom(ST_Transform(ST_simplify(t.geom,simplify_tolerance), 3857), bounds.geom)
AS geom,
t.fclass,z,x,y
FROM table_name t, bounds
WHERE ST_Intersects(t.geom, ST_Transform(bounds.geom, 25833))
AND upper(t.fclass) LIKE (upper(name_prefix) || '%') AND z>=10
)
-- Serialize the result set into an MVT object
SELECT ST_AsMVT(mvtgeom, 'public.countries_name1') FROM mvtgeom;
$$
LANGUAGE 'sql'
STABLE
PARALLEL SAFE;
In this code I want to pass my table_name from function parameter.I want to use this function in pg_tileserv .I tried to pass table name from argument but it did not work.

OK, how do you rewrite your statement to dynamic SQL.
Well the first thing you do is read the Dynamic SQL documentation. Also helpful would be Lexical Structure section 4.1.2.4, and the Format function. I advise you first write a few simple test cases to become familiar with it. Dynamic SQL is not simple.
Below I do a direct translation of your function into dynamic SQL. I make **no guarantees on the correctness of the query; it just shows how to convert it and it is not tested. It will fail. You said you passed the table name as a parameter, but your function does not have a parameter table_name, the rewrite just shows how you would use it. Note the lines marked --<<<.
create or replace
function public.countries_name(z integer
, x integer
, y integer
, name_prefix text default 'B' )
returns bytea
language plpgsql --<<<
as $$
declare
k_sql_base constant text = --<<< Convert Query to a STRING
-- Convert tile coordinates to a bounding box
$STMT$ --<<<
with bounds as
(
select
ST_TileEnvelope(z
, x
, y) as geom
,
(case
when z >= 16 then 5
when z = 15 then 50
when z = 14 then 100
when z = 13 then 150
when z = 12 then 200
when z <= 11 then 300
else 1
end
) as simplify_tolerance
)
-- Convert raw geometry into MVT geometry
-- Pull just the name in addition to the geometry
-- Apply the name_prefix parameter to the WHERE clause
, mvtgeom as
(
select
ST_AsMVTGeom(ST_Transform(ST_simplify(t.geom
, simplify_tolerance)
, 3857)
, bounds.geom) as geom
, t.fclass
, z
, x
, y
from
%I t --<<< place holder for TABLE_NAME
, bounds
where
ST_Intersects(t.geom
, ST_Transform(bounds.geom
, 25833))
and upper(t.fclass) like (upper(name_prefix) || '%')
and z >= 10
)
-- Serialize the result set into an MVT object
select
ST_AsMVT(mvtgeom, 'public.countries_name1')
from
mvtgeom;
$STMT$;
--<<<
l_sql_statement text;
l_result bytea;
begin
l_sql_statement = format(k_sql_base, table_name); -- <<< fails as table_name NOT defined as parameter
raise notice E'Executing SQL Statement:\n' || l_sql_statement; -- show the statement to be executed
execute l_sql_statement into l_result bytea; -- execute statement
return l_result bytea; -- return result
end ;
$$

Related

How to use ROW_NUMBER() - but instead of incremental numbers , generate A, B, C, D, E

I'm using ROW_NUMBER() to generate incremetal IDs numbers like this
ROW_NUMBER () OVER (ORDER BY ProductDate) as ID
i.e.
1
2
3
4
How can I do the same but create Alphabetic ordered letters like this
A
B
C
D
E
F.
..... AA, BB, CC
any ideas? Thx in advance
demo:db<>fiddle
You need to write an own function like this (original source):
CREATE OR REPLACE FUNCTION number_to_base(num BIGINT, base INTEGER)
RETURNS TEXT
LANGUAGE sql
IMMUTABLE
STRICT
AS $function$
WITH RECURSIVE n(i, n, r) AS (
SELECT -1, num - 1, 0
UNION ALL
SELECT i + 1, n / base, (n % base)::INT
FROM n
WHERE n > 0
)
SELECT string_agg(ch, '')
FROM (
SELECT chr(ascii('A') + r) ch
FROM n
WHERE i >= 0
ORDER BY i DESC
) ch
$function$;
It converts the numeral radix from base 10 to base 26 and replace the numbers 0-25 with A to Z. It's not quite perfect but a quick sketch (e.g. the A is 0 at the moment, you need to adjust it a bit).
You can use the the functions Chr() and Ascii() together to increment a character. The only other complication is determining when the next value is '...AA' instead a incrementing the last letter (i.e 'AY' => 'AZ' while 'AZ' => 'AAA). So try:
create or replace function alpha_sequence_next_val( alpha_seq_in text)
returns text
language sql
immutable
as $$
select case when alpha_seq_in is null
or alpha_seq_in = ''
then 'A'
when substr(alpha_seq_in, length(alpha_seq_in), 1) = 'Z'
then concat(substr(alpha_seq_in,1,length(alpha_seq_in)-1 ), 'AA')
else (substr(alpha_seq_in, 1,length(alpha_seq_in)-1)) ||
chr(ascii(substr(alpha_seq_in, length(alpha_seq_in)))+1)
end;
$$;
Test:
do $$
declare
seq_value text;
begin
for test_seq in 0 .. 26*3
loop
seq_value = alpha_sequence_next_val(seq_value);
raise notice 'Next Sequence==> %', seq_value;
end loop;
end;
$$;

ERROR: structure of query does not match function result type, Returned type text does not match expected type geometry

Trying to write a function that returns the geometries of points interpolated in a line from a DEM and elevations but getting this error ERROR: structure of query does not match function result type. The query in the end of points should return the geometries of the startpoint, endpoint and interpolated points of an interval. This is my function:
test_get_slope_profile
CREATE OR REPLACE FUNCTION test_get_slope_profile(ways_id bigint, interval_ float default 10, way_table TEXT DEFAULT 'ways')
RETURNS TABLE(p_geom geometry, elevation float)
LANGUAGE plpgsql
AS $function$
DECLARE
way_geom geometry;
length_meters float;
length_degree NUMERIC;
translation_m_degree NUMERIC;
BEGIN
IF way_table = 'ways' THEN
SELECT geom, length_m, ST_Length(geom)
INTO way_geom, length_meters, length_degree
FROM ways
WHERE id = ways_id;
ELSEIF way_table = 'ways_userinput' THEN
SELECT geom, length_m, ST_Length(geom)
INTO way_geom, length_meters, length_degree
FROM ways_userinput
WHERE id = ways_id;
END IF;
translation_m_degree = length_degree/length_meters;
DROP TABLE IF EXISTS dump_points;
IF length_meters > (2*interval_) THEN
CREATE TEMP TABLE dump_points AS
SELECT (ST_DUMP(ST_Lineinterpolatepoints(way_geom,interval_/length_meters))).geom AS geom;
ELSEIF length_meters > interval_ AND length_meters < (2*interval_) THEN
CREATE TEMP TABLE dump_points AS
SELECT ST_LineInterpolatePoint(way_geom,0.5) AS geom;
interval_ = length_meters/2;
ELSE
CREATE TEMP TABLE dump_points AS
SELECT NULL::geometry AS geom;
END IF;
RETURN query
WITH points AS
(
SELECT ROW_NUMBER() OVER() cnt, geom, length_meters
FROM (
SELECT st_startpoint(way_geom) AS geom
UNION ALL
SELECT geom FROM dump_points
UNION ALL
SELECT st_endpoint(way_geom)
) x
)
SELECT 'geom', SUM(idw.val/(idw.distance/translation_m_degree))/SUM(1/(idw.distance/translation_m_degree))::real AS elev
FROM points p, get_idw_values(geom) idw
WHERE p.geom IS NOT NULL
GROUP BY cnt
ORDER BY cnt;
END;
$function$;
ERROR: structure of query does not match function result type
DETAIL: Returned type text does not match expected type geometry in column 1.
CONTEXT: PL/pgSQL function test_get_slope_profile(bigint,double precision,text) line 38 at RETURN QUERY
SQL state: 42804
In the RETURN query statement, don't do SELECT 'geom', SUM(...))::real AS elev but rather SELECT geom, SUM(...))::real AS elev
The former returns the text 'geom' while the later returns the column content, i.e. the geometry

How can i put a table in a function?

id_session,x,z,time
1,-10,-10,0
1,-10,-10,1
1,-5,-4,2
I have this function:
Select x.x,x.z,sum(case when x=nextlat and z=nextlng then 0 else 1 end ) s
From
( Select x,
z,
lead(x,1,-9999) over (order by tempo) nextlat,
lead(z,1,-9999) over (order by tempo) nextlng
from xmltrack where id_session = 1
) x
Group by x.x,x.z
order by s desc
How I create a function with id_session as parameter?
Something like this:
CREATE OR REPLACE FUNCTION xmlintersezioni (idsession integer)
RETURNS TABLE (cx integer, cz integer, cs integer) AS $$
BEGIN
RETURN QUERY Select x.x,x.z,sum(case when x=nextlat and z=nextlng then 0 else 1 end ) s
From
(select x,
z,
lead(x,1,-9999) over (order by tempo) nextlat,
lead(z,1,-9999) over (order by tempo) nextlng
from xmltrack where id_session = idsession
) x
Group by x.x,x.z
order by s desc;
END; $$
LANGUAGE 'plpgsql';
I'm assuming id_session is a custom type -- something like this:
create type id_session_type AS <whatever>;
create table xmltrack (
id_session id_session_type,
x integer,
y integer,
z integer
);
In which case, I think all you need to do is use that same datatype in your function:
CREATE OR REPLACE FUNCTION xmlintersezioni (idsesssion id_session_type)

Convert value from string representation in base N to numeric

On my database i keep a numbers in specific notation as varchar values. Is there any way to typecast this values into decimals with selected notation?
What i basically looking here should look like this:
SELECT to_integer_with_notation('d', '20')
int4
---------
13
One more example:
SELECT to_integer_with_notation('d3', '23')
int4
---------
302
Unfortunately, there is no built-in function for that in PostgreSQL, but can be written fairly easy:
CREATE OR REPLACE FUNCTION number_from_base(num TEXT, base INTEGER)
RETURNS NUMERIC
LANGUAGE sql
IMMUTABLE
STRICT
AS $function$
SELECT sum(exp * cn)
FROM (
SELECT base::NUMERIC ^ (row_number() OVER () - 1) exp,
CASE
WHEN ch BETWEEN '0' AND '9' THEN ascii(ch) - ascii('0')
WHEN ch BETWEEN 'a' AND 'z' THEN 10 + ascii(ch) - ascii('a')
END cn
FROM regexp_split_to_table(reverse(lower(num)), '') ch(ch)
) sub
$function$;
Note: I used numeric as a return type, as int4 is not enough in many cases (with longer string input).
Edit: Here is a sample reverse function, which can convert a bigint to its text representation within a custom base:
CREATE OR REPLACE FUNCTION number_to_base(num BIGINT, base INTEGER)
RETURNS TEXT
LANGUAGE sql
IMMUTABLE
STRICT
AS $function$
WITH RECURSIVE n(i, n, r) AS (
SELECT -1, num, 0
UNION ALL
SELECT i + 1, n / base, (n % base)::INT
FROM n
WHERE n > 0
)
SELECT string_agg(ch, '')
FROM (
SELECT CASE
WHEN r BETWEEN 0 AND 9 THEN r::TEXT
WHEN r BETWEEN 10 AND 35 THEN chr(ascii('a') + r - 10)
ELSE '%'
END ch
FROM n
WHERE i >= 0
ORDER BY i DESC
) ch
$function$;
Example usage:
SELECT number_to_base(1248, 36);
-- +----------------+
-- | number_to_base |
-- +----------------+
-- | yo |
-- +----------------+

ERROR: function expression in form cannot refer to other relations of same query level : How to work around LATERAL

DROP SCHEMA tmp CASCADE;
CREATE SCHEMA tmp ;
SET search_path=tmp;
CREATE TABLE primes
( pos SERIAL NOT NULL PRIMARY KEY
, val INTEGER NOT NULL
, CONSTRAINT primes_alt UNIQUE (val)
);
CREATE FUNCTION is_prime(_val INTEGER)
RETURNS BOOLEAN
AS $func$
DECLARE ret BOOLEAN ;
BEGIN
SELECT False INTO ret
WHERE EXISTS (SELECT *
FROM primes ex
WHERE ex.val = $1
OR ( (ex.val * ex.val) <= $1 AND ($1 % ex.val) = 0 )
);
RETURN COALESCE(ret, True);
END;
$func$ LANGUAGE plpgsql STABLE;
CREATE VIEW vw_prime_step AS (
-- Note when the table is empty we return {2,3,1} as a bootstrap
SELECT
COALESCE(MAX(val) +2,2) AS start
, COALESCE((MAX(val) * MAX(val))-1, 3) AS stop
, COALESCE(min(val), 1) AS step
FROM primes
);
SELECT * FROM vw_prime_step;
-- The same as a function.
-- Works, but is not usable in a query that alters the primes table.
-- ; even not with the TEMP TABLE construct
CREATE FUNCTION fnc_prime_step ( OUT start INTEGER, OUT stop INTEGER, OUT step INTEGER)
RETURNS RECORD
AS $func$
BEGIN
/***
CREATE TEMP TABLE tmp_limits
ON COMMIT DROP
AS SELECT ps.start,ps.stop,ps.step FROM vw_prime_step ps
;
-- RETURN QUERY
SELECT tl.start,tl.stop,tl.step INTO $1,$2,$3
FROM tmp_limits tl
LIMIT 1
;
***/
SELECT tl.start,tl.stop,tl.step INTO $1,$2,$3
FROM vw_prime_step tl
LIMIT 1;
END;
$func$
-- Try lying ...
-- IMMUTABLE LANGUAGE plpgsql;
-- Try lying ...
Stable LANGUAGE plpgsql;
-- This works
SELECT * FROM fnc_prime_step();
INSERT INTO primes (val)
SELECT gs FROM fnc_prime_step() sss
, generate_series( 2, 3, 1 ) gs
WHERE is_prime(gs) = True
;
-- This works
SELECT * FROM fnc_prime_step();
INSERT INTO primes (val)
SELECT gs FROM fnc_prime_step() sss
, generate_series( 5, 24, 2 ) gs
WHERE is_prime(gs) = True
;
-- This does not work
-- ERROR: function expression in FROM cannot refer to other relations of same query level:1
SELECT * FROM fnc_prime_step();
INSERT INTO primes (val)
SELECT gs FROM fnc_prime_step() sss
, generate_series( sss.start, sss.stop, sss.step ) gs
WHERE is_prime(gs) = True
;
SELECT * FROM primes;
SELECT * FROM fnc_prime_step();
Of course, this question is purely hypothetic, I am not stupid enough to attempt to calculate a table of prime numbers in an DBMS. But the question remains: is there a clean way to hack around the absence of LATERAL?
As you can see, I tried with a view (does not work), function around this view (does not work either), a temp table in this function (njet), and twiddling the function's attributes.
Next step will probably be some trigger-hack (but I really,really hate triggers, basically because they are invisible to the strictness of the DBMS schema)
you can use SRF function in target list, but there should be some strange corner cases. LATERAL is best.
postgres=# select i, generate_series(1,i) X from generate_series(1,3) g(i);
i | x
---+---
1 | 1
2 | 1
2 | 2
3 | 1
3 | 2
3 | 3
(6 rows)