"SELECT something INTO variable" in trigger function's code creates a table named variable - sql

I discovered a mysterious table named num in my database which has one column named count. I had no idea how it got there, then I realized it might be caused by a misbehaving trigger.
I have a trigger function:
DECLARE num integer := 0;
BEGIN
IF ... THEN
SELECT COUNT(*) INTO num FROM ...
END IF;
IF num > 1 THEN
DELETE FROM ...
END IF;
RETURN NEW;
END;
As you can see my purpose is to count the rows returned by a query and perform some operation if it is greater than one.
Can this faulty code be responsible for the unwanted table created? If so, how to fix this?

SELECT ... INTO foo in PL/pgSQL stores the result of the SELECT in a PL/pgSQL variable foo. Whereas SELECT ... INTO foo run as an ordinary SQL statement creates a table foo to store the result.
This is what caused the confusion, the table was created when I was testing the SQL statements from the trigger function manually against the DB.

Related

Table variable doesn't exist even though it clearly defined in PostgreSQL

I tried to create loop from table variable.
do
$$
DECLARE
modified IDType;
BEGIN
INSERT into modified (id)
SELECT i.id FROM item i WHERE i.id in ('55D6F516-7D8F-4DF3-A4E5-1E3F505837A1', 'FFE2A4D3-267C-465F-B4B4-C7BB2582F1BC');
for p in select id from modified
loop
raise notice (p.id);
end loop;
end;
$$ LANGUAGE plpgsql;
the problem is there has an error that says:
SQL Error [42P01]: ERROR: relation "modified" does not exist
Where: PL/pgSQL function inline_code_block line 5 at SQL statement
what I expected is the variable can be used to loop and runs normally.
this is the full query that you can try: http://sqlfiddle.com/#!17/9caba/3
I made the query in DBEAVER app, it will have some different error message.
I suggest you can experiment with it outside sqlfiddle.
There is no such thing as a "table variable" in Postgres. You can define a record that has a the type of a table but that is something completely different.
Even if idtype is the name of a table or a record type, it still holds a single value (in case of a record, it would be a single record with multiple fields)
Why would you expect a scalar variable to be usable in a loop? Are you looking for an
array?
To loop over an array use foreach
The SELECT also seems quite strange. You can assign a value to an array directly.
The parameter for raise notice needs to be a string. If you want to "print" a variable, you need to use a placeholder in the string.
So maybe you are looking for:
do
$$
DECLARE
modified idtype[];
id idtype;
BEGIN
modified := array['55D6F516-7D8F-4DF3-A4E5-1E3F505837A1', 'FFE2A4D3-267C-465F-B4B4-C7BB2582F1BC'];
foreach id in array modified
loop
raise notice '%', id;
end loop;
end;
$$ LANGUAGE plpgsql;

PL/pgSQL function won't run correctly outside pgAdmin

I created a PL/pgSQL function in PostgreSQL 10.4 using pgAdmin. It returns a query and updates entries in the same table. Called in pgAdmin, it runs as expected. When run from external code, the table is returned, but the updates never run. Wondering if this has to do with how I wrote the function?
CREATE OR REPLACE FUNCTION report()
RETURNS TABLE(id text, t1 text, t2 text)
LANGUAGE 'plpsql'
AS $BODY$
DECLARE
rec RECORD;
BEGIN
RETURN QUERY
SELECT id,
name AS t1,
data AS t2
FROM table1
WHERE status IS NULL;
IF FOUND THEN
FOR rec IN SELECT id
FROM table1
WHERE status IS NULL
LOOP
UPDATE table1 SET val=val+1
WHERE id=rec.id;
END IF;
RETURN;
END
$BODY$;
EDIT:
Thanks for the replies. Don't have anyone else to help take a look, so this was very helpful with troubleshooting what was going on.
Was so fixated on my PL/pgSQL function as having the issue, I overlooked my external program running the function. User error on my part. After moving my UPDATE above my SELECT, I noticed my program was seeing the UPDATE. I was forgetting to COMMIT the updates back to the database in my external program, so the table was never updating. Added a commit and good to.
It seems like you can replace the whole function with this plain, much cheaper UPDATE query and a RETURNING clause:
UPDATE table1
SET val = val + 1
WHERE status IS NULL
RETURNING id, name AS t1, data AS t2;
If there can be race conditions, consider:
Postgres UPDATE … LIMIT 1
You can run this query as is, without function wrapper. If you want a function wrapper, prepend RETURN QUERY to return results directly. Code example:
PostgreSQL: ERROR: 42601: a column definition list is required for functions returning "record"

PostgreSQL variable in select and delete statements

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);

"If not exist"-condition in a case in SQL-procedure from Oracle-Database

I´m kind of new to SQL-Procedures, but I´ve written a procedure which adds a line to my datatable. This works pretty fine, but when i run trough the procedure twice i want to avoid the line being added again into the datatable based on an Oracle-Database. So my question is, how to set the "if already exists"-condition within the case command.
My code looks like that:
...
case number
when 5 then
/* if not exists` <<--- i need something to avoid doubled entrys */
pi_event_line_add(5,'xxx' ....);
end case;
end procedure;
One solution, is to interrogate your table first then decide to insert. Like:
declare cnt number;
begin
select count(*) into cnt from your_table where id = 5;
if cnt = 0 then
pi_event_line_add(5,'xxx'...);
end if;
Also, you probably want to add a unique constraint on the column that stores "5" if it truly should be unique.
Use a MERGE statement in pi_event_line_add instead of an INSERT. In the MERGE only implement the WHEN NOT MATCHED INSERT portion of the statement.
Best of luck.

How to get the objects from an oracle table of row objects

When dealing with an oracle table containing row objects I would expect that each row is an object and I can invoke functions on it or pass it to functions in any context.
As an example if I declare the following:
create type scd_type as object
(
valid_from date,
valid_to date,
member function get_new_valid_to return date
);
create type scd_type_table as table of scd_type;
create table scd_table of scd_type;
create procedure scd_proc (in_table in scd_type_table)
as
begin
... do stuff ...
end;
/
And now I try to call my proc with the table
begin
scd_proc (scd_table);
end;
/
I get an error. Even reading the rows into a nested table is not straight forward. I would expect it to work like this:
declare
temp_table scd_type_table;
begin
select * bulk collect into temp_table from scd_table;
... do stuff ...
end;
/
but instead I have to call the constructor for every line.
And last I cannot invoke functions in a merge statement even though it works in an update statement. Example:
update scd_table st
set st.valid_to = st.get_new_valid_to(); <--- Works.
merge into scd_table st
using (select sysdate as dateCol from dual) M
on (st.valid_from = M.dateCol)
when matched then update set st.valid_to = st.get_new_valid_to(); <--- Does not work.
So I guess there are three sub-questions here:
1) What is the easiest way to pass a table of row objects into a procedure expecting a nested table of the same type?
2) What is the easiest way to convert a table of row objects into a nested table of the same type?
3) Why can't I invoke functions on an object as part of a merge statement (but in an update statement)?
which all come down to the question of "How to extract objects from a table of row objects?".
I can't help but think you need to re-read the documentation on PL/SQL types.
You were close with the bulk collect code. Minor change given below:
declare
plsql_table scd_type_table;
begin
select VALUE(t) bulk collect into plsql_table from scd_table t;
-- do stuff
end;
/
I will admit, I have no idea why the merge fails but the update works.