I have n tables sharing a least one column name ("Date"), I want to create a function which can make a
select column1, column2, ..., columnx from myfunction('table_name','date_value')
First, tried with just table name parameter (excerpt from another post on this website) :
CREATE OR REPLACE FUNCTION test(_tbl regclass, OUT result integer) AS
$func$
BEGIN
EXECUTE format('SELECT (EXISTS (SELECT FROM %s))::int', _tbl)
INTO result;
END
$func$ LANGUAGE plpgsql;
and called with SELECT test('"vDxi"'); but the result is :
ERROR: syntax error at or near "FROM"
LINE 1: SELECT (EXISTS (SELECT FROM "vDxi"))::int
^
QUERY: SELECT (EXISTS (SELECT FROM "vDxi"))::int
CONTEXT: PL/pgSQL function test(regclass) line 3 at EXECUTE statement
SQL state: 42601
Since I'm a real newbie in PLSQL, I don't know where the error is (tried a SELECT * ... with no success).
And the calling query does not permit me to select column names, if I can make it working...
Related
I would like to create a trigger called before an insert in my db.
The trigger function check one condition.
if the conditions is true :
CANNOT insert
else :
CAN insert
To check my condition I need RECURSIVITY, This is what I've done :
CREATE OR REPLACE FUNCTION trigger_check_relation()
RETURNS TRIGGER AS
$$BEGIN
WITH RECURSIVE parent_list AS (
SELECT relation.parent
FROM relation
WHERE relation.child = 9817
UNION
SELECT r.parent FROM relation r
JOIN parent_list on parent_list.parent = r.child
)
SELECT name FROM component WHERE _id in (SELECT parent FROM parent_list);
IF 9817 in (SELECT _id FROM component WHERE _id in (SELECT parent FROM parent_list))
THEN RETURN OLD;
ELSE
RETURN NEW;
END IF;
END;$$ LANGUAGE plpgsql;
I can create my trigger and my function but when I run it have :
ERROR: query has no destination for result data HINT: If you want to
discard the results of a SELECT, use PERFORM instead. CONTEXT:
PL/pgSQL function trigger_check_relation() line 3 at SQL statement SQL
state: 42601
Could you please help me to understand and fix this issue ?
You have two statements in the function: the first one is “WITH RECURSIVE parent_list … SELECT name FROM …” but it doesn't do anything with the result such as assigning it to a variable.
I think you may need to move the WITH clause into the condition:
IF 9817 in (
WITH RECURSIVE parent_list AS (
SELECT relation.parent
FROM relation
WHERE relation.child = 9817
UNION
SELECT r.parent FROM relation r
JOIN parent_list on parent_list.parent = r.child
)
SELECT _id FROM component
WHERE _id in (SELECT parent FROM parent_list)
)
…
I'm trying to use CTE in PostgreSQL function and returning the CTE as table. But I couldn't manage to compile the function as it says ERROR: syntax error at end of input in the select query. Could someone point me what I'm missing here.
CREATE OR REPLACE FUNCTION my_func(name varchar) RETURNS TABLE (hours integer) AS $$
BEGIN
WITH a AS (
SELECT hours FROM name_table tbl where tbl.name= name; <- giving error here
)
RETURN QUERY SELECT hours FROM a;
END;
$$ LANGUAGE plpgsql;
PS: I'm on PostgreSQL 9.6 if that helps.
The CTE expression is part of the query, so it needs to come immediately after the return query clause, not before it. Additionally, to avoid syntax errors later on, you should select a parameter name that ins't ambiguous with the names of the columns, and fully qualify the columns you're querying:
CREATE OR REPLACE FUNCTION my_func(v_name varchar)
RETURNS TABLE (hours integer) AS $$
BEGIN
RETURN QUERY WITH a AS (
SELECT tbl.hours
FROM name_table tbl
WHERE name = v_name
)
SELECT a.hours FROM a;
END;
$$ LANGUAGE plpgsql;
The function is created fine, but when I try to execute it, I get this error:
ERROR: relation "column1" does not exist
SQL state: 42P01
Context: SQL statement "ALTER TABLE COLUMN1 ADD COLUMN locationZM geography (POINTZM, 4326)"
PL/pgSQL function addlocationzm() line 6 at SQL statement
Code:
CREATE OR REPLACE FUNCTION addlocationZM()
RETURNS void AS
$$
DECLARE
COLUMN1 RECORD;
BEGIN
FOR COLUMN1 IN SELECT f_table_name FROM *schema*.geography_columns WHERE type LIKE 'Point%' LOOP
ALTER TABLE COLUMN1 ADD COLUMN locationZM geography (POINTZM, 4326);
END LOOP;
END;
$$
LANGUAGE 'plpgsql';
SELECT addlocationZM()
I'm probably just being dumb, but I've been at this for a while now and I just can't get it. The SELECT f_table_name ... statement executed on its own returns 58 rows of a single column, each of which is the name of a table in my schema. The idea of this is to create a new column, type PointZM, in each table pulled by the SELECT.
The function would work like this:
CREATE OR REPLACE FUNCTION addlocationZM()
RETURNS void AS
$func$
DECLARE
_tbl text;
BEGIN
FOR _tbl IN
SELECT f_table_name FROM myschema.geography_columns WHERE type LIKE 'Point%'
LOOP
EXECUTE
format('ALTER TABLE %I ADD COLUMN location_zm geography(POINTZM, 4326)', _tbl);
END LOOP;
END
$func$ LANGUAGE plpgsql;
Note how I use a simple text variable to simplify matters. You don't need the record to begin with.
If it's a one-time operation, use a DO command instead of creating a function:
DO
$do$
BEGIN
EXECUTE (
SELECT string_agg(
format(
'ALTER TABLE %I ADD COLUMN location_zm geography(POINTZM, 4326);'
, f_table_name)
, E'\n')
FROM myschema.geography_columns
WHERE type LIKE 'Point%'
);
END
$do$;
This is concatenating a single string comprised of all commands (separated with ;) for a single EXECUTE.
Or, especially while you are not familiar with plpgsql and dynamic SQL, just generate the commands, copy/paste the result and execute as 2nd step:
SELECT 'ALTER TABLE '
|| quote_ident(f_table_name)
|| ' ADD COLUMN locationZM geography(POINTZM, 4326);'
FROM myschema.geography_columns
WHERE type LIKE 'Point%';
(Demonstrating quote_ident() this time.)
Related:
Table name as a PostgreSQL function parameter
Aside: Unquoted CaMeL-case identifiers like locationZM or your function name addlocationZM may not be such a good idea:
Are PostgreSQL column names case-sensitive?
I am venturing into pg/sql for the first time and I wrote this function that (attempts) to return results from a query that uses a JOIN.
The query works when I run on it self. However, when it is running within the function I wrote it complains about a missing "FROM". I also used table aliases hoping that it may fix the problem but it did not.
I am getting information from the following tables and rows.
TABLE | ROW
---------------------------------------
banned_users | banned_lcl_account
---------------------------------------
rhost_active_users | active_users
Here is the error when I run my function
ERROR: missing FROM-clause entry for table "a_u"
LINE 1: SELECT LOOP_V.a_u.ipaddress
And here is the function I wrote.
CREATE OR REPLACE FUNCTION GET_BANNED_ACTIVE_USERS()
RETURNS TABLE
(
SRC_HOST TEXT,
DST_HOST TEXT
)
AS $$
DECLARE
LOOP_V RECORD;
BEGIN
FOR LOOP_V IN (
select
b_u.banned_lcl_account,
a_u.ipaddress,
a_u.hostip,
date_trunc ('second', a_u.time_captured)
from banned_users as b_u
inner join rhost_active_users as a_u
on b_u.banned_lcl_account = a_u.active_users
and a_u.hostip <> 'TTY Login'
or a_u.hostip <> 'Local PTS Login'
)
LOOP
SRC_HOST := LOOP_V.a_u.ipaddress;
DST_HOST := LOOP_V.a_u.hostip;
END LOOP;
END; $$
LANGUAGE 'plpgsql';
--select CHECK_BANNED_ACCOUNTS()
your problem is this:
SRC_HOST := LOOP_V.a_u.ipaddress;
DST_HOST := LOOP_V.a_u.hostip;
The record that is used for the loop variable only contains column names, not their table aliases. So the record only has the fields: ipaddress and hostip. So your immediate problem can be fixed using:
SRC_HOST := LOOP_V.ipaddress;
DST_HOST := LOOP_V.hostip;
However the whole function is overly complex. You don't need the loop nor do you need PL/pgSQL. This can be written as a simple SQL function:
CREATE OR REPLACE FUNCTION GET_BANNED_ACTIVE_USERS()
RETURNS TABLE (SRC_HOST TEXT,DST_HOST TEXT)
AS $$
select a_u.ipaddress,
a_u.hostip,
from banned_users as b_u
join rhost_active_users as a_u
on b_u.banned_lcl_account = a_u.active_users
and (a_u.hostip <> 'TTY Login' or a_u.hostip <> 'Local PTS Login')
$$
LANGUAGE sql;
Note that I also removed the unused columns from the SELECT statement in order to match the function's signature of returning two columns
Some additional notes:
--select CHECK_BANNED_ACCOUNTS()
This is wrong, you need to use a set returning function like a table:
select *
from CHECK_BANNED_ACCOUNTS();
And finally:
LANGUAGE 'plpgsql';
The language name is an identifier, do not enclose it in single quotes:
LANGUAGE plpgsql;
or
LANGUAGE sql;
I have created a custom type
CREATE TYPE rc_test_type AS (a1 bigint);
and a function
CREATE OR REPLACE FUNCTION public.rc_test_type_function(test_table character varying, dummy integer)
RETURNS rc_test_type AS
$BODY$
DECLARE
ret rc_test_type;
query text;
BEGIN
query := 'SELECT count(*) from ' || test_table ;
EXECUTE query into ret.a1;
RETURN ret;
END $BODY$
LANGUAGE plpgsql VOLATILE
If I run
SELECT * FROM rc_test_type_function('some_table', 1);
I get
"a1"
1389
So far so good.
If I run
SELECT p FROM (SELECT rc_test_type_function('some_table', s.step) AS p
FROM some_other_table s) foo;
I get
"p"
"(1389)"
"(1389)"
since 'some_other_table' has just two records. Fine.
But then if I try
SELECT p.a1 FROM (select rc_test_type_function('some_table', s.step) AS p
FROM some_other_table s) foo;
I get the error
missing FROM-clause entry in subquery for table »p«
which I find strange since the subquery has not changed.
Two questions:
Can anyone explain what's going on?
How do I extract the field value a1 from the returned array?
Use parentheses around the composite type:
SELECT (p).a1
FROM (SELECT rc_test_type_function('some_table', s.step) AS p
FROM some_other_table s
) foo;
Even though your type has just a single column is still a composite type - with its own column name. Doesn't make a lot of sense, but that's how you built it.
(You might want to just use a simple type or maybe a DOMAIN instead.)
Quoting the manual here:
(compositecol).somefield
(mytable.compositecol).somefield
The parentheses are required here to show that compositecol is a column name not a
a table name, or that mytable is a table name not a schema name in the second case.
Proper function
Omitting the part with the composite type, your function would be safer, simpler and faster this way:
CREATE OR REPLACE FUNCTION foo(test_table varchar, dummy int, OUT p bigint)
AS
$func$
BEGIN
EXECUTE format('SELECT count(*) from %I', test_table) -- !avoid SQLi!
INTO p;
END
$func$ LANGUAGE plpgsql;
Avoid SQL injection with dynamic SQL!
An OUT parameter simplifies the syntax in this case. You don't need a DECLARE clause at all, and no RETURN either
Even better
CREATE OR REPLACE FUNCTION foo(test_table regclass, dummy int, OUT p bigint)
AS
$func$
BEGIN
EXECUTE 'SELECT count(*) from ' || test_table
INTO p;
END
$func$ LANGUAGE plpgsql;
By using the object identifier regclass this would also work with schema-qualified table names. And SQLi is not possible to begin with. The function would fail immediately if the table name is illegal and it is quoted automatically when converted to text automatically.