How to transfer a variable within a funtion in PostgreSQL - postgresql

I'm running km_test.sql from a bat-file (Windows 7):
call psql -h ... -U ... -d ... -f C:\svn\tre2\prog\km_test.sql -v nrl=%a%
where %a% is an integer. I'm running on PostgreSQL 9.5.
km_test.sql looks like this
\set n :nrl
DROP FUNCTION km_test(integer);
CREATE FUNCTION km_test(n integer)
RETURNS void AS
$BODY$
DECLARE
j smallint;
BEGIN
DROP TABLE IF EXISTS km_test CASCADE;
CREATE UNLOGGED TABLE km_test (
lnr smallint,
km_id character varying(16),
flatenr smallint,
geo geometry(Linestring,25833)
);
j = 1;
WHILE j < n+1 LOOP
RAISE NOTICE 'Verdi j er : %', j;
INSERT INTO km_test (lnr, km_id, flatenr, geo)
SELECT d.i,
p.km_id,
CAST(substring(p.flatenr from 5 for 4) AS smallint),
ST_MakeLine(p.geo,(ST_Translate(p.geo, d.dx, d.dy)))
FROM org_tre2.km_punkter_des2016 AS p, org_tre2.km_dxdy1 AS d
WHERE j = d.i;
j = j + 1;
END LOOP;
COMMENT ON TABLE org_tre2.km_test IS 'KM innsyn: n innsynslinjer for kulturminnepunkt utenfor IK i tre2-flater';
END;
$BODY$
LANGUAGE plpgsql;
\set tab 'org_tre2.km_punkter_des2016'
select km_test(:n);
Question: How do I make table p (org_tre2.km_punkter_des2016) to be an input parameter? Meaning how to include the tablename in the function-call (with select km_tull(:n,:p)) and refer to this table within the function?
CREATE FUNCTION km_test(n integer, t text)
...
select km_test(:n,:'tab')
So far I have not managed to refer to the input-table as the t-variable in the FROM-statement.
Is it possible? Or is there a workaround?

Thanks for the hint/link #McNets. I think I've figured it out. Correct code in km_test.sql:
\set n :nrl
DROP FUNCTION km_test(integer, text);
CREATE FUNCTION km_test(n integer, t text)
RETURNS void AS
$BODY$
DECLARE
j smallint;
s integer;
BEGIN
DROP TABLE IF EXISTS km_test CASCADE;
CREATE UNLOGGED TABLE km_test (
lnr smallint,
km_id character varying(16),
flatenr smallint,
geo geometry(Linestring,25833)
);
j = 1;
WHILE j < n+1 LOOP
s:=j;
RAISE NOTICE 'Verdi j er : %', j;
RAISE NOTICE 'Tabell t er : %', t;
EXECUTE 'INSERT INTO km_test (lnr, km_id, flatenr, geo)
SELECT d.i,
p.km_id,
CAST(substring(p.flatenr from 5 for 4) AS smallint),
ST_MakeLine(p.geo,(ST_Translate(p.geo, d.dx, d.dy)))
FROM '||t||' as p, org_tre2.km_dxdy1 AS d
WHERE '||s||' = d.i';
j = j + 1;
END LOOP;
COMMENT ON TABLE org_tre2.km_test IS 'KM innsyn: n innsynslinjer for kulturminnepunkt utenfor IK i tre2-flater';
END;
$BODY$
LANGUAGE plpgsql;
\set ptab 'org_tre2.km_punkter_des2016'
select km_test(:n,:'ptab');
The solution being using EXECUTE, put all the INSER INTO-code within '', declare s as a variable and use s instead of j in the WHERE-clause.

Related

PostgreSql cursorfunction for unique id

I am trying to Create a cursor on cartesian product/join as below, it gives an error
create or replace function som1() returns integer as $$
declare
rCur cursor for (select* from t1);
er route%rowtype;
begin
for er in
select route_id, location, happy from t1, t2 where exams.pid = route.pid
loop
end loop;
return 4;
end;
$$ language plpgsql;
select som1();

How do i create a column 1-10 with a loop?

This is an example but I want only a column with 1-10 values without other text columns.
CREATE OR REPLACE FUNCTION somefun_recordset(param_numcount integer)
RETURNS SETOF record AS
$$
DECLARE
result text := '';
searchsql text := '';
var_match record;
BEGIN
searchsql := 'SELECT n || '' down'' As countdown, n as integer
FROM generate_series(' || CAST(param_numcount As text) || ', 1, -1) As n ';
FOR var_match IN EXECUTE(searchsql) LOOP
RETURN NEXT var_match;
END LOOP;
END;
$$
LANGUAGE 'plpgsql' IMMUTABLE;
SELECT r.n , r.countdown
FROM somefun_recordset(10)
As r(countdown text, n integer)
ORDER BY r.n;
How do I create a loop in postgres?
From your current description, you seem to be over-complicating this.
As described in the Postgres manual, the generate_series function can generate a column of values from 10 to 1, like so:
SELECT generate_series(10, 1, -1) as n;
Or using it as a table alias rather than a column alias:
SELECT * FROM generate_series(10, 1, -1) as n;
If you wanted to wrap this in a custom function with only one parameter, it would look like this:
CREATE FUNCTION countdown(param_numcount INT) RETURNS SETOF INT LANGUAGE SQL AS $$
SELECT generate_series(param_numcount, 1, -1);
$$;
Then you would run it as:
SELECT countdown(10) as n;

PostgreSQL update trigger Comparing Hstore values

