PL/PgSQL treats variable name as column name in CREATE VIEW - sql

I have a function similar to this:
CREATE FUNCTION func(TEXT) RETURNS TEXT AS $$
DECLARE
v_name1 ALIAS FOR $1;
BEGIN
SELECT * from table1 WHERE names_column = v_name1;
RETURN v_name1;
END;
$$ LANGUAGE plpgsql;
If I do
SELECT func('Alex');
It should run:
SELECT * from table1 WHERE names_column = 'Alex';
But instead I get this error:
ERROR: column "v_name1" does not exist
LINE 2: SELECT * FROM table1 WHERE names_column = v_name1;
How do I tell Postgres to get the TEXT inside the variable to compare?
[EDIT]This is the function I have so far that produces the error:
CREATE OR REPLACE FUNCTION get_champ(end_date DATE, champ_name TEXT) RETURNS TEXT AS $$
DECLARE
new_champ_name TEXT;
id_of_new_champ INTEGER;
date_of_new_champ DATE;
BEGIN
CREATE OR REPLACE VIEW champ AS
SELECT * FROM basketball2 WHERE visitor = champ_name OR home = champ_name;
SELECT MIN(id) INTO id_of_new_champ FROM champ WHERE winner <> champ_name;
SELECT date INTO date_of_new_champ FROM basketball2 WHERE id = id_of_new_champ;
SELECT winner INTO new_champ_name FROM basketball2 WHERE id = id_of_new_champ;
RETURN new_champ_name;
END;
$$ LANGUAGE plpgsql;

