Alter each column named 'xxx' - sql

I'm trying to create a PostgreSQL stored procedure that must set default value 'now()' to each columns named 'CreationDate' in every tables.
CREATE OR REPLACE FUNCTION set_creation_date() RETURNS
void AS $$
DECLARE
t pg_tables%ROWTYPE;
BEGIN
FOR t IN SELECT "tablename" FROM pg_tables WHERE "schemaname" = 'public' LOOP
IF EXISTS (select * from information_schema.columns where table_name = t."tablename"
and column_name = 'CreationDate') THEN
EXECUTE FORMAT('ALTER TABLE %I ALTER COLUMN "CreationDate" SET DEFAULT now()', t."tablename");
END IF;
END LOOP;
END;
$$ LANGUAGE plpgsql;
But no columns are affected. What's wrong?

It looks like it's trying to alter t."tablename" every time. Try the following instead:
EXECUTE FORMAT('ALTER TABLE %I ALTER COLUMN "CreationDate" SET DEFAULT now()', t."tablename");
I don't have a PostgreSQL server available to test, so please let me know if the syntax isn't quite right.

I finally resolved. The error was in pg_tables%ROWTYPE: using debugger i realized that the field 'tablename' doesn't exist, so i declared 't' as text.
I created a function with parameters, where you have to pass schema name and column name. I ll post it here for anyone who ll need it
DROP FUNCTION IF EXISTS set_creation_date(character varying, character varying);
CREATE OR REPLACE FUNCTION set_creation_date(_schema_name character varying, _column_date character varying)
RETURNS void AS
$BODY$
DECLARE
t TEXT;
BEGIN
FOR t IN SELECT "tablename" FROM pg_tables WHERE "schemaname" = _schema_name LOOP
IF EXISTS (select * from information_schema.columns where table_name = t and column_name = _column_date) THEN
EXECUTE FORMAT('ALTER TABLE %I ALTER COLUMN %I SET DEFAULT now()', t, _column_date);
END IF;
END LOOP;
END;
$BODY$
LANGUAGE plpgsql;

Related

PL/pgSQL select statement inside trigger returns null using where NEW

