I have created a PL/pgSQL function that accepts two column names, a "relation", and two table names. It finds distinct rows in one table and inserts them in to a temporary table, deletes any row with a null value, and sets all values of one column to relation. I have the first part of the process using this function.
create or replace function alt_edger(s text, v text, relation text, tbl text, tbl_src text)
returns void
language plpgsql as
$func$
begin
raise notice 's: %, v: %, tbl: %, tbl_src: %', s,v,tbl,tbl_src;
execute ('insert into '||tbl||' ("source", "target") select distinct "'||s||'","'||v||'" from '||tbl_src||'');
execute ('DELETE FROM '||tbl||' WHERE "source" IS null or "target" is null');
end
$func$;
It is executed as follows:
-- create a temporary table and execute the function twice
drop table if exists temp_stack;
create temporary table temp_stack("label" text, "source" text, "target" text, "attr" text, "graph" text);
select alt_edger('x_x', 'y_y', ':associated_with', 'temp_stack','pg_check_table' );
select alt_edger('Document Number', 'x_x', ':documents', 'temp_stack','pg_check_table' );
select * from temp_stack;
Note that I didn't use relation, yet. The INSERT shall also assign relation, but I can't figure out how to make that happen to get something like:
label
source
target
attr
graph
:associated_with
638000
ARAS
:associated_with
202000
JASE
:associated_with
638010
JASE
:associated_with
638000
JASE
:associated_with
202100
JASE
:documents
A
638010
:documents
A
202000
:documents
A
202100
:documents
B
638000
:documents
A
638000
:documents
B
124004
:documents
B
202100
My challenges are:
How to integrate relation in the INSERT? When I try to use VALUES and comma separation I get an "error near select".
How to allow strings starting with ":" in relation? I'm anticipating here, the inclusion of the colon has given me challenges in the past.
How can I do this? Or is there a better approach?
Toy data model:
drop table if exists pg_check_table;
create temporary table pg_check_table("Document Number" text, x_x int, y_y text);
insert into pg_check_table values ('A',202000,'JASE'),
('A',202100,'JASE'),
('A',638010,'JASE'),
('A',Null,'JASE'),
('A',Null,'JASE'),
('A',202100,'JASE'),
('A',638000,'JASE'),
('A',202100,'JASE'),
('B',638000,'JASE'),
('B',202100,null),
('B',638000,'JASE'),
('B',null,'ARAS'),
('B',638000,'ARAS'),
('B',null,'ARAS'),
('B',638000,null),
('B',124004,null);
alter table pg_check_table add row_num serial;
select * from pg_check_table;
-- DROP FUNCTION alt_edger(_s text, _v text, _relation text, _tbl text, _tbl_src text)
CREATE OR REPLACE FUNCTION alt_edger(_s text, _v text, _relation text, _tbl text, _tbl_src text, OUT row_count int)
LANGUAGE plpgsql AS
$func$
DECLARE
_sql text := format(
'INSERT INTO pg_temp.%3$I (label, source, target)
SELECT DISTINCT $1, %1$I, %2$I FROM pg_temp.%4$I
WHERE (%1$I, %2$I) IS NOT NULL'
, _s, _v, _tbl, _tbl_src);
BEGIN
-- RAISE NOTICE '%', _sql; -- debug
EXECUTE _sql USING _relation;
GET DIAGNOSTICS row_count = ROW_COUNT; -- return number of inserted rows
END
$func$;
db<>fiddle here
Most importantly, use format() to concatenate your dynamic SQL commands safely. And use the format specifier %I for identifiers. This way, SQL injection is not possible and identifiers are double-quoted properly - preserving non-standard names like Document Number. That's where your original failed.
We could concatenate _relation as string to be inserted into label, too. But the preferable way to pass values to EXECUTE is with the USING clause. $1 inside the SQL string passed to EXECUTE is a placeholder for the first USING argument. Not to be confused with $1 referencing function parameters in the context of the function body outside EXECUTE! (You can pass any string, leading colon (:) does not matter, the string is not interpreted when done right.)
See:
Format specifier for integer variables in format() for EXECUTE?
Table name as a PostgreSQL function parameter
I replaced the DELETE in your original with a WHERE clause to the SELECT of the INSERT. Don't insert rows in the first place, instead of deleting them again later.
(%1$I, %2$I) IS NOT NULL only qualifies when both values are NOT NULL.
About that:
Check if a Postgres composite field is null/empty
Don't use the prefix "pg_" for your table names. That's what Postgres uses for system tables. Don't mess with those.
I schema-qualify known temporary tables with pg_temp. That's typically optional as the temporary schema comes first in the search_path by default. But that can be changed (maliciously), and then the table name would resolve to any existing regular table of the same name in the search_path. So better safe than sorry. See:
How does the search_path influence identifier resolution and the "current schema"
I made the function return the number of inserted rows. That's totally optional!
Since I do that with an OUT parameter, I am allowed to skip the RETURNS clause. See:
Can I make a plpgsql function return an integer without using a variable?
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.
I'm working on what shouldn't be too difficult an SQL function: it takes a few parameters to find a specific course in a table, counts how many people are in that course, compares it to the course's maximum capacity, and returns 1 or 0 as appropriate:
drop function if exists room_for_more_students(the_class_name varchar, the_semester_code int);
create function room_for_more_students(the_class_name varchar, the_semester_code int)
returns int as $BODY$
begin
select * from class_offerings as match_table
where class_name = the_class_name and semester_code = the_semester_code;
select count(student_id) from match_table as num_students_in_class;
select avg(maximum_capacity) from match_table as num_students_allowed_in_class;
--These will all be the same so "avg" just means "the maximum capacity for the class"
if num_students_in_class < num_students_allowed_in_class then return 1;
else return 0;
end if;
end
$BODY$
language plpgsql;
This doesn't really seem like it should be all that complex to implement, and the function creates without issue, but every time I try and invoke it through psycopg2 I receive:
ProgrammingError: query has no destination for result data
HINT: If you want to discard the results of a SELECT, use PERFORM instead
I have tried experimenting with PERFORM instead, but any combination I try seems to either keep the same issue or create a host of new ones. I've also done some research on this as there are a few other posts about the same issue, but the majority of the time the answer seems to be that the user hasn't added specific return statements, which I have. I'm completely out of ideas and would appreciate any input possible.
For your case, you must declare some variable and assign it with the result of query. You can not run a query without assign its result to nowhere.
I update your function as below:
drop function if exists room_for_more_students(the_class_name varchar, the_semester_code int);
create function room_for_more_students(the_class_name varchar, the_semester_code int)
returns int as
$BODY$
DECLARE
num_students_allowed_in_class numeric;
num_students_in_class numeric;
begin
WITH match_table AS (
select *
from class_offerings
where class_name = the_class_name and semester_code = the_semester_code
)
select count(student_id), avg(maximum_capacity)
INTO num_students_in_class, num_students_allowed_in_class
from match_table;
if num_students_in_class
Hopefully it match your request!
The Problem: I have many delete lines in a PostgreSQL script where I am deleting data related to the same item in the database. Example:
delete from <table> where <column>=180;
delete from <anothertable> where <column>=180;
...
delete from <table> where <column>=180;
commit work;
There are about 15 delete statements deleting data that references <column>=180.
I have tried to replace the 180 with a variable so that I only have to change the variable, instead of all the lines in the code (like any good programmer would do). I can't seem to figure out how to do it, and it's not working.
NOTE: I am very much a SQL novice (I rarely use it), so I know there's probably a better way to do this, but please enlighten me on how I can fix this problem.
I have used these answers to try and fix it with no luck: first second third. I've even gone to the official PostgreSQL documentation, with no luck.
This is what I'm trying (these lines are just for testing and not in the actual script):
DO $$
DECLARE
variable INTEGER:
BEGIN
variable := 101;
SELECT * FROM <table> WHERE <column> = variable;
END $$;
I've also tried just delcaring it like this:
DECLARE variable INTEGER := 101;
Whenever I run the script after replacing one of the numbers with a variable this is the error I get:
SQL Error [42601]: ERROR: query has no destination for result data
Hint: If you want to discard the results of a SELECT, use PERFORM instead.
Where: PL/pgSQL function inline_code_block line 6 at SQL statement
Can someone tell me where I'm going wrong? It would be nice to only have to change the number in the variable, instead of in all the lines in the script, and I just can't seem to figure it out.
As #Vao Tsun said, you must define a destination to your SELECT statement. Use PERFORM otherwise:
--Test data
CREATE TEMP TABLE my_table (id, description) AS
VALUES (1, 'test 1'), (2, 'test 2'), (101, 'test 101');
--Example procedure
CREATE OR REPLACE FUNCTION my_procedure(my_arg my_table) RETURNS VOID AS $$
BEGIN
RAISE INFO 'Procedure: %,%', my_arg.id, my_arg.description;
END
$$ LANGUAGE plpgsql;
DO $$
DECLARE
variable INTEGER;
my_record my_table%rowtype;
BEGIN
variable := 101;
--Use your SELECT inside a LOOP to work with result
FOR my_record IN SELECT * FROM my_table WHERE id = variable LOOP
RAISE INFO 'Loop: %,%', my_record.id, my_record.description;
END LOOP;
--Use SELECT to populate a variable.
--In this case you MUST define a destination to your result data
SELECT * INTO STRICT my_record FROM my_table WHERE id = variable;
RAISE INFO 'Select: %,%', my_record.id, my_record.description;
--Use PERFORM instead of SELECT if you want to discard result data
--It's often used to call a procedure
PERFORM my_procedure(t) FROM my_table AS t WHERE id = variable;
END $$;
--DROP FUNCTION my_procedure(my_table);
I write function which will add new version of file to the table. The user of function should be call update_file(path_to_file). I need load all content from file and store it to the table like:
insert into file(new_version, new_content);
Now I could find a way to read all content and put it to the table. The COPY command allows me get the file concatenate as sequence of lines.
I see only way to contact the lines, but I don't like this approach.
Is there some good solution?
Thanks.
I found the following solution, hope it will be helpful for someone.
CREATE OR REPLACE FUNCTION read_file_utf8(path CHARACTER VARYING)
RETURNS TEXT AS $$
DECLARE
var_file_oid OID;
var_record RECORD;
var_result BYTEA := '';
BEGIN
SELECT lo_import(path)
INTO var_file_oid;
FOR var_record IN (SELECT data
FROM pg_largeobject
WHERE loid = var_file_oid
ORDER BY pageno) LOOP
var_result = var_result || var_record.data;
END LOOP;
PERFORM lo_unlink(var_file_oid);
RETURN convert_from(var_result, 'utf8');
END;
$$ LANGUAGE plpgsql;
As another solution may be using of $tag$...$tag$ syntax. It is PostgreSQL's feature, that called 'dollar quoting', it allows you insert any text between tags without quoting. For more details, please visit https://www.postgresql.org/docs/9.5/static/sql-syntax-lexical.html, 4.1.2.4. Dollar-quoted String Constants