Stored procedure in pgsql to create materialized views - postgresql

I'm trying to create a stored procedure in pgsql that creates a materialized view with data from a specific year from a table. The parameters will be the table name, the column that contains the year, and the year.
I know that in SQL Server it would be something like this:
CREATE PROCEDURE createMaterializedView
#tablename varchar(100),
#column varchar(100),
#year integer
AS
BEGIN
DECLARE #return varchar(1000) = 'CREATE MATERIALIZED VIEW view_' + #tablename + '_' + #year + 'AS SELECT * from' + #tablename + 'where'
+ #column + ' = ' + #year
EXECUTE sp_executesql #return
END
In pgsql, what I got right now is this:
CREATE PROCEDURE createMaterializedView(tablename varchar(100), column varchar (100), year integer)
LANGUAGE 'plpgsql'
AS $$
BEGIN
CREATE MATERIALIZED VIEW "view_" + $tablename + "_" + $year
AS
SELECT * from tablename
WHERE column = year
END;
$$;
Appreciate any help.

Consider a dynamic query, formatted for your identifiers (view name, table, column) and literal value (year) to be run using EXECUTE.
CREATE OR REPLACE PROCEDURE create_materialized_view (
_tablename varchar(100),
_column varchar(100),
_year integer
)
LANGUAGE 'plpgsql' AS
$proc$
DECLARE
_vw text := 'view_' || _tablename || '_' || _year;
_sql text :=
'CREATE MATERIALIZED VIEW %I AS
SELECT * FROM %I WHERE %I = %L';
BEGIN
EXECUTE format(_sql, _vw, _tablename, _column, _year);
END
$proc$

Related

PostgreSQL: dealing with variables and apostrophes in dblink query