i'm creating a trigger that triggers on INSERT on a table,
and i wish to log the structure of tables inserted so i wrote this Function
CREATE OR REPLACE FUNCTION update_table_log_received()
RETURNS TRIGGER AS $$
DECLARE
added_column TEXT;
target_table_name TEXT;
old_column text;
BEGIN
-- Check if a new column has been added
IF (TG_OP = 'INSERT') THEN
added_column := NEW."COLUMN_NAME";
target_table_name := NEW."TABLE_NAME";
END IF;
SELECT column_name into old_column
FROM information_schema."columns"
WHERE table_schema = 'items'
and table_name = LOWER(NEW."TABLE_NAME")
and column_name = LOWER(NEW."COLUMN_NAME");
if (coalesce(old_column,'')='' or old_column='' or old_column = added_column) THEN
-- If a new column has been added
IF (Lower(added_column) != 'sync') then
-- Add the new column to the target table
EXECUTE 'ALTER TABLE items.' || LOWER(target_table_name)|| ' ADD COLUMN ' || LOWER(added_column) || ' VARCHAR(50)';
END IF;
end if;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
executed by this TRIGGER :
CREATE TRIGGER update_table_log_received_trigger
AFTER INSERT
ON items."TABLE_LOG_RECEIVED"
FOR EACH ROW
EXECUTE FUNCTION update_table_log_received();
the returned exception is the following :
! ERROR: the column « x » of the relation « y » already exists
Where: instruction SQL « ALTER TABLE items. ADD COLUMN x VARCHAR(50) »
my problem now is that it isn't supposed to pass the If checks (i pasted the code after many alterations i have two if conditions that do the same thing just because),
i debugged and logged the statements to note that the select query inside my function returns null apparently.
i also tried to use "USING NEW" but i am no expert so i couldn't make it work
is it a problem with the declared variable not being populated from the "NEW" record or am i executing the select statement wrong ?
EDIT : tl;dr for my problem, I would like to update a table in Database2 whenever the same table (that had the same structre) is altered from Database1, be it added column or changed column, at this point iI'm stuck at the first problem to add the column.
I am logging my tables' structures as strings into a new table and syncing that with Database2 to then have the trigger alter the same altered table from Database1, hope this makes more sense now.
Database1 log_table that logs all my tables' structures:
Database2 log_table_received that is a copy of log_table that executes
the trigger whenever new values are inserted;
Try this syntax:
CREATE OR REPLACE FUNCTION update_table_log_received()
RETURNS TRIGGER AS $$
DECLARE
added_column TEXT;
target_table_name TEXT;
old_column text;
BEGIN
-- Check if a new column has been added
IF (TG_OP = 'INSERT') THEN
added_column := new."column_name";
target_table_name := new."table_name";
END IF;
if not exists(select 1 from information_schema."columns" where table_name = target_table_name and column_name = added_column)
then
EXECUTE 'ALTER TABLE items.' || LOWER(target_table_name)|| ' ADD COLUMN ' || LOWER(added_column) || ' VARCHAR(50)';
end if;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
I tried on my DB this is works. You can change some details yourself.
Fixed; Question should have been :
How to select tables & table columns inside function in postgresql.
References:
How to add column if not exists on PostgreSQL?
How to check if a table exists in a given schema
How to get a list column names and datatypes of a table in PostgreSQL?
Basically information_schema can only be accessed by owner meaning the user or (i) see the result when i query it but it returns FALSE when executed inside a script more details here :
https://stackoverflow.com/a/24089729/15170264
Full trigger after fix with CTE to query the pg_catalog also added ADD COLUMN IF NOT EXISTS in my Execute query just to be safe
CREATE OR REPLACE FUNCTION update_table_log_received()
RETURNS TRIGGER AS $$
DECLARE
added_column TEXT;
target_table_name TEXT;
old_column varchar;
old_table varchar;
BEGIN
-- Check if a new column has been added
IF (TG_OP = 'INSERT') THEN
added_column := NEW."COLUMN_NAME";
target_table_name := NEW."TABLE_NAME";
END IF;
/*
* --------------- --CTE to find Columns of table "Target_table_name" from pg_catalog
*/
WITH cte_tables AS (
SELECT
pg_attribute.attname AS column_name,
pg_catalog.format_type(pg_attribute.atttypid, pg_attribute.atttypmod) AS data_type
FROM
pg_catalog.pg_attribute
INNER JOIN
pg_catalog.pg_class ON pg_class.oid = pg_attribute.attrelid
INNER JOIN
pg_catalog.pg_namespace ON pg_namespace.oid = pg_class.relnamespace
WHERE
pg_attribute.attnum > 0
AND NOT pg_attribute.attisdropped
AND pg_namespace.nspname = 'items'
AND pg_class.relname = 'trace'
ORDER BY
attnum ASC
)
select column_name into old_column from cte_tables where
column_name=LOWER(added_column);
if (old_column is null ) then
-- Add the new column to the target table
old_column := added_column;
EXECUTE 'ALTER TABLE items.' || LOWER(target_table_name)|| ' ADD COLUMN IF NOT EXISTS ' || LOWER(added_column) || ' VARCHAR(50)';
else
old_column := added_column || 'already exists ! ';
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
create TRIGGER update_table_log_received_trigger
AFTER INSERT
ON items."TABLE_LOG_RECEIVED"
FOR EACH ROW
EXECUTE FUNCTION update_table_log_received();
Variable old_column stores the else condition message but i do not return it, would have if it was a simple function.

Truncate if exists in psql function and call function

I have the following code to create a function that truncates all rows from the table web_channel2 if the table is not empty:
create or replace function truncate_if_exists(tablename text)
returns void language plpgsql as $$
begin
select
from information_schema.tables
where table_name = tablename;
if found then
execute format('truncate %I', tablename);
end if;
end $$;
Unfortunately I don't know how should I continue ...
How to execute the function?
TLDR
To execute a Postgres function (returning void), call it with SELECT:
SELECT truncate_if_exists('web_channel2');
Proper solution
... how should I continue?
Delete the function again.
DROP FUNCTION truncate_if_exists(text);
It does not offer any way to schema-qualify the table. Using it might truncate the wrong table ...
Looks like you are trying to avoid an exception if the table is not there.
And you only want to truncate ...
if the table is not empty
To that end, I might use a safe function like this:
CREATE OR REPLACE FUNCTION public.truncate_if_exists(_table text, _schema text DEFAULT NULL)
RETURNS text
LANGUAGE plpgsql AS
$func$
DECLARE
_qual_tbl text := concat_ws('.', quote_ident(_schema), quote_ident(_table));
_row_found bool;
BEGIN
IF to_regclass(_qual_tbl) IS NOT NULL THEN -- table exists
EXECUTE 'SELECT EXISTS (SELECT FROM ' || _qual_tbl || ')'
INTO _row_found;
IF _row_found THEN -- table is not empty
EXECUTE 'TRUNCATE ' || _qual_tbl;
RETURN 'Table truncated: ' || _qual_tbl;
ELSE -- optional!
RETURN 'Table exists but is empty: ' || _qual_tbl;
END IF;
ELSE -- optional!
RETURN 'Table not found: ' || _qual_tbl;
END IF;
END
$func$;
To execute, call it with SELECT:
SELECT truncate_if_exists('web_channel2');
If no schema is provided, the function falls back to traversing the search_path - like your original did. If that's unreliable, or generally, to be safe (which seems prudent when truncating tables!) provide the schema explicitly:
SELECT truncate_if_exists('web_channel2', 'my_schema');
db<>fiddle here
When providing identifiers as strings, you need to use exact capitalization.
Why the custom variable _row_found instead of FOUND? See:
Dynamic SQL (EXECUTE) as condition for IF statement
Basics:
Table name as a PostgreSQL function parameter
How to check if a table exists in a given schema
PL/pgSQL checking if a row exists
How does the search_path influence identifier resolution and the "current schema"
Are PostgreSQL column names case-sensitive?