I am creating trigger in PostgresSQL. On update I would like to compare all of the values in a Hstore column and update changes in my mirror table. I managed to get names of my columns in variable k but I am not able to get values using it from NEW and OLD.
CREATE OR REPLACE FUNCTION function_replication() RETURNS TRIGGER AS
$BODY$
DECLARE
k text;
BEGIN
FOR k IN SELECT key FROM EACH(hstore(NEW)) LOOP
IF NEW.k != OLD.k THEN
EXECUTE 'UPDATE ' || TG_TABLE_NAME || '_2' || 'SET ' || k || '=' || new.k || ' WHERE ID=$1.ID;' USING OLD;
END IF;
END LOOP;
RETURN NEW;
END;
$BODY$
language plpgsql;
You should operate on hstore representations of the records new and old. Also, use the format() function for better control and readibility.
create or replace function function_replication()
returns trigger as
$body$
declare
newh hstore = hstore(new);
oldh hstore = hstore(old);
key text;
begin
foreach key in array akeys(newh) loop
if newh->key != oldh->key then
execute format(
'update %s_2 set %s = %L where id = %s',
tg_table_name, key, newh->key, oldh->'id');
end if;
end loop;
return new;
end;
$body$
language plpgsql;
Another version - with minimalistic numbers of updates - in partially functional design (where it is possible).
This trigger should be AFTER trigger, to be ensured correct behave.
CREATE OR REPLACE FUNCTION function_replication()
RETURNS trigger AS $$
DECLARE
newh hstore;
oldh hstore;
update_vec text[];
pair text[];
BEGIN
IF new IS DISTINCT FROM old THEN
IF new.id <> old.id THEN
RAISE EXCEPTION 'id should be immutable';
END IF;
newh := hstore(new); oldh := hstore(old); update_vec := '{}';
FOREACH pair SLICE 1 IN ARRAY hstore_to_matrix(newh - oldh)
LOOP
update_vec := update_vec || format('%I = %L', pair[1], pair[2]);
END LOOP;
EXECUTE
format('UPDATE %I SET %s WHERE id = $1',
tg_table_name || '_2',
array_to_string(update_vec, ', '))
USING old.id;
END IF;
RETURN NEW; -- the value is not important in AFTER trg
END;
$$ LANGUAGE plpgsql;
CREATE TABLE foo(id int PRIMARY KEY, a int, b int);
CREATE TABLE foo_2(LIKE foo INCLUDING ALL);
CREATE TRIGGER xxx AFTER UPDATE ON foo
FOR EACH ROW EXECUTE PROCEDURE function_replication();
INSERT INTO foo VALUES(1, NULL, NULL);
INSERT INTO foo VALUES(2, 1,1);
INSERT INTO foo_2 VALUES(1, NULL, NULL);
INSERT INTO foo_2 VALUES(2, 1,1);
UPDATE foo SET a = 20, b = 30 WHERE id = 1;
UPDATE foo SET a = NULL WHERE id = 1;
This code is little bit more complex, but all what should be escaped is escaped and reduce number of executed UPDATE commands. UPDATE is full SQL command and the overhead of full SQL commands should be significantly higher than code that reduce number of full SQL commands.

plpgsql function creates sequences for row based tenancy?

Trying to create a function that looks for sequence with particular name if does not exist should create it. Then returns function value of sequence. Part of function that does not seem to be working is where it tests if sequence already exists. Below is code for function
CREATE OR REPLACE FUNCTION public."tenantSequence"(
vtenantid integer,
vtablename character)
RETURNS bigint AS
$BODY$DECLARE
vSeqName character varying;
vSQL character varying;
BEGIN
select ('t' || trim(to_char(vtenantid,'0000')) || vtablename) INTO vSeqName;
if not exists(SELECT 0 FROM pg_class where relkind = 'S' and relname = vSeqName )
then
vSQL := 'create sequence '||vSeqName||';';
execute vSQL;
ELSE
return 0;
end if;
return nextval(vSeqName) * 10000 + vtenantid;
END$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION public."tenantSequence"(integer, character)
OWNER TO postgres;
Problem was case as mentioned by a_horse_with_no_name
changed line assign vSeqName to the following
vSeqName := lower('t' || trim(to_char(vtenantid,'0000')) || vtablename);
Now function works as expected.

Check constraint fire only on Insert and not on updates PostgresSQL

My Check constraint is as follows:
ALTER TABLE tablename
ADD CONSTRAINT check_duplicate_rows
CHECK (reject_duplicate_rows(columnB, columnC, columnD) < 2);
I want the constraint to be evaluated only when you insert a record.
Currently it does for both the insert and update statements, The problem is that my system needs to update the inserted rows and the check constraint blocks the updates.
The reject_duplicate_rows function is as follows:
CREATE OR REPLACE FUNCTION reject_duplicate_rows(columnB integer, columnC integer, columnD integer)
RETURNS integer AS
$BODY$
DECLARE
results INTEGER := 1;
v_count INTEGER := 0;
BEGIN
IF columnC <> 23 THEN
RETURN results;
END IF;
SELECT total INTO v_count FROM
(SELECT columnB,
columnC,
columnD,
count(*) AS total
FROM table_name
WHERE B = columnB AND C = columnC AND D = columnD
GROUP BY 1, 2, 3)
as temp_table;
IF COALESCE(v_count, 0) = 0 THEN
RETURN results;
END IF;
IF v_count >= 1 THEN
results := 2;
END IF;
RETURN results;
EXCEPTION
WHEN OTHERS THEN
RETURN results;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 1000;
ALTER FUNCTION reject_duplicate_rows(integer, integer, integer)
OWNER TO postgres
Have you tried to create an UPDATE trigger? see Creating postgresql trigger