SQL query returns necessary data, but the procedure doesn't - oracle-sqldeveloper

I need to create a procedure that returns the list of entries from two tables. Here's the code for the procedure:
create or replace PROCEDURE goods_to_transfer
IS
begin
for rec in (select sales.good_id, sales.good_count, goods.priority
FROM goods,sales
where sales.good_id=goods.id and sales.delivered='YES');
loop
dbms_output.put_line( 'GOOD ID' || rec.good_id);
dbms_output.put_line( 'GOOD COUNT' || rec.good_count);
dbms_output.put_line( 'PRIORITY' || rec.good_priority);
end loop;
end goods_to_transfer;
The execution ends up with the following mistakes:
ORA-01403: no data found
ORA-06512: in "C##XSENIA.GOODS_TO_TRANSFER", line 7
ORA-06512: in line 2
The same query returns all the necessary data:
select sales.good_id, sales.good_count, goods.priority FROM goods,sales
where sales.good_id=goods.id and sales.delivered='YES'
I wonder if anyone could possibly tell me, what do I do wrong.
Thank you!

try to remove the ; at the end of the request
for rec in (select sales.good_id, sales.good_count, goods.priority
FROM goods,sales
where sales.good_id=goods.id and sales.delivered='YES')

Related

how to get the output for specific column in this function from postgresql?

