How to list columns of a Row? - postgresql

CREATE FUNCTION cs_refresh_mviews() RETURNS integer AS $$
DECLARE
mviews RECORD;
BEGIN
PERFORM cs_log('Refreshing materialized views...');
FOR mviews IN SELECT * FROM cs_materialized_views ORDER BY sort_key LOOP
-- How For columns of mviews?
END LOOP;
PERFORM cs_log('Done refreshing materialized views.');
RETURN 1;
END;
$$ LANGUAGE plpgsql;
I want get value of columns in mviews.
How browser columns of mviews use to For or While?
The same as:
For i=0 to mviews.columns.count step i++
raise mviews[i]

Materialized Views, really? Are you on 9.3 already?
Anyways your question is not very clear, are you trying to iterate for all the columns in each view?
The Information Schema and information_schema.columns in particular can be what you are looking for.
Assuming you have some schema and name fields in cs_materialized_views table, you can do something like that:
Declare another Record variable: mcols RECORD;
And put this inside your loop:
FOR mcols IN (SELECT ordinal_position, column_name FROM information_schema.columns WHERE table_schema = mviews.schema AND table_name = mviews.name ORDER BY ordinal_position) LOOP
RAISE NOTICE 'View %, Column no. %: %', mviews.table_name, mcols.ordinal_position, mcols.column_name;
END LOOP;

Related

Postgres Update Statement: how to set a column's value only if the column name contains a certain substring?

Each of the following tables ['table1', 'table2'] are part of the public schema, knowing that each table may contain multiple columns containing the substring 'substring' in the name for Example let's look at the following :
Table_1 (xyz, xyz_substring,...some_other_columns... abc,
abc_substring)
Table_2 (xyz, xyz_substring,..some_other_columns... abc,
abc_substring)
I am coming to this from a pythonic way of thinking, but basically, how could one execute a statement without knowing exactly what to set since the columns we need to target need to meet a certain criteria ?
I had the idea to add a another loop over the column names of the current table and check if the name meets the criteria. then execute the query but It feels very far away from optimal.
DO $$
declare
t text;
tablenames TEXT ARRAY DEFAULT ARRAY['table_1', 'table_2'];
BEGIN
FOREACH t IN ARRAY tablenames
LOOP
raise notice 'table(%)', t;
-- update : for all the column that contain 'substring' in their names set a value
END LOOP;
END$$;
EDIT:
Thanks for the answer #Stefanov.sm , i followed exactly your thought and was able to base your logic to have just one statement :
DO $$
declare
t text;
tablenames TEXT ARRAY DEFAULT ARRAY['table_1', 'table_2'];
dynsql text;
colname text;
BEGIN
FOREACH t IN ARRAY tablenames LOOP
raise notice 'table (%)', t;
dynsql := format('update %I set', t);
for colname in select column_name from information_schema.columns where table_schema = 'public' and table_name = t
loop
if colname like '%substring%' then
dynsql := concat(dynsql,format(' %I = ....whatever expression here (Make sure to check if you should use Literal formatter %L if needed) ....,',colname,...whateverargs...));
end if;
end loop;
dynsql := concat(dynsql,';'); -- not sure if required.
raise notice 'SQL to execute (%)', dynsql;
execute dynsql;
END LOOP;
END;
$$;
Extract the list of columns of each table and then format/execute dynamic SQL. Something like
DO $$
declare
t text;
tablenames text[] DEFAULT ARRAY['table_1', 'table_2'];
dynsql text;
colname text;
BEGIN
FOREACH t IN ARRAY tablenames LOOP
raise notice 'table (%)', t;
for colname in select column_name
from information_schema.columns
where table_schema = 'public' and table_name = t loop
if colname ~ '__substring$' then
dynsql := format('update %I set %I = ...expression... ...other clauses if any...', t, colname);
raise notice 'SQL to execute (%)', dynsql;
execute dynsql;
end if;
end loop;
END LOOP;
END;
$$;
This will cause excessive bloat so do not forget to vacuum your tables. If your tables' schema is not public then edit the select from information_schema accordingly. You may use pg_catalog resource instead of information_schema too.

