I have the following table called as test_type which contains two columns namely cola and colb.
Table: test_type
create table test_type
(
cola int,
colb varchar(50)
);
Now I want to create a type with same columns.
Type: type1
create type type1 as
(
cola int,
colb varchar(50)
);
Here I have created function in which I am passing type name type1 to insert the data to the
table test_type.
--Creating Function
create or replace function fun_test ( p_Type type1 )
returns void
as
$$
begin
insert into test_type(cola,colb)
select cola,colb from p_type
EXCEPT
select cola,colb from test_type;
end
$$
language plpgsql;
---Calling Function
SELECT fun_test(1,'Xyz');
Error Details:
ERROR: function fun_test(integer, unknown) does not exist
SQL state: 42883
Hint: No function matches the given name and argument types. You might need to add explicit type casts.
Character: 8
You need to "pack" the arguments together: (1,'xs'), so that postgres recognise them as single argument of type type1:
SELECT fun_test((1,'xs'));
For a better readability you can cast the argument to type1 (not really necessary):
SELECT fun_test((1,'xs')::type1);
If the purpose of the function is to to insert the values only if they are not already contained in the table, you could change your code so:
create or replace function fun_test ( p_Type type1 )
returns void AS $$
BEGIN
INSERT INTO test_type(cola,colb)
SELECT p_Type.cola,p_Type.colb
EXCEPT
SELECT cola,colb FROM test_type;
END;
$$ language plpgsql;
But this syntax is my opinion not good readable. This statement looks better:
...
BEGIN
PERFORM 0 FROM test_type WHERE (cola, colb) = p_Type;
IF NOT FOUND THEN
INSERT INTO test_type(cola,colb) VALUES (p_Type.cola,p_Type.colb);
END IF;
END;
...
Related
I'm working with Postgres and PostGIS. Trying to write a function that that selects specific columns according to the given argument.
I'm using a WITH statement to create the result table before converting it to bytea to return.
The part I need help with is the $4 part. I tried it is demonstrated below and $4::text and both give me back the text value of the input and not the column value in the table if cols=name so I get back from the query name and not the actual names in the table. I also try data($4) and got type error.
The code is like this:
CREATE OR REPLACE FUNCTION select_by_txt(z integer,x integer,y integer, cols text)
RETURNS bytea
LANGUAGE 'plpgsql'
AS $BODY$
declare
res bytea;
begin
WITH bounds AS (
SELECT ST_TileEnvelope(z, x, y) AS geom
),
mvtgeom AS (
SELECT ST_AsMVTGeom(ST_Transform(t.geom, 3857), bounds.geom) AS geom, $4
FROM table1 t, bounds
WHERE ST_Intersects(t.geom, ST_Transform(bounds.geom, 4326))
)
SELECT ST_AsMVT(mvtgeom, 'public.select_by_txt')
INTO res
FROM mvtgeom;
RETURN res;
end;
$BODY$;
Example for calling the function:
select_by_txt(10,32,33,"col1,col2")
The argument cols can be multiple column names from 1 and not limited from above. The names of the columns inside cols will be checked before calling the function that they are valid columns.
Passing multiple column names as concatenated string for dynamic execution urgently requires decontamination. I suggest a VARIADIC function parameter instead, with properly quoted identifiers (using quote_ident() in this case):
CREATE OR REPLACE FUNCTION select_by_txt(z int, x int, y int, VARIADIC cols text[] = NULL, OUT res text)
LANGUAGE plpgsql AS
$func$
BEGIN
EXECUTE format(
$$
SELECT ST_AsMVT(mvtgeom, 'public.select_by_txt')
FROM (
SELECT ST_AsMVTGeom(ST_Transform(t.geom, 3857), bounds.geom) AS geom%s
FROM table1 t
JOIN (SELECT ST_TileEnvelope($1, $2, $3)) AS bounds(geom)
ON ST_Intersects(t.geom, ST_Transform(bounds.geom, 4326))
) mvtgeom
$$, (SELECT ', ' || string_agg(quote_ident (col), ', ') FROM unnest(cols) col)
)
INTO res
USING z, x, y;
END
$func$;
db<>fiddle here
The format specifier %I for format() deals with a single identifier. You have to put in more work for multiple identifiers, especially for a variable number of 0-n identifiers. This implementation quotes every single column name, and only add a , if any column names have been passed. So it works for every possible input, even no input at all. Note VARIADIC cols text[] = NULL as last input parameter with NULL as default value:
Optional argument in PL/pgSQL function
Related:
quote_ident() does not add quotes to column name "first"
Column names are case sensitive in this context!
Call for your example (important!):
SELECT select_by_txt(10,32,33,'col1', 'col2');
Alternative syntax:
SELECT select_by_txt(10,32,33, VARIADIC '{col1,col2}');
More revealing call, with a third column name and malicious (though futile) intent:
SELECT select_by_txt(10,32,33,'col1', 'col2', $$col3'); DROP TABLE table1;--$$);
About that odd third column name and SQL injection:
https://www.explainxkcd.com/wiki/index.php/Little_Bobby_Tables
About VAIRADIC parameters:
Return rows matching elements of input array in plpgsql function
Pass multiple values in single parameter
Using an OUT parameter for simplicity. That's totally optional. See:
Returning from a function with OUT parameter
What I would not do
If you really, really trust the input to be a properly formatted list of 1 or more valid column names at all times - and you asserted that ...
the names of the columns inside cols will be checked before calling the function that they are valid columns
You could simplify:
CREATE OR REPLACE FUNCTION select_by_txt(z int, x int, y int, cols text, OUT res text)
LANGUAGE plpgsql AS
$func$
BEGIN
EXECUTE format(
$$
SELECT ST_AsMVT(mvtgeom, 'public.select_by_txt')
FROM (
SELECT ST_AsMVTGeom(ST_Transform(t.geom, 3857), bounds.geom) AS geom, %s
FROM table1 t
JOIN (SELECT ST_TileEnvelope($1, $2, $3)) AS bounds(geom)
ON ST_Intersects(t.geom, ST_Transform(bounds.geom, 4326))
) mvtgeom
$$, cols
)
INTO res
USING z, x, y;
END
$func$;
(How can you be so sure that the input will always be reliable?)
You would need to use a dynamic query:
CREATE OR REPLACE FUNCTION select_by_txt(z integer,x integer,y integer, cols text)
RETURNS bytea
LANGUAGE 'plpgsql'
AS $BODY$
declare
res bytea;
begin
EXECUTE format('
WITH bounds AS (
SELECT ST_TileEnvelope($1, $2, $3) AS geom
),
mvtgeom AS (
SELECT ST_AsMVTGeom(ST_Transform(t.geom, 3857), bounds.geom) AS geom, %I
FROM table1 t, bounds
WHERE ST_Intersects(t.geom, ST_Transform(bounds.geom, 4326))
)
SELECT ST_AsMVT(mvtgeom, ''public.select_by_txt'')
FROM mvtgeom', cols)
INTO res
USING z,x,y;
RETURN res;
end;
$BODY$;
I have a SQL table having two columns 'Product_Type' and 'Product_SubType'. A product has a unique name defined based on combining the type and subtype. But the table does not defined the product name. When I query the table, I would like to have the product name presented.
So I am trying to create a function containing cases of different types and output a product name in a select statement:
create function getNameByTypes( MainType varchar, SubType varchar)
return varchar as $$
declare TypeName varchar;
begin
TypeName = case MainType
when 'type_I' then
case SubType
when 'sub_type_1' then 'TypeName1'
when 'sub_type_2' then 'TypeName2'
end
else 'unknown_type' end;
return TypeName;
end;
$$
And use the function like:
select *, getNameByParams(Product_Type, Product_SubType) as TypeName
where id = 'ABC'
I am getting an error SQL Error [42601]: ERROR: syntax error at or near "return". My question is: what is the correct way to create the function and use the function to query?
you must change return varchar clause for returns varchar in functions definition, and missing the function language, see the following:
create function getNameByTypes( MainType varchar, SubType varchar)
returns varchar as $$
declare TypeName varchar;
begin
TypeName = case MainType
when 'type_I' then
case SubType
when 'sub_type_1' then 'TypeName1'
when 'sub_type_2' then 'TypeName2'
end
else 'unknown_type' end;
return TypeName;
end;
$$
language plpgsql;
And use with your table in from clause:
select *, getNameByParams(Product_Type, Product_SubType) as TypeName from your_table
where id = 'ABC'
I have the table with some columns:
--table
create table testz
(
ID uuid,
name text
);
Note: I want to insert ID values by passing as a parameter to the function. Because I am generating the ID value
in the front end by using uuid_generate_v4(). So I need to pass the generated value to the function to insert
into the table
My bad try:
--function
CREATE OR REPLACE FUNCTION testz
(
p_id varchar(50),
p_name text
)
RETURNS VOID AS
$BODY$
BEGIN
INSERT INTO testz values(p_id,p_name);
END;
$BODY$
LANGUAGE PLPGSQL;
--EXECUTE FUNCTION
SELECT testz('24f9aa53-e15c-4813-8ec3-ede1495e05f1','Abc');
Getting an error:
ERROR: column "id" is of type uuid but expression is of type character varying
LINE 1: INSERT INTO testz values(p_id,p_name)
You need a simple cast to make sure PostgreSQL understands, what you want to insert:
INSERT INTO testz values(p_id::uuid, p_name); -- or: CAST(p_id AS uuid)
Or (preferably) you need a function, with exact parameter types, like:
CREATE OR REPLACE FUNCTION testz(p_id uuid, p_name text)
RETURNS VOID AS
$BODY$
BEGIN
INSERT INTO testz values(p_id, p_name);
END;
$BODY$
LANGUAGE PLPGSQL;
With this, a cast may be needed at the calling side (but PostgreSQL usually do better automatic casts with function arguments than inside INSERT statements).
SQLFiddle
If your function is that simple, you can use SQL functions too:
CREATE OR REPLACE FUNCTION testz(uuid, text) RETURNS VOID
LANGUAGE SQL AS 'INSERT INTO testz values($1, $2)';
Example: I am passing two parameters to function namely n(case number) and tname(table name), and want to display rows accordingly.
--Table "testing"
create table testing
(
rollno integer,
fname text,
lname text,
age integer,
branch text,
phno integer,
email text,
address text,
city text,
state text,
country text
)
--Rows insertion
insert into testing values(1,'aaa','bbb',25,'CS',1234567890,'abc#gmail.com','sector1','xyz','zyx','yxz');
insert into testing values(2,'zzz','xxx',25,'EE',987654321,'zzz#gmail.com','sector2','uvw','wvu','vuw');
--Function "f1()"
create or replace function f1(n integer,tname varchar)/*n for case number and tname for table name */
returns setof tname as
$body$
begin
case n
when 1 then
return query execute format ($$ select rollno,fname from %I $$,tname);
when 2 then
return query execute format ($$ select lname,age,branch from %I $$,tname);
when 3 then
return query execute format ($$ select phno,email,address,city,country from %I $$,tname);
end case;
end
$body$
language plpgsql;
--Function calling
select * from f1(1,'testing');
/*Show only case "1" select query result*/
select * from f1(2,'testing');
/*Show only case "2" select query result*/
select * from f1(3,'testing');
/*Show only case "3" select query result*/
While Craig is correct that return types cannot be dynamic in function declarations, there is a way around this with polymorphic types. This is surprisingly simple and would actually work flawlessly:
CREATE OR REPLACE FUNCTION data_of(_tbl_type anyelement)
RETURNS SETOF anyelement AS
$func$
BEGIN
RETURN QUERY EXECUTE 'SELECT * FROM '|| pg_typeof(_tbl_type);
END
$func$ LANGUAGE plpgsql;
Call (important!):
SELECT rollno,fname FROM data_of(NULL::testing);
SELECT * FROM data_of(NULL::my_schema.my_table);
SELECT * FROM data_of(NULL::my_custom_type);
What you need is a well-known type. For every table there is a well-known type automatically. But you can create any type, cast NULL to it and pass it to the function. This way you can build exactly what you have in your question ...
Related answer with a lot more details:
Refactor a PL/pgSQL function to return the output of various SELECT queries
I have dynamicly generated SELECT. I try to return result as SETOF RECORD. Sth like that:
CREATE FUNCTION test(column_name text) RETURNS SETOF RECORD AS $$
DECLARE
row RECORD;
BEGIN
FOR row IN EXECUTE 'SELECT ' || quote_ident(column_name) || ' FROM dates'
LOOP
RETURN NEXT row;
END LOOP;
RETURN;
END;
$$ LANGUAGE 'plpgsql';
When I try:
SELECT * FROM test('column1');
I get this:
ERROR: a column definition list is required for functions returning "record"
I know that column1 is integer type:
SELECT * FROM test('column1') f(a int);
result is correct, because I know that this is going to be Integer type.
When I try:
SELECT * FROM test('column1') f(a varchar);
I get error:
ERROR: wrong record type supplied in RETURN NEXT
DETAIL: Returned type integer does not match expected type character varying in column 1.
Now my question:
What to do to get rid of part of querty where I define types 'f(a int)'. It should by feasible because Postgres knowns what is returned type. I tried with IMMUTABLE options, but unsuccessfully.
You could cast the value to text inside the function, and declare that the function RETURNS SETOF text. You can also return the whole result set at once; no need to iterate explicitly.
CREATE TABLE dates (column1 int, column2 date);
INSERT INTO dates VALUES (1, date '2012-12-22'), (2, date '2013-01-01');
CREATE FUNCTION test(column_name text) RETURNS SETOF text AS $$
BEGIN
RETURN QUERY EXECUTE 'SELECT '
|| quote_ident(column_name) || '::text FROM dates';
END;
$$ LANGUAGE 'plpgsql';
Now SELECT test('column1'); yields:
test
------
1
2
(2 rows)
... and (with my locale settings) SELECT test('column2'); yields:
test
------------
2012-12-22
2013-01-01
(2 rows)
You need to specify OUT parameters corresponding to the columns you want to return.