what is the mistake I am doing here? I have tried to call the function like this, but getting 0 rows. please, anyone, suggest me an idea to get the output. Thanks in advance
input:
if input is given as select * from test2('township'),i have to get output as
|township|--column name
|--------|
|1|
|3|
|7|
|9|
if input given as select * from test2('range'),output will be
|range|--column name
|-----|
|4|
|8|
|35|
|21|
below one is the code:
create type holder as (t text);
CREATE OR REPLACE FUNCTION test2(text) returns setof holder as
'
declare
township holder%rowtype;
range holder%rowtype;
begin
if township then
for township in
SELECT distinct "Township" as township
from tableA
loop
return next township;
end LOOP;
return;
end if;
if range then
for range in
SELECT distinct "Range" as range
from tableB
loop
return next range;
end LOOP;
return;
end if;
end
'
language 'plpgsql';```

What is wrong in my dynamic query postgres statements inside function?

I have a PL/pgSQL function that takes table name as dynamic parameter. As I am updating an existing query to take table name as dynamic parameter, this is what I have as my function:
DECLARE rec RECORD;
BEGIN
EXECUTE 'insert into stat_300_8_0(ts, target, data)
select distinct timestamp-(timestamp%3600) as wide_row_ts,
target, array[]::real[] as data
from ' || temp_table_name || ' as temp
where class_id=8
and subclass_id=0
and not exists (select ts from stat_300_8_0
where ts=temp.timestamp-(temp.timestamp%3600)
and target=temp.target)';
FOR rec IN EXECUTE 'SELECT DISTINCT timestamp AS ts
FROM ' || temp_table_name ||
' WHERE class_id=8'
LOOP
EXECUTE 'update stat_300_8_0 as disk_table
set data[new_data.start_idx:new_data.end_idx] = array[data_0,data_1]
from (select timestamp-(timestamp%3600) as wide_row_ts,
(timestamp%3600)/300 * 2 + 1 as start_idx,
((timestamp%3600 / 300) + 1) * 2 as end_idx,
target, data_0, data_1
from ' || temp_table_name ||
' where class_id=8 and subclass_id=0
and timestamp=rec.ts) as new_data
where disk_table.ts=new_data.wide_row_ts
and disk_table.target=new_data.target';
END LOOP;
END;
However, when this function is executed I get an error saying
ERROR: missing FROM-clause entry for table "rec"
However, rec is declared in the first line of the above code. I am not able to figure what is wrong with my queries. Any help is appreciated.
Supplemental to Eelke's answer:
Assuming temp_table_name is an argument, you really, really want to run it through quote_ident() because otherwise someone could create a table with a name that could inject sql into your function.
Instead of the change suggested there, you are better off using EXECUTE...USING since that gives you parameterization regarding values (and hence protection against SQL injection). You would change rec.ts to $1 and then add to the end USING ts.rec (outside the quoted execute string). This gives you a parameterized statement inside your execute which is safer. However parameters cannot include table names, so it doesn't spare you from the first point above.

Function or loop using table and topologies names as arguments in Postgresql

I'm working with topologies in PostGIS and to create a TopoGeometry column, I'm using this loop:
DO $$DECLARE r record;
BEGIN
FOR r IN SELECT * FROM table_uf_11 LOOP
BEGIN
UPDATE table_uf_11 SET tg_geom = toTopoGeom(ST_Force2D(geom),'topology_uf_11', 1, 1)
WHERE gid= r.gid;
EXCEPTION
WHEN OTHERS THEN
RAISE WARNING 'Loading of record % failed: %', r.gid, SQLERRM;
END;
END LOOP;
END$$;
The reason for using this loop is because in some rows the toTopoGeom function displays error, but are just a few cases, for exemplo 38 cases in 24.000.
Using this structure I can identify which cases are problematic in the log and fix them later.
My problem is that I have another 26 tables with their respective topologies, all of them identified by the state code, for exemplo:
table_uf_12 / topology_uf_12
table_uf_13 / topology_uf_13
table_uf_14 / topology_uf_14
...
table_uf_53 / topology_uf_53
The state code are not necessarily sequential, but the names has the same pattern. Column names as geom and tg_geom are equal for all tables.
How can I make a function or another loop structure to replicate this process in all 27 tables and the same time save the log of each table?
I tried to make a function, but in this case the arguments would be the table name and the topology name, and i'm having difficult to elaborate this structure.
Any suggestions?
I think this should do it:
DO $BODY$
DECLARE
t regclass;
gid bigint;
BEGIN
FOR t IN SELECT oid::regclass FROM pg_class WHERE relname ~ '^table_uf_\d+$' LOOP
FOR gid IN EXECUTE 'SELECT gid FROM ' || t::text LOOP
BEGIN
EXECUTE
' UPDATE ' || t::text ||
' SET tg_geom = toTopoGeom(ST_Force2D(geom), $2, 1, 1)'
' WHERE gid = $1'
USING gid, replace(t::text, 'table', 'topology');
EXCEPTION
WHEN OTHERS THEN
RAISE WARNING 'Loading of record % failed: %', gid, SQLERRM;
END;
END LOOP;
END LOOP;
END
$BODY$

count number of rows to be affected before update in trigger

I want to know number of rows that will be affected by UPDATE query in BEFORE per statement trigger . Is that possible?
The problem is that i want to allow only queries that will update up to 4 rows. If affected rows count is 5 or more i want to raise error.
I don't want to do this in code because i need this check on db level.
Is this at all possible?
Thanks in advance for any clues on that
Write a function that updates the rows for you or performs a rollback. Sorry for poor style formatting.
create function update_max(varchar, int)
RETURNS void AS
$BODY$
DECLARE
sql ALIAS FOR $1;
max ALIAS FOR $2;
rcount INT;
BEGIN
EXECUTE sql;
GET DIAGNOSTICS rcount = ROW_COUNT;
IF rcount > max THEN
--ROLLBACK;
RAISE EXCEPTION 'Too much rows affected (%).', rcount;
END IF;
--COMMIT;
END;
$BODY$ LANGUAGE plpgsql
Then call it like
select update_max('update t1 set id=id+10 where id < 4', 3);
where the first param ist your sql-Statement and the 2nd your max rows.
Simon had a good idea but his implementation is unnecessarily complicated. This is my proposition:
create or replace function trg_check_max_4()
returns trigger as $$
begin
perform true from pg_class
where relname='check_max_4' and relnamespace=pg_my_temp_schema();
if not FOUND then
create temporary table check_max_4
(value int check (value<=4))
on commit drop;
insert into check_max_4 values (0);
end if;
update check_max_4 set value=value+1;
return new;
end; $$ language plpgsql;
I've created something like this:
begin;
create table test (
id integer
);
insert into test(id) select generate_series(1,100);
create or replace function trg_check_max_4_updated_records()
returns trigger as $$
declare
counter_ integer := 0;
tablename_ text := 'temptable';
begin
raise notice 'trigger fired';
select count(42) into counter_
from pg_catalog.pg_tables where tablename = tablename_;
if counter_ = 0 then
raise notice 'Creating table %', tablename_;
execute 'create temporary table ' || tablename_ || ' (counter integer) on commit drop';
execute 'insert into ' || tablename_ || ' (counter) values(1)';
execute 'select counter from ' || tablename_ into counter_;
raise notice 'Actual value for counter= [%]', counter_;
else
execute 'select counter from ' || tablename_ into counter_;
execute 'update ' || tablename_ || ' set counter = counter + 1';
raise notice 'updating';
execute 'select counter from ' || tablename_ into counter_;
raise notice 'Actual value for counter= [%]', counter_;
if counter_ > 4 then
raise exception 'Cannot change more than 4 rows in one trancation';
end if;
end if;
return new;
end; $$ language plpgsql;
create trigger trg_bu_test before
update on test
for each row
execute procedure trg_check_max_4_updated_records();
update test set id = 10 where id <= 1;
update test set id = 10 where id <= 2;
update test set id = 10 where id <= 3;
update test set id = 10 where id <= 4;
update test set id = 10 where id <= 5;
rollback;
The main idea is to have a trigger on 'before update for each row' that creates (if necessary) a temporary table (that is dropped at the end of transaction). In this table there is just one row with one value, that is the number of updated rows in current transaction. For each update the value is incremented. If the value is bigger than 4, the transaction is stopped.
But I think that this is a wrong solution for your problem. What's a problem to run such wrong query that you've written about, twice, so you'll have 8 rows changed. What about deletion rows or truncating them?
PostgreSQL has two types of triggers: row and statement triggers. Row triggers only work within the context of a row so you can't use those. Unfortunately, "before" statement triggers don't see what kind of change is about to take place so I don't believe you can use those, either.
Based on that, I would say it's unlikely you'll be able to build that kind of protection into the database using triggers, not unless you don't mind using an "after" trigger and rolling back the transaction if the condition isn't satisfied. Wouldn't mind being proved wrong. :)
Have a look at using Serializable Isolation Level. I believe this will give you a consistent view of the database data within your transaction. Then you can use option #1 that MusiGenesis mentioned, without the timing vulnerability. Test it of course to validate.
I've never worked with postgresql, so my answer may not apply. In SQL Server, your trigger can call a stored procedure which would do one of two things:
Perform a SELECT COUNT(*) to determine the number of records that will be affected by the UPDATE, and then only execute the UPDATE if the count is 4 or less
Perform the UPDATE within a transaction, and only commit the transaction if the returned number of rows affected is 4 or less
No. 1 is timing vulnerable (the number of records affected by the UPDATE may change between the COUNT(*) check and the actual UPDATE. No. 2 is pretty inefficient, if there are many cases where the number of rows updated is greater than 4.

EXECUTE...USING statement in PL/pgSQL doesn't work with record type?

I'm trying to write a function in PL/PgSQL that have to work with a table it receives as a parameter.
I use EXECUTE..INTO..USING statements within the function definition to build dynamic queries (it's the only way I know to do this) but ... I encountered a problem with RECORD data types.
Let's consider the follow (extremely simplified) example.
-- A table with some values.
DROP TABLE IF EXISTS table1;
CREATE TABLE table1 (
code INT,
descr TEXT
);
INSERT INTO table1 VALUES ('1','a');
INSERT INTO table1 VALUES ('2','b');
-- The function code.
DROP FUNCTION IF EXISTS foo (TEXT);
CREATE FUNCTION foo (tbl_name TEXT) RETURNS VOID AS $$
DECLARE
r RECORD;
d TEXT;
BEGIN
FOR r IN
EXECUTE 'SELECT * FROM ' || tbl_name
LOOP
--SELECT r.descr INTO d; --IT WORK
EXECUTE 'SELECT ($1)' || '.descr' INTO d USING r; --IT DOES NOT WORK
RAISE NOTICE '%', d;
END LOOP;
END;
$$ LANGUAGE plpgsql STRICT;
-- Call foo function on table1
SELECT foo('table1');
It output the following error:
ERROR: could not identify column "descr" in record data type
although the syntax I used seems valid to me. I can't use the static select (commented in the example) because I want to dinamically refer the columns names.
So..someone know what's wrong with the above code?
It's true. You cannot to use type record outside PL/pgSQL space.
RECORD value is valid only in plpgsql.
you can do
EXECUTE 'SELECT $1.descr' INTO d USING r::text::xx;
$1 should be inside the || ,like || $1 || and give spaces properly then it will work.
BEGIN
EXECUTE ' delete from ' || quote_ident($1) || ' where condition ';
END;