Fully replace a record with unbound PostgreSQL cursor

Is it possible to use unbound cursors to fully edit and replace a row in a table?
I'm using unbound cursors since the table is dynamically specified with a parameter, but I can't use the "UPDATE table SET column = value WHERE" syntax since the columns are unspecified.
CREATE OR REPLACE FUNCTION trim_table(in_table TEXT) AS $$
DECLARE
ref REFCURSOR;
current_row RECORD;
BEGIN
OPEN ref FOR EXECUTE 'SELECT * FROM '|| quote_ident(in_table);
LOOP
FETCH ref INTO current_row;
EXIT WHEN NOT FOUND;
current_row = my_row_function(current_row);
/*How can I replace my row here?*/
END LOOP;
CLOSE ref;
END
$$ LANGUAGE plpgsql;
All the example and answers I found show only how to update a single field and not the full record.
I think this code can help you in some ways :
select
string_agg('UPDATE '||table_schema||'.'||table_name||chr(13)||' SET '||column_name||' = TRIM('||column_name||')', '; '||chr(13)) into query
from information_schema.columns
where data_type in ('varchar', 'text')
and table_schema = 'your_schema'
and table_name = 'your_table_name';
execute query;
Put it in your procedure, modify it to your convenience, and you will no longer need this loop.

Loop through all user tables and insert row in each

for some reason I just can not figure this out. I have a seperate schema in PostgreSQL for notification related tables for each user connected to the server. My plan is to have each user create a TEMP table to receive extra notification info from since Xojo doesn't support PostgreSQL payloads.
I feel like I'm starting to get close so I'll just post my code that is in my trigger function.
DECLARE
my_table RECORD;
BEGIN
FOR my_table IN
SELECT table_name
FROM information_schema.tables
WHERE table_schema = 'information_schema'
LOOP
INSERT INTO my_table.table_name (effected_row_id)
VALUES (NEW.effected_row_id);
END LOOP;
END;
Tell me if I'm wrong, but I believe my main problem is figuring out how to use the table name returned from the SELECT statement in the INSERT statement.
EDIT:
This is my current trigger function
-- Function: notification.my_insert_trigger_function()
-- DROP FUNCTION notification.my_insert_trigger_function();
CREATE OR REPLACE FUNCTION notification.my_insert_trigger_function()
RETURNS trigger AS
$BODY$DECLARE
my_table RECORD;
BEGIN
FOR my_table IN
SELECT table_name
FROM information_schema.tables
WHERE table_schema = 'notification' AND table_name <> 'notification_global' AND table_name <> 'switcher'
LOOP
EXECUTE(FORMAT($f$
INSERT INTO %s (effected_row_username)
VALUES (%s);
$f$, 'notification.' || my_table.table_name, NEW.effected_row_username));
END LOOP;
RETURN new;
END;$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION notification.my_insert_trigger_function()
OWNER TO serveradmin;
You need to use dynamic commands in your trigger function.
The funcion format() is often very helpful.
DECLARE
my_table RECORD;
BEGIN
FOR my_table IN
SELECT table_name
FROM information_schema.tables
WHERE table_schema = 'information_schema'
LOOP
EXECUTE(FORMAT($f$
INSERT INTO %s (effected_row_id)
VALUES (%s);
$f$, my_table.tablename, NEW.effected_row_id));
END LOOP;
END;

Only perform update if column exists

