I have a statement here that runs fine from what I can tell. If evaluates the condition and sticks the result into a variable. All I need to know is how to read the value out of the variable and display it. Thanks
DO
$do$
DECLARE result text;
BEGIN
IF EXISTS (select 1 from siteName where SiteNameID=9) THEN
SELECT 'Yes' into result;
ELSE
SELECT 'No' into result;
END IF;
END
$do$
In the event that by display, you meant output to STDOUT:
RAISE NOTICE 'result: %', result;
http://www.postgresql.org/docs/current/static/plpgsql-errors-and-messages.html
You cannot return data from a DO command. For that, you would need a function. With DO commands you are restricted to messages from RAISE like Denis provided, or you can write data to tables or temp tables, and select from them.
DO
$do$
BEGIN
CREATE TEMP TABLE site9_exists AS
SELECT EXISTS (SELECT 1 FROM sitename WHERE sitenameid=9) AS result;
END
$do$;
SELECT result FROM site9_exists;
Of course, you wouldn't need the DO command at all for the trivial example ...
A DO-statement can't return anything. You could use a notice, like Denis already showed.
But why do you need a function? You could use a normal query for this, something like this:
SELECT CASE
WHEN input.SiteNameID = siteName.SiteNameID THEN 'Yes'
ELSE 'No'
END AS result
FROM (SELECT 9 AS SiteNameID) input
LEFT JOIN siteName ON input.SiteNameID = siteName.SiteNameID;
(not tested....)
Related
I have a function that uses RECORD to temporarily store the data. I can use it - it's fine. My problem is that I can't hardcode columns I need to get from the RECORD. I must do it dynamically. Something line:
DECLARE
r1 RECORD;
r2 RECORD;
BEGIN
for r1 in Select column_name
from columns_to_process
where process_now = True
loop
for r2 in Select *
from my_data_table
where whatever
loop
-----------------------------
here I must call column by its name that is unknown at design time
-----------------------------
... do something with
r2.(r1.column_name)
end loop;
end loop;
END;
Does anyone know how to do it?
best regards
M
There is no need to select the all the qualifying rows and compute the total in a loop. Actually when working with SQL try to drop the word loop for your vocabulary; instead just use sum(column_name) in the select. The issue here is that you do not know what column to sum when the query is written, and all structural components(table names, columns names, operators, etc) must be known before submitting. You cannot use a variable for a structural component - in this case a column name. To do that you must use dynamic sql - i.e. SQL statement built by the process. The following accomplishes that: See example here.
create or replace function sum_something(
the_something text -- column name
, for_id my_table.id%type -- my_table.id
)
returns numeric
language plpgsql
as $$
declare
k_query_base constant text :=
$STMT$ Select sum(%I) from my_table where id = %s; $STMT$;
l_query text;
l_sum numeric;
begin
l_query = format(k_query_base, the_something, for_id);
raise notice E'Rumming Statememt:\n %',l_query; -- for prod raise Log
execute l_query into l_sum;
return l_sum;
end;
$$;
Well, after some time I figured out that I could use temporary table instead of RECORD. Doing so gives me all advantages of using dynamic queries so I can call any column by its name.
DECLARE
_my_var bigint;
BEGIN
create temporary table _my_temp_table as
Select _any, _column, _you, _need
from _my_table
where whatever = something;
execute 'Select ' || _any || ' from _my_temp_table' into _my_var;
... do whatever
END;
However I still believe that there should be a way to call records field by it's name.
I have a long and complex plpgsql function that creates a bunch of temporary tables nested within a while statement to get the optimal result. When the condition has been met I insert the result into an existing table, the function is far to long to post here but this is an example:
CREATE OR REPLACE FUNCTION public.test_function(id_input integer, val_input numeric)
RETURNS VOID AS
$BODY$
DECLARE
id_input numeric = $1;
val_input numeric = $2;
BEGIN
WHILE test_val < 0
LOOP
CREATE TEMP TABLE temp_table AS
SELECT a.existing_val - val_input AS new_val
FROM existing_table a
WHERE a.id = id_input;
test_val := (SELECT new_val FROM temp_table);
val_input := val_input + 1;
END LOOP;
INSERT INTO output_table (id, new_val)
SELECT a.id, a.new_val
FROM temp_table a;
END;
$BODY$
LANGUAGE plpgsql;
The function works if I call it like this SELECT test_function(1, 1000) However I would like run this function on a table with 60,000+ rows, like this:
SELECT test_function(a.id, a.val_input)
FROM data_table a;
It works when I use a subset of the data_table, say 1000 rows. However when I run it on the full table (60,000+ rows) I get the following error "AbortTransaction while in COMMIT state". After some reading I found out COMMITS, so in my case the inserts do not occur until the function has finished running which takes about 4 hours. So does anyone know what is going on?
As a workaround I tried nesting the function in a DO statement so the inserts are committed straight away:
DO
$do$
DECLARE
r data_table%rowtype;
BEGIN
FOR r IN
SELECT * FROM data_table
LOOP
SELECT public.test_function(r.id, r.val_input);
END LOOP;
END
$do$;
However then I get the following error "ERROR: query has no destination for result data", which I guess means I need to rewrite the function to use PERFORM instead of SELECT. However I have not had any luck with this as yet.
Any ideas?
Since you are not interested in the function result, you should use
PERFORM public.test_function(r.id, r.val_input);
instead of
SELECT public.test_function(r.id, r.val_input);
The latter syntax would only work if you add INTO some_variable as a destination for the query result.
Thank you all for your suggestions. I ended up using Jim Jones's suggestion and converting the function to a procedure which allowed me to use COMMIT after I did the INSERT. I also followed Jeremy's suggestion and moved from temp tables to CTE's. This solved the problem for me.
I am trying to create a function which calls 2 other functions.
Below is the calling function's code from where I am trying to call 2 another functions, schema1.func1() and schema1.func2().
But it is throwing error at the line SELECT schema1.func1(temp_val); saying:
syntax error at or near "SELECT".
I tried to figure out the correct syntax but couldn't resolve.
I am using Postgres version 1.14.3
DECLARE
temp_val int;
cursor1 CURSOR
FOR
SELECT col1 from schema1.table1;
BEGIN
OPEN cursor1;
LOOP
FETCH cursor1 INTO temp_val;
EXIT WHEN NOT FOUND;
SELECT CASE
WHEN NOT EXISTS (SELECT col2 FROM schema1.table2 WHERE col2 = temp_val)
THEN
BEGIN
SELECT schema1.func1(temp_val);
SELECT schema1.func2(temp_val);
END;
END CASE;
END LOOP;
CLOSE cursor1;
END;
There is a ; missing at:
SELECT CASE WHEN NOT EXISTS (SELECT col2 FROM schema1.table2 WHERE col2 = temp_val)
so..
SELECT CASE WHEN NOT EXISTS (SELECT col2 FROM schema1.table2 WHERE col2 = temp_val);
You can't mix PL/pgSQL BEGIN ... END blocks with SQL statement - even if that SQL statement is part of a PL/pgSQL function. So the BEGIN inside the THEN part of the CASE expression is invalid. A SQL CASE expression ends with just an END. A PL/pgSQL CASE statement would end with END CASE. As you are trying to use a CASE expression, it would require just END, not end case.
You also need to use perform in order to call a function where you want to discard the result.
It's not clear to me what exactly you want to achieve. If you only want to call those two functions if a row does not exist in table2 then you can do this with a single loop:
DECLARE
t1_rec record;
BEGIN
FOR t1_rec IN select t1.col1
from table1 t1
where not exists (select *
from table2 t2
where t2.col2 = t1.col1)
LOOP
perform schema1.func1(t1_rec.col1);
perform schema1.func2(t1_rec.col1);
END LOOP;
END;
I am trying to create a complicated pl/pgsql function that gathers some results from a query and then checks each one and returns it or not.
This is my code so far. The record and loop part confuse me.
CREATE FUNCTION __a_search_creator(creator text, ordertype integer, orderdate integer, areaid bigint) RETURNS record
AS $$
DECLARE
fromText text;
whereText text;
usingText text;
firstrecord record;
areageom geometry;
BEGIN
IF areaid IS NOT NULL
THEN
EXECUTE format('SELECT area.geom FROM area WHERE area.id=$1')
INTO areageom
USING areaid;
FOR firstrecord IN
EXECUTE format(
'SELECT place.id, person.name, place.geom
FROM '||fromText||'
WHERE '||whereText)
USING creator, ordertype , orderdate
LOOP
--return only data that the place.geom is inside areageom using PostGIS
END LOOP;
END IF;
RETURN firstrecord;
END;
$$
LANGUAGE plpgsql;
I plan to do some extra checks in the loop, as you can see and RETURN only data that the place.geom (lon/lat) is inside areageom. But since I am new to pl/pgsql, I am creating now the first step, just gathering all the data, put them in a record and return.
My problem is that, no matter what I try I keep getting only one result back. I call select __a_search_creator('johnson',8, 19911109, 20); and I get 1,"seth johnson", 65485,84545 but I know I should be getting another row of results. Is there overwrite happening?
I tried putting RETURN NEXT firstrecord; I tried something like
select place.id from place INTO placeid;
select person.name from person INTO personname;
select place.geom from place INTO placegeom;
firstrecord.id := placeid;
firstrecord.name := personname;
firstrecord.geom := placegeom;
That still brings back just one result, I tried testing just this RAISE NOTICE '%', firstrecord.id; that still brings back just one full set of result.
I don't know how to proceed, please advice.
You declared your function with RETURNS record, so it returns a single record (of unknown type).
Use RETURNS SETOF record to return a set of rows. The manual:
The SETOF modifier indicates that the function will return a set of items, rather than a single item.
Better yet, use RETURNS TABLE with a proper table definition. Like you already had it in your recent questions:
Syntax error in dynamic SQL in pl/pgsql function
Using text in pl/pgsql brings empty set of results
Related (with detailed explanation and links to the manual):
How to return result of a SELECT inside a function in PostgreSQL?
Return setof record (virtual table) from function
Return a query from a function?
This is my solution on the problem
IF areaid IS NOT NULL
THEN
EXECUTE format('SELECT area.geom FROM area WHERE area.id=$1')
INTO areageom
USING areaid;
FOR firstrecord IN
EXECUTE format(
'SELECT place.id, person.name, place.geom FROM '||fromText||' WHERE '||whereText)
USING creator, ordertype, orderdate
LOOP
IF ST_Within(firstrecord.geom , areageom)
THEN
RETURN QUERY VALUES(firstrecord.id, firstrecord.name, firstrecord.geom);
END IF;
END LOOP;
END IF;
RETURN;
Thanks to #Erwin Brandstetter for the useful links.
I have a function that is used as an INSERT trigger. This function deletes rows that would conflict with [the serial number in] the row being inserted. It works beautifully, so I'd really rather not debate the merits of the concept.
DECLARE
re1 feeds_item.shareurl%TYPE;
BEGIN
SELECT regexp_replace(NEW.shareurl, '/[^/]+(-[0-9]+\.html)$','/[^/]+\\1') INTO re1;
RAISE NOTICE 'DELETEing rows from feeds_item where shareurl ~ ''%''', re1;
DELETE FROM feeds_item where shareurl ~ re1;
RETURN NEW;
END;
I would like to add to the NOTICE an indication of how many rows are affected (aka: deleted). How can I do that (using LANGUAGE 'plpgsql')?
UPDATE:
Base on some excellent guidance from "Chicken in the kitchen", I have changed it to this:
DECLARE
re1 feeds_item.shareurl%TYPE;
num_rows int;
BEGIN
SELECT regexp_replace(NEW.shareurl, '/[^/]+(-[0-9]+\.html)$','/[^/]+\\1') INTO re1;
DELETE FROM feeds_item where shareurl ~ re1;
IF FOUND THEN
GET DIAGNOSTICS num_rows = ROW_COUNT;
RAISE NOTICE 'DELETEd % row(s) from feeds_item where shareurl ~ ''%''', num_rows, re1;
END IF;
RETURN NEW;
END;
For a very robust solution, that is part of PostgreSQL SQL and not just plpgsql you could also do the following:
with a as (DELETE FROM feeds_item WHERE shareurl ~ re1 returning 1)
select count(*) from a;
You can actually get lots more information such as:
with a as (delete from sales returning amount)
select sum(amount) from a;
to see totals, in this way you could get any aggregate and even group and filter it.
In Oracle PL/SQL, the system variable to store the number of deleted / inserted / updated rows is:
SQL%ROWCOUNT
After a DELETE / INSERT / UPDATE statement, and BEFORE COMMITTING, you can store SQL%ROWCOUNT in a variable of type NUMBER. Remember that COMMIT or ROLLBACK reset to ZERO the value of SQL%ROWCOUNT, so you have to copy the SQL%ROWCOUNT value in a variable BEFORE COMMIT or ROLLBACK.
Example:
BEGIN
DECLARE
affected_rows NUMBER DEFAULT 0;
BEGIN
DELETE FROM feeds_item
WHERE shareurl = re1;
affected_rows := SQL%ROWCOUNT;
DBMS_OUTPUT.
put_line (
'This DELETE would affect '
|| affected_rows
|| ' records in FEEDS_ITEM table.');
ROLLBACK;
END;
END;
I have found also this interesting SOLUTION (source: http://markmail.org/message/grqap2pncqd6w3sp )
On 4/7/07, Karthikeyan Sundaram wrote:
Hi,
I am using 8.1.0 postgres and trying to write a plpgsql block. In that I am inserting a row. I want to check to see if the row has been
inserted or not.
In oracle we can say like this
begin
insert into table_a values (1);
if sql%rowcount > 0
then
dbms.output.put_line('rows inserted');
else
dbms.output.put_line('rows not inserted');
end if; end;
Is there something equal to sql%rowcount in postgres? Please help.
Regards skarthi
Maybe:
http://www.postgresql.org/docs/8.2/static/plpgsql-statements.html#PLPGSQL-STATEMENTS-SQL-ONEROW
Click on the link above, you'll see this content:
37.6.6. Obtaining the Result Status There are several ways to determine the effect of a command. The first method is to use the GET
DIAGNOSTICS command, which has the form:
GET DIAGNOSTICS variable = item [ , ... ];This command allows
retrieval of system status indicators. Each item is a key word
identifying a state value to be assigned to the specified variable
(which should be of the right data type to receive it). The currently
available status items are ROW_COUNT, the number of rows processed by
the last SQL command sent down to the SQL engine, and RESULT_OID, the
OID of the last row inserted by the most recent SQL command. Note that
RESULT_OID is only useful after an INSERT command into a table
containing OIDs.
An example:
GET DIAGNOSTICS integer_var = ROW_COUNT; The second method to
determine the effects of a command is to check the special variable
named FOUND, which is of type boolean. FOUND starts out false within
each PL/pgSQL function call. It is set by each of the following types
of statements:
A SELECT INTO statement sets FOUND true if a row is assigned, false if
no row is returned.
A PERFORM statement sets FOUND true if it produces (and discards) a
row, false if no row is produced.
UPDATE, INSERT, and DELETE statements set FOUND true if at least one
row is affected, false if no row is affected.
A FETCH statement sets FOUND true if it returns a row, false if no row
is returned.
A FOR statement sets FOUND true if it iterates one or more times, else
false. This applies to all three variants of the FOR statement
(integer FOR loops, record-set FOR loops, and dynamic record-set FOR
loops). FOUND is set this way when the FOR loop exits; inside the
execution of the loop, FOUND is not modified by the FOR statement,
although it may be changed by the execution of other statements within
the loop body.
FOUND is a local variable within each PL/pgSQL function; any changes
to it affect only the current function.
I would to share my code (I had this idea from Roelof Rossouw):
CREATE OR REPLACE FUNCTION my_schema.sp_delete_mytable(_id integer)
RETURNS integer AS
$BODY$
DECLARE
AFFECTEDROWS integer;
BEGIN
WITH a AS (DELETE FROM mytable WHERE id = _id RETURNING 1)
SELECT count(*) INTO AFFECTEDROWS FROM a;
IF AFFECTEDROWS = 1 THEN
RETURN 1;
ELSE
RETURN 0;
END IF;
EXCEPTION WHEN OTHERS THEN
RETURN 0;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;