In below create view statement I don't understand the how the phone_number is passed to a function fn_services (which considered as a table) and that function will return what kind of output a table or what?
Inside the function fn_service a variable
vServTab services_tab := services_tab ();
is returned.
I don't understand this join...
CREATE OR REPLACE FORCE VIEW (COLA,COLB)
AS
SELECT COL1,COL2
FROM v_service_view spn,
TABLE (fn_services (spn.phone_number, NULL)) serv;
Related
I have a table that looks like this:
CREATE TABLE label (
hid UUID PRIMARY KEY DEFAULT UUID_GENERATE_V4(),
name TEXT NOT NULL UNIQUE
);
I want to create a function that takes a list of names and inserts multiple rows into the table, ignoring duplicate names, and returns an array of the IDs generated for the rows it inserted.
This works:
CREATE OR REPLACE FUNCTION insert_label(nms TEXT[])
RETURNS UUID[]
AS $$
DECLARE
ids UUID[];
BEGIN
CREATE TEMP TABLE tmp_names(name TEXT);
INSERT INTO tmp_names SELECT UNNEST(nms);
WITH new_names AS (
INSERT INTO label(name)
SELECT tn.name
FROM tmp_names tn
WHERE NOT EXISTS(SELECT 1 FROM label h WHERE h.name = tn.name)
RETURNING hid
)
SELECT ARRAY_AGG(hid) INTO ids
FROM new_names;
DROP TABLE tmp_names;
RETURN ids;
END;
$$ LANGUAGE PLPGSQL;
I have many tables with the exact same columns as the label table, so I would like to have a function that can insert into any of them. I'd like to create a dynamic query to do that. I tried that, but this does not work:
CREATE OR REPLACE FUNCTION insert_label(h_tbl REGCLASS, nms TEXT[])
RETURNS UUID[]
AS $$
DECLARE
ids UUID[];
query_str TEXT;
BEGIN
CREATE TEMP TABLE tmp_names(name TEXT);
INSERT INTO tmp_names SELECT UNNEST(nms);
query_str := FORMAT('WITH new_names AS ( INSERT INTO %1$I(name) SELECT tn.name FROM tmp_names tn WHERE NOT EXISTS(SELECT 1 FROM %1$I h WHERE h.name = tn.name) RETURNING hid)', h_tbl);
EXECUTE query_str;
SELECT ARRAY_AGG(hid) INTO ids FROM new_names;
DROP TABLE tmp_names;
RETURN ids;
END;
$$ LANGUAGE PLPGSQL;
This is the output I get when I run that function:
psql=# select insert_label('label', array['how', 'now', 'brown', 'cow']);
ERROR: syntax error at end of input
LINE 1: ...SELECT 1 FROM label h WHERE h.name = tn.name) RETURNING hid)
^
QUERY: WITH new_names AS ( INSERT INTO label(name) SELECT tn.name FROM tmp_names tn WHERE NOT EXISTS(SELECT 1 FROM label h WHERE h.name = tn.name) RETURNING hid)
CONTEXT: PL/pgSQL function insert_label(regclass,text[]) line 19 at EXECUTE
The query generated by the dynamic SQL looks like it should be exactly the same as the query from static SQL.
I got the function to work by changing the return value from an array of UUIDs to a table of UUIDs and not using CTE:
CREATE OR REPLACE FUNCTION insert_label(h_tbl REGCLASS, nms TEXT[])
RETURNS TABLE (hid UUID)
AS $$
DECLARE
query_str TEXT;
BEGIN
CREATE TEMP TABLE tmp_names(name TEXT);
INSERT INTO tmp_names SELECT UNNEST(nms);
query_str := FORMAT('INSERT INTO %1$I(name) SELECT tn.name FROM tmp_names tn WHERE NOT EXISTS(SELECT 1 FROM %1$I h WHERE h.name = tn.name) RETURNING hid', h_tbl);
RETURN QUERY EXECUTE query_str;
DROP TABLE tmp_names;
RETURN;
END;
$$ LANGUAGE PLPGSQL;
I don't know if one way is better than the other, returning an array of UUIDs or a table of UUIDs, but at least I got it to work one of those ways. Plus, possibly not using a CTE is more efficient, so it may be better to stick with the version that returns a table of UUIDs.
What I would like to know is why the dynamic query did not work when using a CTE. The query it produced looked like it should have worked.
If anyone can let me know what I did wrong, I would appreciate it.
... why the dynamic query did not work when using a CTE. The query it produced looked like it should have worked.
No, it was only the CTE without (required) outer query. (You had SELECT ARRAY_AGG(hid) INTO ids FROM new_names in the static version.)
There are more problems, but just use this query instead:
INSERT INTO label(name)
SELECT unnest(nms)
ON CONFLICT DO NOTHING
RETURNING hid;
label.name is defined UNIQUE NOT NULL, so this simple UPSERT can replace your function insert_label() completely.
It's much simpler and faster. It also defends against possible duplicates from within your input array that you didn't cover, yet. And it's safe under concurrent write load - as opposed to your original, which might run into race conditions. Related:
How to use RETURNING with ON CONFLICT in PostgreSQL?
I would just use the simple query and replace the table name.
But if you still want a dynamic function:
CREATE OR REPLACE FUNCTION insert_label(_tbl regclass, _nms text[])
RETURNS TABLE (hid uuid)
LANGUAGE plpgsql AS
$func$
BEGIN
RETURN QUERY EXECUTE format(
$$
INSERT INTO %s(name)
SELECT unnest($1)
ON CONFLICT DO NOTHING
RETURNING hid
$$, _tbl)
USING _nms;
END
$func$;
If you don't need an array as result, stick with the set (RETURNS TABLE ...). Simpler.
Pass values (_nms) to EXECUTE in a USING clause.
The tablename (_tbl) is type regclass, so the format specifier %I for format() would be wrong. Use %s instead. See:
Table name as a PostgreSQL function parameter
It may look like a duplicate of existing questions (e.g. This one) but they only deal with passing "new" arguments, not selecting rows from the database.
I have a table, for example:
CREATE TABLE my_table (
id bigserial NOT NULL,
name text,
CONSTRAINT my_table_pkey PRIMARY KEY (id)
);
And a function:
CREATE FUNCTION do_something(row_in my_table) RETURNS void AS
$$
BEGIN
-- does something
END;
$$
LANGUAGE plpgsql;
I would like to run it on data already existing in the database. It's no problem if I would like to use it from another PL/pgSQL stored procedure, for example:
-- ...
SELECT * INTO row_var FROM my_table WHERE id = 123; -- row_var is of type my_table%rowtype
PERFORM do_something(row_var);
-- ...
However, I have no idea how to do it using an "ordinary" query, e.g.
SELECT do_something(SELECT * FROM my_table WHERE id = 123);
ERROR: syntax error at or near "SELECT"
LINE 1: SELECT FROM do_something(SELECT * FROM my_table ...
Is there a way to execute such query?
You need to pass a scalar record to that function, this requires to enclose the actual select in another pair of parentheses:
SELECT do_something( (SELECT * FROM my_table WHERE id = 123) );
However the above will NOT work, because the function only expects a single column (a record of type my_table) whereas select * returns multiple columns (which is something different than a single record with multiple fields).
In order to return a record from the select you need to use the following syntax:
SELECT do_something( (SELECT my_table FROM my_table WHERE id = 123) );
Note that this might still fail if you don't make sure the select returns exactly one row.
If you want to apply the function to more than one row, you can do that like this:
select do_something(my_table)
from my_table;
I would like to use a plpgsql function with a table and several columns as input parameter. The idea is to split the table in chunks and do something with each part.
I tried the following function:
CREATE OR REPLACE FUNCTION my_func(Integer)
RETURNS SETOF my_part
AS $$
DECLARE
out my_part;
BEGIN
FOR i IN 0..$1 LOOP
FOR out IN
SELECT * FROM my_func2(SELECT * FROM table1 WHERE id = i)
LOOP
RETURN NEXT out;
END LOOP;
END LOOP;
RETURN;
END;
$$
LANGUAGE plpgsql;
my_func2() is the function that does some work on each smaller part.
CREATE or REPLACE FUNCTION my_func2(table1)
RETURNS SETOF my_part2 AS
$$
BEGIN
RETURN QUERY
SELECT * FROM table1;
END
$$
LANGUAGE plpgsql;
If I run:
SELECT * FROM my_func(99);
I guess I should receive the first 99 IDs processed for each id.
But it says there is an error for the following line:
SELECT * FROM my_func2(select * from table1 where id = i)
The error is:
The subquery is only allowed to return one column
Why does this happen? Is there an easy way to fix this?
There are multiple misconceptions here. Study the basics before you try advanced magic.
Postgres does not have "table variables". You can only pass 1 column or row at a time to a function. Use a temporary table or a refcursor (like commented by #Daniel) to pass a whole table. The syntax is invalid in multiple places, so it's unclear whether that's what you are actually trying.
Even if it is: it would probably be better to process one row at a time or rethink your approach and use a set-based operation (plain SQL) instead of passing cursors.
The data types my_part and my_part2 are undefined in your question. May be a shortcoming of the question or a problem in the test case.
You seem to expect that the table name table1 in the function body of my_func2() refers to the function parameter of the same (type!) name, but this is fundamentally wrong in at least two ways:
You can only pass values. A table name is an identifier, not a value. You would need to build a query string dynamically and execute it with EXECUTE in a plpgsql function. Try a search, many related answers her on SO. Then again, that may also not be what you wanted.
table1 in CREATE or REPLACE FUNCTION my_func2(table1) is a type name, not a parameter name. It means your function expects a value of the type table1. Obviously, you have a table of the same name, so it's supposed to be the associated row type.
The RETURN type of my_func2() must match what you actually return. Since you are returning SELECT * FROM table1, make that RETURNS SETOF table1.
It can just be a simple SQL function.
All of that put together:
CREATE or REPLACE FUNCTION my_func2(_row table1)
RETURNS SETOF table1 AS
'SELECT ($1).*' LANGUAGE sql;
Note the parentheses, which are essential for decomposing a row type. Per documentation:
The parentheses are required here to show that compositecol is a column name not a table name
But there is more ...
Don't use out as variable name, it's a keyword of the CREATE FUNCTION statement.
The syntax of your main query my_func() is more like psudo-code. Too much doesn't add up.
Proof of concept
Demo table:
CREATE TABLE table1(table1_id serial PRIMARY KEY, txt text);
INSERT INTO table1(txt) VALUES ('a'),('b'),('c'),('d'),('e'),('f'),('g');
Helper function:
CREATE or REPLACE FUNCTION my_func2(_row table1)
RETURNS SETOF table1 AS
'SELECT ($1).*' LANGUAGE sql;
Main function:
CREATE OR REPLACE FUNCTION my_func(int)
RETURNS SETOF table1 AS
$func$
DECLARE
rec table1;
BEGIN
FOR i IN 0..$1 LOOP
FOR rec IN
SELECT * FROM table1 WHERE table1_id = i
LOOP
RETURN QUERY
SELECT * FROM my_func2(rec);
END LOOP;
END LOOP;
END
$func$ LANGUAGE plpgsql;
Call:
SELECT * FROM my_func(99);
SQL Fiddle.
But it's really just a a proof of concept. Nothing useful, yet.
As the error log is telling you.. you can return only one column in a subquery, so you have to change it to
SELECT my_func2(SELECT Specific_column_you_need FROM hasval WHERE wid = i)
a possible solution can be that you pass to funct2 the primary key of the table your funct2 needs and then you can obtain the whole table by making the SELECT * inside the function
I'm trying to write a procedure, which inserts a row in a table and then returns an assigned id.
Table example:
CREATE TABLE sometable (
UUID Id,
varchar field1,
varchar field2)
My function:
CREATE OR REPLACE FUNCTION insert_sometable(varchar pfield1, varchar pfield2)
RETURNS UUID AS $$
DECLARE
pid UUID;
BEGIN
pid=uuid_generate();
insert into sometable(id, field1, field2) values (pid, pfield1, pfield2);
return pid;
END;
$$
LANGUAGE plpgsql;
And i want it to use it like that:
select pid from insert_sometable('_field1_value', '_field2_value')
But now i got no column name of its result, so i can't access it in my ESB service.
I want column called "pid" in result set.
Seems like easy task, but i didn't found any simple solution. Any suggestions?
I think you're just trying to alias the returned column name. If so:
select pid
from insert_sometable('_field1_value', '_field2_value') AS inserted_row(pid);
PostgreSQL permits you to write shorthand for this for functions that return only one column:
select pid
from insert_sometable('_field1_value', '_field2_value') pid;
You shouldn't use a function whose return value is atomic (not a row) in the FROM clause (it's possible, but makes your query less readable). You can add an alias in the SELECT clause too:
select insert_sometable('_field1_value', '_field2_value') as pid;
I need to perform the same operation on several tables. Right now the query looks like this:
create view foobar
as
select this, that
from here
where this=1
union all
select this,that
from there
where this=1
..... .....
and so on, for several tables. All the results are union-ed.
Is there a way to, instead of writing this very long query where it's easy to get something wrong, to write something like
for table in here, there, upthere
do
select this, that from $table where this=1
and then take the union of them all.
I have the query working right now and it's going to take a while, but this is just curiosity and I didn't know how to search for this on Google!
Create a VIEW from UNION of all tables you need and then SELECT from this VIEW.
Additionally you can create a VIEW like this:
CREATE OR REPLACE VIEW table_set AS
SELECT 'table1' as table_name, field1, field2 ...
FROM table1
UNION ALL
SELECT 'table2' as table_name, field1, field2 ...
FROM table2
UNION ALL
SELECT 'table3' as table_name, field1, field2 ...
FROM table3
UNION ALL
...
and SELECT from this VIEW like:
SELECT field1, field3
FROM table_set
WHERE table_name IN ('table2','table4')
AND field5 = 'abc'
A view like #Igor desrcibes is one option.
If performance is of the essence and you don't have a lot of write operations, you could use that view to (re-)create a materialized view.
Or, for shorter syntax and dynamic selection of source tables, you could utilize a plpgsql function like this one:
CREATE OR REPLACE FUNCTION f_select_from_tbls(tables text[], cond int)
RETURNS TABLE (this int, that text) AS
$BODY$
DECLARE
tbl text; -- automatically avoids SQLi
BEGIN
FOREACH tbl IN ARRAY tables LOOP
RETURN QUERY EXECUTE format('
SELECT this, that
FROM %I
WHERE this = $1', tbl)
USING cond;
END LOOP;
END
$BODY$
LANGUAGE plpgsql;
Call:
SELECT * FROM f_select_from_tbls('{t1,t2,nonStandard tablename}', 4);
I assume a number of tables sharing columns of the same name and type (this int, that text) (You didn't define the data types in you question.) The function takes an array of text as tables names and an integer value as condition.
More about Looping Through Arrays in the manual.
More about Returning From a Function in the manual.
With dynamic SQL you have to be wary of SQL injection. That threat is neutralized here in two ways:
Table names in the array tables are injected with the function format() which properly quotes any non-standard table name
The value in parameter cond is passed in as is via USING, thereby making SQLi impossible.
Variant with VARIADIC parameter
The purpose is to make the function call simpler. You can hand in a list of text variables instead of building an array from them (array is built internally from the parameters). That's all.
CREATE OR REPLACE FUNCTION f_select_from_tbls2(cond int, VARIADIC tables text[])
RETURNS TABLE (this int, that text) AS
$BODY$
DECLARE
tbl text; -- automatically avoids SQLi
BEGIN
FOREACH tbl IN ARRAY tables LOOP
RETURN QUERY EXECUTE format('
SELECT this, that
FROM %I
WHERE this = $1', tbl)
USING cond;
END LOOP;
END
$BODY$
LANGUAGE plpgsql;
I placed tables last in this case to have an open end for the VARIADIC parameter.
Call:
SELECT * FROM f_select_from_tbls2(4, 't1','t2','iLLegal name')