CREATE OR REPLACE VIEW is not a plannable statement in PostgreSQL, it's DDL.
This means it cannot accept query parameters. So PL/PgSQL's normal translation of PL/PgSQL variables into query parameters is not available. It takes the query literally as written.
To get the result you want, you must use dynamic SQL with EXECUTE, e.g.
EXECUTE format('CREATE OR REPLACE VIEW champ AS
SELECT * FROM basketball2 WHERE visitor = %L OR home = %L', champ_name, champ_name);
... but in this case, creating a view in the first place appears to be nonsensical. Why would you do that, it gains you nothing whatsoever. Just write:
SELECT MIN(id)
INTO id_of_new_champ
FROM basketball2
WHERE (visitor = champ_name OR home = champ_name)
AND winner <> champ_name;
Also, never "simplify" or "anonymize" your code without clearly saying you have done so in the question, and *testing that the problem still happens with the simplified code. Otherwise you're just wasting your time and everybody else's.

Related

PostgreSQL: migrating ms sql xml query to postgresql query

I am trying to migrate the following MS SQL Server procedure to a PostgreSQL function.
CREATE PROCEDURE [dbo].[GMC]
AS
BEGIN
DECLARE #LID VARCHAR(3);
DECLARE #xml XML = '<XMLProf><CID>840</CID><MD>101113</MD></XMLProf>';
SELECT #LID = Pay.b.value('.','Varchar(3)')
FROM #xml.nodes('/XMLProf/CID') as Pay(b)
SELECT 'Return Value' = #LID
END
I have tried to convert to the following but it doesn't work.
CREATE OR REPLACE FUNCTION dbo.GMC()
RETURNS void
AS
$BODY$
DECLARE
LID VARCHAR(3);
bxml XML = '<XMLProf><CID>840</CID><MD>101113</MD></XMLProf>';
BEGIN
SELECT LID = Pay.b.value('.','Varchar(3)')
FROM XMLTABLE('/XMLProf/CID' PASSING bxml) as Pay(b)
SELECT 'Return Value' = LID
end;
$BODY$
LANGUAGE plpgsql;
Edit:
The result I am expecting is "840"
The error that I am getting is a syntax error:
ERROR: syntax error at or near ")"
LINE 12: FROM XMLTABLE('/XMLProf/CID' PASSING bxml) as Pay(b)
Can someone please tell me how can I accomplish this. Any help is really appreciated.
If you want to return something from a function, you can't use returns void. As XML is character data, returns text makes more sense.
As you only want to return a single value, xmltable() isn't really needed. And you don't need PL/pgSQL either:
CREATE OR REPLACE FUNCTION dbo.gmc()
RETURNS text
AS
$BODY$
select (xpath('/XMLProf/CID/text()',
'<XMLProf><CID>840</CID><MD>101113</MD></XMLProf>'::xml))[1]::text;
$BODY$
LANGUAGE sql;
xpath() returns an array of all matches, that's why the [1] is needed to pick out the first match.
Assuming you actually want to pass the XML to the function, you can use this:
CREATE OR REPLACE FUNCTION dbo.gmc(p_xml xml)
RETURNS text
AS
$BODY$
select (xpath('/XMLProf/CID/text()', p_xml))[1]::text;
$BODY$
LANGUAGE sql;
If you're basing your response element on the string size of the inner nodes of XMLProf, you might wanna take a look at XPATH and UNNEST.
CREATE OR REPLACE FUNCTION gmc() RETURNS text
AS $BODY$
WITH j AS (
SELECT
UNNEST(XPATH('//XMLProf/node()',
'<XMLProf><CID>840</CID><MD>101113</MD></XMLProf>'::XML)) AS rawxml
) SELECT (XPATH('//text()',j.rawxml))[1]::TEXT FROM j
WHERE CHAR_LENGTH((XPATH('//text()',j.rawxml))[1]::TEXT) = 3
$BODY$
LANGUAGE sql;
Testing ..
db=# SELECT * FROM gmc();
gmc
-----
840
(1 Zeile)
If you know exactly where to look and the string length is irrelevant, just get rid of the UNNEST and use the XPATH /XMLProf/CID/text() as pointed out by #a_horse_with_no_name.

query has no destination for result data in a function that has a set of instructions in postgresql

I am trying to automate a set of sentences that I execute several times a day. For this I want to put them in a postgres function and just call the function to execute the sentences consecutively. If everything runs OK then in the end return the SUCCESS value. The following function replicates my idea and the error I am getting when executing the function:
CREATE OR REPLACE FUNCTION createTable() RETURNS int AS $$
BEGIN
DROP TABLE IF EXISTS MY_TABLE;
CREATE TABLE MY_TABLE
(
ID integer
)
WITH (
OIDS=FALSE
);
insert into MY_TABLE values(1);
select * from MY_TABLE;
RETURN 'SUCCESS';
END;
$$ LANGUAGE plpgsql;
Invocation:
select * from createTable();
With my ignorance of postgresql I would expect to obtain the SUCCESS value as a return (If everything runs without errors). But the returned message causes me confusion, isn't it the same as a function in any other programming language? When executing the function I get the following message:
query has no destination for result data Hint: If you want to
discard the results of a SELECT, use PERFORM instead.
query has no destination for result data Hint: If you want to discard the results of a SELECT, use PERFORM instead.
You are getting this error because you do not assign the results to any variable in the function. In a function, you would typically do something like this instead:
select * into var1 from MY_TABLE;
Therefore, your function would look something like this:
CREATE OR REPLACE FUNCTION createTable() RETURNS int AS $$
DECLARE
var1 my_table%ROWTYPE;
BEGIN
DROP TABLE IF EXISTS MY_TABLE;
CREATE TABLE MY_TABLE
(
ID integer
)
WITH (
OIDS=FALSE
);
insert into MY_TABLE values(1);
select * into var1 from MY_TABLE;
<do something with var1>
RETURN 'SUCCESS';
END;
$$ LANGUAGE plpgsql;
Otherwise, if you don't put the results into a variable, then you're likely hoping to achieve some side effect (like advancing a sequence or firing a trigger somehow). In that case, plpgsql expects you to use PERFORM instead of SELECT
Also, BTW your function RETURNS int but at the bottom of your definition you RETURN 'SUCCESS'. SUCCESS is a text type, not an int, so you will eventually get this error once you get past that first error message -- be sure to change it as necessary.

pgadmin, sql, function inputday by table name

I made a function im pgadmin
create or replace function get_source2(a text)
returns integer as
$$
declare
a text;
geom geometry;
begin
select get_source(geom)
from a;
end
$$
language plpgsql;
I want input a by table name
How can I do?
I try to like this
select get_source2('postgis.center')
but
ERROR: relation "a" does not exist
LINE 2: from a
help me
You need to call it as part of a regular select statement:
select get_source(geom), name, place
from location;
But that will be very inefficient as you are running the select inside the function for each and every row in the location table.

Input table for PL/pgSQL function

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

Passing a ResultSet into a Postgresql Function

Is it possible to pass the results of a postgres query as an input into another function?
As a very contrived example, say I have one query like
SELECT id, name
FROM users
LIMIT 50
and I want to create a function my_function that takes the resultset of the first query and returns the minimum id. Is this possible in pl/pgsql?
SELECT my_function(SELECT id, name FROM Users LIMIT 50); --returns 50
You could use a cursor, but that very impractical for computing a minimum.
I would use a temporary table for that purpose, and pass the table name for use in dynamic SQL:
CREATE OR REPLACE FUNCTION f_min_id(_tbl regclass, OUT min_id int) AS
$func$
BEGIN
EXECUTE 'SELECT min(id) FROM ' || _tbl
INTO min_id;
END
$func$ LANGUAGE plpgsql;
Call:
CREATE TEMP TABLE foo ON COMMIT DROP AS
SELECT id, name
FROM users
LIMIT 50;
SELECT f_min_id('foo');
Major points
The first parameter is of type regclass to prevent SQL injection. More info in this related answer on dba.SE.
I made the temp table ON COMMIT DROP to limit its lifetime to the current transaction. May or may not be what you want.
You can extend this example to take more parameters. Search for code examples for dynamic SQL with EXECUTE.
-> SQLfiddle demo
I would take the problem on the other side, calling an aggregate function for each record of the result set. It's not as flexible but can gives you an hint to work on.
As an exemple to follow your sample problem:
CREATE OR REPLACE FUNCTION myMin ( int,int ) RETURNS int AS $$
SELECT CASE WHEN $1 < $2 THEN $1 ELSE $2 END;
$$ LANGUAGE SQL STRICT IMMUTABLE;
CREATE AGGREGATE my_function ( int ) (
SFUNC = myMin, STYPE = int, INITCOND = 2147483647 --maxint
);
SELECT my_function(id) from (SELECT * FROM Users LIMIT 50) x;
It is not possible to pass an array of generic type RECORD to a plpgsql function which is essentially what you are trying to do.
What you can do is pass in an array of a specific user defined TYPE or of a particular table row type. In the example below you could also swap out the argument data type for the table name users[] (though this would obviously mean getting all data in the users table row).
CREATE TYPE trivial {
"ID" integer,
"NAME" text
}
CREATE OR REPLACE FUNCTION trivial_func(data trivial[])
RETURNS integer AS
$BODY$
DECLARE
BEGIN
--Implementation here using data
return 1;
END$BODY$
LANGUAGE 'plpgsql' VOLATILE;
I think there's no way to pass recordset or table into function (but I'd be glad if i'm wrong). Best I could suggest is to pass array:
create or replace function my_function(data int[])
returns int
as
$$
select min(x) from unnest(data) as x
$$
language SQL;
sql fiddle demo