PostgreSQL: How to add a column in every table of a database?

I have a database with 169 tables
I need this column in every table:
wid integer not null primary key
I tried this(Thanks https://stackoverflow.com/users/27535/gbn for the solution):
SELECT
'ALTER TABLE ' + T.name + ' ADD foo int NULL'
FROM
sys.tables AS T
WHERE
T.is_ms_shipped = 0
But it didn't work on PostgreSQL.
It only worked on tsql.
How to add this column in every table at once ?
do $$
declare
selectrow record;
begin
for selectrow in
select
'ALTER TABLE '|| T.mytable || ' ADD COLUMN foo integer NULL' as script
from
(
select tablename as mytable from pg_tables where schemaname ='public' --your schema name here
) t
loop
execute selectrow.script;
end loop;
end;
$$;
You can test whether all your tables altered with the new column using the following select
select
table_name,COLUMN_NAME
from
INFORMATION_SCHEMA.COLUMNS
where
COLUMN_NAME='foo' -- column name here
Try this (change 'public' to whatever schema you're doing this in)
DO $$
DECLARE
row record;
cmd text;
BEGIN
FOR row IN SELECT schemaname, tablename FROM pg_tables WHERE schemaname = 'public' LOOP
cmd := format('ALTER TABLE %I.%I ADD COLUMN foo SERIAL PRIMARY KEY ', row.schemaname, row.tablename);
RAISE NOTICE '%', cmd;
-- EXECUTE cmd;
END LOOP;
END
$$ LANGUAGE plpgsql;
If you run as is, it'll show you the commands. Uncomment the EXECUTE line to actually perform the alterations.
I'd run within a transaction so you can roll back if you're not happy with the results.
Note that the type is SERIAL - the column type will be integer, but also creates a sequence owned by the table and defaults the column value to the next value of that sequence.
We may need to check column is already exist or not.
Tested on PostgreSQL V10
do $$
declare selectrow record;
begin
for selectrow in
select 'ALTER TABLE '|| T.mytable || ' ADD COLUMN x_is_exported boolean DEFAULT FALSE' as script
from (select tablename as mytable from pg_tables where schemaname ='public') t
loop
begin
execute selectrow.script;
EXCEPTION WHEN duplicate_column THEN CONTINUE;
END;
end loop;
end;
$$;

PostgreSQL: Create table if not exists AS

I'm using PostgreSQL and am an SQL beginner. I'm trying to create a table from a query, and if I run:
CREATE TABLE table_name AS
(....query...)
it works just fine. But then if I add 'if not exists' and run:
CREATE TABLE IF NOT EXISTS table_name AS
(....query...)
using exactly the same query, I get:
ERROR: syntax error at or near "as"
Is there any way to do this?
CREATE TABLE AS is considered a separate statement from a normal CREATE TABLE, and until Postgres version 9.5 (see changelog entry) didn't support an IF NOT EXISTS clause. (Be sure to look at the correct version of the manual for the version you are using.)
Although not quite as flexible, the CREATE TABLE ... LIKE syntax might be an alternative in some situations; rather than taking its structure (and content) from a SELECT statement, it copies the structure of another table or view.
Consequently, you could write something like this (untested); the final insert is a rather messy way of doing nothing if the table is already populated:
CREATE OR REPLACE VIEW source_data AS SELECT * FROM foo NATURAL JOIN bar;
CREATE TABLE IF NOT EXISTS snapshot LIKE source_data;
INSERT INTO snapshot
SELECT * FROM source_data
WHERE NOT EXISTS ( SELECT * FROM snapshot );
Alternatively, if you want to discard previous data (e.g. an abandoned temporary table), you could conditionally drop the old table, and unconditionally create the new one:
DROP TABLE IF EXISTS temp_stuff;
CREATE TEMPORARY TABLE temp_stuff AS SELECT * FROM foo NATURAL JOIN bar;
CREATE TABLE IF NOT EXISTS ... was added in Postgres 9.1. See:
PostgreSQL create table if not exists
Postgres 9.0 or older
If you are going to write a function for this, base it on system catalog table pg_class, not on views in the information schema or the statistics collector (which only exist if activated).
How to check if a table exists in a given schema
CREATE OR REPLACE FUNCTION create_table_qry(_tbl text
, _qry text
, _schema text = NULL)
RETURNS bool
LANGUAGE plpgsql AS
$func$
DECLARE
_sch text := COALESCE(_schema, current_schema());
BEGIN
IF EXISTS (
SELECT FROM pg_catalog.pg_class c
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
WHERE n.nspname = _sch
AND c.relname = _tbl
) THEN
RAISE NOTICE 'Name is not free: %.%',_sch, _tbl;
RETURN FALSE;
ELSE
EXECUTE format('CREATE TABLE %I.%I AS %s', _sch, _tbl, _qry);
RAISE NOTICE 'Table created successfully: %.%',_sch, _tbl;
RETURN TRUE;
END IF;
END
$func$;
The function takes a table name and the query string, and optionally also a schema to create the table in (defaults to the current schema).
Note the correct use of = in the function header and := in the function body:
The forgotten assignment operator "=" and the commonplace ":="
Also note how identifiers are escaped as identifiers. You can't use regclass, since the table does not exist, yet:
Table name as a PostgreSQL function parameter
Try this,
create or replace function create_table(tblname text) returns text as
$$
BEGIN
$1 = trim($1);
IF not EXISTS (select relname from pg_stat_user_tables where relname =$1) THEN
execute 'create table '||$1||' as select * from tbl'; -- <put your query here>
return ''||$1||' Created Successfully !!';
else
return ''||$1||' Already Exists !!';
END IF;
END
$$
language plpgsql
create or replace function create_table_qry(tblname text,qry text) returns text as
$$
BEGIN
$1 = trim($1);
IF not EXISTS (select relname from pg_stat_user_tables where relname =$1) THEN
execute 'create table '||$1||' as '||$2||'';
return ''||$1||' Created Successfully !!';
else
return ''||$1||' Already Exists !!';
END IF;
END
$$
language plpgsql
It’s simple:
CREATE TABLE IF NOT EXISTS abc ( sql_id BIGINT(20) NOT NULL
AUTO_INCREMENT PRIMARY KEY, sender VARCHAR(20) NULL)
Use do :
do $$ begin
if not exists ( SELECT 1
FROM information_schema.tables
WHERE table_schema = 'schema_name'
AND table_name = 'bla ') then
create table schema_name.bla as select * from blu;
end if;
end $$;
CTAS (Create Table AS) for REDSHIFT PLPGSQL flavor. Shout out to Erwin Brandstetter for the root idea using pure PG syntax.
CREATE
OR
REPLACE
PROCEDURE pipeline.sp_create_table_if_not_exists_as (sch VARCHAR, tbl VARCHAR, qry VARCHAR, tbl_attrs VARCHAR)
AS
/*
specifically an exception for CTAS functionality: https://docs.aws.amazon.com/redshift/latest/dg/r_CREATE_TABLE_AS.html
*/
$$
BEGIN
IF EXISTS (
SELECT 1
FROM pg_catalog.pg_class c
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
WHERE n.nspname = sch
AND c.relname = tbl
) THEN
RAISE INFO 'Table already exists: %.%', sch, tbl;
ELSE
EXECUTE 'CREATE TABLE ' || sch || '.' || tbl || ' ' || tbl_attrs || ' AS ' || qry;
RAISE INFO 'Table created successfully: %.%, using query: [%], optional attributes: [%]', sch, tbl, qry, tbl_attrs;
END IF;
END;
$$
language plpgsql;

Syntax error in PL/pgSQL FOR loop

I'm trying to use a FOR loop:
create or replace function update_revisions() returns trigger as
$$
begin
declare col_name declare col_name information_schema.columns%ROWTYPE;
for col_name in
select column_name from information_schema.columns
where table_name='t'
loop
insert into debug_table values (col_name);
end loop;
end;
$$
language plpgsql;
But it always says:
syntax error at or near 'for'
Could someone please give me a hint what's wrong with it?
Immediate problem
Invalid syntax. Untangled:
create or replace function update_revisions()
returns trigger as
$$
declare
col_name information_schema.columns%ROWTYPE;
begin
for col_name in
select column_name from information_schema.columns
where table_name='t'
loop
insert into debug_table values (col_name);
end loop;
end;
$$ language plpgsql;
More problems
Table names are not unique in a Postgres database. More in this recent answer:
Behaviour of NOT LIKE with NULL values
The whole approach is inefficient. Use a single INSERT statement instead:
INSERT INTO debug_table (target_column) -- with column definition list!
SELECT column_name
FROM information_schema.columns
WHERE table_name = 't'
AND table_schema = 'public'; -- your schema