Is it possible to execute an update conditionally if a column exists?
For instance, I may have a column in a table and if that column exists I want that update executed, otherwise, just skip it (or catch its exception).
You can do it inside a function. If you don't want to use the function later you can just drop it afterwards.
To know if a column exists in a certain table, you can try to fetch it using a select(or a perform, if you're gonna discard the result) in information_schema.columns.
The query bellow creates a function that searches for a column bar in a table foo, and if it finds it, updates its value. Later the function is run, then droped.
create function conditional_update() returns void as
$$
begin
perform column_name from information_schema.columns where table_name= 'foo' and column_name = 'bar';
if found then
update foo set bar = 12345;
end if;
end;
$$ language plpgsql;
select conditional_update();
drop function conditional_update();
With the following table as example :
CREATE TABLE mytable (
idx INT
,idy INT
);
insert into mytable values (1,2),(3,4),(5,6);
you can create a custom function like below to update:
create or replace function fn_upd_if_col_exists(_col text,_tbl text,_val int) returns void as
$$
begin
If exists (select 1
from information_schema.columns
where table_schema='public' and table_name=''||_tbl||'' and column_name=''||_col||'' ) then
execute format('update mytable set '||_col||'='||_val||'');
raise notice 'updated';
else
raise notice 'column %s doesn''t exists on table %s',_col,_tbl;
end if;
end;
$$
language plpgsql
and you can call this function like:
select fn_upd_if_col_exists1('idz','mytable',111) -- won't update raise "NOTICE: column idz deosnt exists on table mytables"
select fn_upd_if_col_exists1('idx','mytable',111) --will upadate column idx with value 1111 "NOTICE: updated"

Variables for identifiers inside IF EXISTS in a plpgsql function

CREATE OR REPLACE FUNCTION drop_now()
RETURNS void AS
$BODY$
DECLARE
row record;
BEGIN
RAISE INFO 'in';
FOR row IN
select relname from pg_stat_user_tables
WHERE schemaname='public' AND relname LIKE '%test%'
LOOP
IF EXISTS(SELECT row.relname.tm FROM row.relname
WHERE row.relname.tm < current_timestamp - INTERVAL '90 minutes'
LIMIT 1)
THEN
-- EXECUTE 'DROP TABLE ' || quote_ident(row.relname);
RAISE INFO 'Dropped table: %', quote_ident(row.relname);
END IF;
END LOOP;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;
Could you tell me how to use variables in SELECT which is inside IF EXISTS? At the present moment, row.relname.tm and row.relname are treated literally which is not I want.
CREATE OR REPLACE FUNCTION drop_now()
RETURNS void AS
$func$
DECLARE
_tbl regclass;
_found int;
BEGIN
FOR _tbl IN
SELECT relid
FROM pg_stat_user_tables
WHERE schemaname = 'public'
AND relname LIKE '%test%'
LOOP
EXECUTE format($f$SELECT 1 FROM %s
WHERE tm < now() - interval '90 min'$f$, _tbl);
GET DIAGNOSTICS _found = ROW_COUNT;
IF _found > 0 THEN
-- EXECUTE 'DROP TABLE ' || _tbl;
RAISE NOTICE 'Dropped table: %', _tbl;
END IF;
END LOOP;
END
$func$ LANGUAGE plpgsql;
Major points
row is a reserved word in the SQL standard. It's use is allowed in Postgres, but it's still unwise. I make it a habbit to prepend psql variable with an underscore _ to avoid any naming conflicts.
You don't don't select the whole row anyway, just the table name in this example. Best use a variable of type regclass, thereby avoiding SQL injection by way of illegal table names automatically. Details in this related answer:
Table name as a PostgreSQL function parameter
You don't need LIMIT in an EXISTS expression, which only checks for the existence of any rows. And you don't need meaningful target columns for the same reason. Just write SELECT 1 or SELECT * or something.
You need dynamic SQL for queries with variable identifiers. Plain SQL does not allow for that. I.e.: build a query string and EXECUTE it. Details in this closely related answer:
Dynamic SQL (EXECUTE) as condition for IF statement
The same is true for a DROP statement, should you want to run it. I added a comment.
You'll need to build your query as a string then execute that - see the section on executing dynamic commands in the plpgsql section of the manual.