I am trying to execute dblink, and into it's query I need to put variables, but the problem is that dblink executes string and before it's execution "converts" variables into that string.
p_int int := 1;
p_text text;
p_apostroph text := '''';
p_sql text;
p_text := (select columnXY from table2 where id =1);
p_sql: =
'insert into table (column1,column2,column3,column4)
select 1
' || p_int || ',
' || p_apostroph || p_text || p_apostroph || ',
''text''
';
dblink_exec('connection' , p_sql);
As seen in the code problem comes with text variables (and nightmare with arrays), because I already have to manually put my text variable p_text between apostrophes, and by nature I do not know what it contains (I fill it from another table or user interface or any other source I do not control). So every time this variable contains and apostrophe it ends up with error because string get's broken. So this means I would have to come up with some complicated string analyse to counter all possibilities.
Is there any other way how to put variables into dblink query without putting them into string?
PostgreSQL 11.6 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 4.8.5
20150623 (Red Hat 4.8.5-39), 64-bit
as suggested by #a_horse_with_no_name I tried Dollar-Quoted String
create table table1 (column1 int, column2 int, column3 text, column4 text);
create table table2 (column1 text, id int);
insert into table2 values ('unsafe ''',1);
insert into table2 values ('safe ',2);
create or replace function test (p_id int) returns integer as
$body$
declare
p_int int := 1;
p_text text;
p_apostroph text := '''';
p_sql text;
begin
p_text := (select column1 from table2 where id = p_id);
p_sql:=
$$
insert into table1(column1,column2,column3,column4)
select
1,
$$ || p_int || $$,
$$ || p_apostroph || p_text || p_apostroph || $$,
'textz'
$$
;
raise notice '%', p_sql;
perform dblink_exec('connection' , p_sql );
return 1;
end;
$body$ LANGUAGE plpgsql
select * from test (2); -- no problem
select * from test (1); -- error
[Code: 0, SQL State: 42601] ERROR: unterminated quoted string at or
near "' " Kde: while executing command on unnamed dblink connection
SQL statement "SELECT dblink_exec('connection' , p_sql )" PL/pgSQL
function s5_text_apostrop(integer) line 22 at PERFORM
You forgot a comma in the INSERT statement.
Your code is also vulnerable to SQL injection. Use format to avoid that and make your code more readable:
p_sql: = format(
'insert into atable (column1, column2, column3, column4)'
'select 1, %s, %L, %L',
p_int, p_text, 'text'
);

PL/pgSQL: How to use IF NEW.<variable_column_name> <> OLD.<variable_column_name>

I am pretty new to PL/pgSQL programming. I have a requirement of audit logging updated columns in my table
Table
create table sample_table(name varchar(15),city varchar(15),age int,mail varchar(20) primary key);
Audit table
create table sample_table__audits_dynamicols(mail varchar(20), columnchanged varchar(10), oldvalue varchar(10), changed_on timestamp(6) NOT NULL)
Trigger Function
CREATE FUNCTION public.log_sample_table_allchanges() RETURNS trigger AS $BODY$DECLARE
_colname text;
_tablename varchar(15) := 'sample_table';
_schema varchar(15) := 'public';
_changed_on time := now();
BEGIN
FOR _colname IN SELECT column_name FROM information_schema.Columns WHERE table_schema = _schema AND table_name = _tablename LOOP
IF NEW._colname <> OLD._colname THEN
INSERT INTO sample_table__audits_dynamicols(mail,columnchanged, oldvalue ,changed_on)
VALUES(OLD.mail,_colname,OLD.:_colname,_changed_on);
END IF;
END LOOP;
RETURN NEW;
END$BODY$
LANGUAGE plpgsql VOLATILE NOT LEAKPROOF;
Trigger
create TRIGGER log_sample_table_allchanges
BEFORE UPDATE
ON SAMPLE_TABLE
FOR EACH ROW
EXECUTE PROCEDURE log_sample_table_allchanges();
Requirement: Whenever a column value is changed i want to log it as
(mail, columnname, columnvalue, date)
E.g:
insert into sample_table (name, mail, city, age) values('kanta','mk#foo.com','hyd',23);
insert into sample_table (name, mail, city, age) values('kmk','mk#gmail.com','hyd',23);
So when i update like the following
update sample_table set age=24 where mail='mk#foo.com';
update sample_table set city='bza' where mail='mk#gmail.com'
I want audit table to record like
(mk#foo.com,age,23, timestamp)
(mk#gmail.com, city, hyd, timestamp)
Right now I am facing issue with column comparison in my Trigger function. Please help me rectifying my Trigger function to meet my requirement.
You may use EXECUTE to get the values of columns dynamically and do the comparison.
CREATE OR REPLACE FUNCTION public.log_sample_table_allchanges() RETURNS trigger AS
$BODY$
DECLARE
_colname text;
_tablename varchar(15) := 'sample_table';
_schema varchar(15) := 'public';
_changed_on timestamp := now();
_old_val text;
_new_val text;
BEGIN
FOR _colname IN SELECT column_name FROM information_schema.Columns WHERE table_schema = _schema AND table_name = _tablename
LOOP
EXECUTE 'SELECT $1.' || _colname || ', $2.' || _colname
USING OLD,NEW
INTO _old_val, _new_val; --get the old and new values for the column.
IF _new_val <> _old_val THEN
INSERT INTO sample_table__audits_dynamicols(mail,columnchanged, oldvalue ,changed_on)
VALUES(OLD.mail,_colname,_old_val,_changed_on);
END IF;
END LOOP;
RETURN NEW;
END$BODY$
LANGUAGE plpgsql VOLATILE NOT LEAKPROOF;
I'm not sure why you have defined mail as a PRIMARY KEY in the audits table, it will cause unique constraint violation if the same mail gets updated twice.

Check to see if a record exists postgres function

I am attempting to create a function that will determine if a record exists for our applications developers to help simplify things.
CREATE FUNCTION records_exist(schema_name VARCHAR(255), table_name VARCHAR(255), field_name VARCHAR(255), field_value VARCHAR(255))
RETURNS BOOLEAN
LANGUAGE plpgsql
AS $$
DECLARE
_schema_name ALIAS FOR $1;
_table_name ALIAS FOR $2;
_field_name ALIAS FOR $3;
_field_value ALIAS FOR $4;
_sql_string VARCHAR(5000);
BEGIN
_sql_string= 'SELECT EXISTS(SELECT 1 FROM ' || _schema_name || '.' || _table_name || ' WHERE ' || _field_name || '=' || _field_value || ');';
RETURN BOOLEAN EXECUTE _sql_string;
--RETURN TABLE EXECUTE _sql_string; doesn't work
END
$$;
The following should work, but I keep getting ERROR: syntax error at or near "EXECUTE"
Please let me know the error of my ways.
also, your dynamic string is bad, better use:
select format('select exists from %I.%I where %I = %L',schema_name,table_name,field_name, field_value) into _sql_string;
also, you realize your _field_value has no check for data type?..
so in short, it could be smth similar to:
db=# CREATE or replace FUNCTION records_exist(schema_name VARCHAR(255), table_name VARCHAR(255), field_name VARCHAR(255), field_value VARCHAR(255))
RETURNS BOOLEAN
LANGUAGE plpgsql
AS $$
DECLARE
_sql text;
_b boolean;
BEGIN
_sql := format('select exists (select null from %I.%I where %I = %L)',schema_name,table_name,field_name, field_value);
execute _sql into _b;
return _b;
END
$$;
CREATE FUNCTION
Time: 10.680 ms
db=# select * from records_exist('pg_catalog','pg_database','datname','postgres'); records_exist
---------------
t
(1 row)
Time: 59.472 ms

How to use argument for table name in dynamic SQL

I am writing a Postgres function to get the number of new records in a table. Here table name is a variable.
create or replace function dmt_mas_updates(
tb_name text,
days integer)
returns integer as
$$
declare
ct integer;
begin
execute 'select count(*) from $1 where etl_create_dtm > now() - $2 * interval ''1 days'' '
using tb_name, days into ct;
return ct;
end;
$$ LANGUAGE 'plpgsql'
When I call the function with select * from dmt_mas_updates('dmt_mas_equip_store_dim',2);, I got syntax error at $1.
If I run the query directly select count(*) from dmt_mas_equip_store_dim where etl_create_dtm >= interval '3 days', it works correctly.
Why am I getting this error? What did I do wrong?
Per the documentation:
Note that parameter symbols can only be used for data values — if you want to use dynamically determined table or column names, you must insert them into the command string textually.
Use the format() function:
create or replace function dmt_mas_updates(
tb_name text,
days integer)
returns integer as
$$
declare
ct integer;
begin
execute format(
'select count(*) from %I where etl_create_dtm > now() - $1 * interval ''1 days'' ',
tb_name)
using days into ct;
return ct;
end;
$$ LANGUAGE 'plpgsql';

PostgreSQL 9.3: Check only time from timestamp

I have the following table with one field of type timestamp.
Create table Test_Timestamp
(
ColumnA timestamp
);
Now inserting some records for demonstration:
INSERT INTO Test_Timestamp VALUES('1900-01-01 01:21:15'),
('1900-01-01 02:11:25'),
('1900-01-01 12:52:10'),
('1900-01-01 03:20:05');
Now I have created function Function_Test with two parameters namely St_time and En_Time which
are of type varchar, In which I only pass the time like 00:00:01. And after that Function has
to return the table with that condition of two time's parameters.
CREATE OR REPLACE FUNCTION Function_Test
(
St_Time varchar,
En_Time varchar
)
RETURNS TABLE
(
columX timestamp
)
AS
$BODY$
Declare
sql varchar;
wher varchar;
BEGIN
wher := 'Where columna BETWEEN '|| to_char(cast(St_Time as time),'''HH24:MI:SS''') ||' AND '|| to_char(cast(En_Time as time),'''HH24:MI:SS''') ||'';
RAISE INFO '%',wher;
sql := 'SELECT * FROM Test_Timestamp ' || wher ;
RAISE INFO '%',sql;
RETURN QUERY EXECUTE sql;
END;
$BODY$
LANGUAGE PLPGSQL;
---Calling function
SELECT * FROM Function_Test('00:00:00','23:59:59');
But getting an error:
ERROR: invalid input syntax for type timestamp: "00:00:01"
LINE 1: ...ELECT * FROM Test_Timestamp where ColumnA BETWEEN '00:00:01'...
You can cast the column to a time: ColumnA::time
You should also not pass a time (or a date, or a timestamp) as a varchar. And you don't need dynamic SQL or a PL/pgSQL function for this:
CREATE OR REPLACE FUNCTION Function_Test(St_Time time, en_Time time)
RETURNS TABLE (columX timestamp)
AS
$BODY$
SELECT *
FROM Test_Timestamp
where columna::time between st_time and en_time;
$BODY$
LANGUAGE sql;
Call it like this:
select *
from Function_Test(time '03:00:00', time '21:10:42');
You can use extract to the hour, minute and second
http://www.postgresql.org/docs/9.3/static/functions-datetime.html#FUNCTIONS-DATETIME-EXTRACT