I have a trigger, but I need to associate with all tables of the my postgres.
Is there a command like this below?
CREATE TRIGGER delete_data_alldb
BEFORE DELETE
ON ALL DATABASE
FOR EACH ROW
EXECUTE PROCEDURE delete_data();
Well there is no database-wide trigger creation but for all such bulk-admin-operations you could use PostgreSQL system tables to generate queries for you instead of writing them by hand.
In this case you could run:
SELECT
'CREATE TRIGGER '
|| tab_name
|| ' BEFORE DELETE ON ALL DATABASE FOR EACH ROW EXECUTE PROCEDURE delete_data();' AS trigger_creation_query
FROM (
SELECT
quote_ident(table_schema) || '.' || quote_ident(table_name) as tab_name
FROM
information_schema.tables
WHERE
table_schema NOT IN ('pg_catalog', 'information_schema')
AND table_schema NOT LIKE 'pg_toast%'
) tablist;
This will get you set of strings which are SQL commands like:
CREATE TRIGGER schema1.table1 BEFORE DELETE ON ALL DATABASE FOR EACH ROW EXECUTE PROCEDURE delete_data();
CREATE TRIGGER schema1.table2 BEFORE DELETE ON ALL DATABASE FOR EACH ROW EXECUTE PROCEDURE delete_data();
CREATE TRIGGER schema1.table3 BEFORE DELETE ON ALL DATABASE FOR EACH ROW EXECUTE PROCEDURE delete_data();
CREATE TRIGGER schema2.table1 BEFORE DELETE ON ALL DATABASE FOR EACH ROW EXECUTE PROCEDURE delete_data();
CREATE TRIGGER schema2."TABLE2" BEFORE DELETE ON ALL DATABASE FOR EACH ROW EXECUTE PROCEDURE delete_data();
...
etc
You just need to run them at once (either by psql or pgAdmin).
Now some explanation:
I select names of tables in my database using information_schema.tables system table. Because there are data of literally all tables, remember to exclude pg_catalog and information_schema schemas and toast tables from your select.
I use quote_ident(text) function which will put string inside double quote signs ("") if necessary (ie. names with spaces or capital letters require that).
When I have list of tables names I just concatenate them with some static strings to get my SQL commands.
I write that command using sub-query because I want you to get better idea of what's going on here. You may write a single query by putting quote_ident(table_schema) || '.' || quote_ident(table_name) in place of tab_name.
A conveniently encapsulated version of Gabriel's answer. This time I am using the trigger to update a column named update_dt datetime granted to be part of any table in the public schema of the current database.
--
-- function: tg_any_update_datetime_fn
-- when: before insert or update
--
create or replace function tg_any_update_datetime_fn ()
returns trigger
language plpgsql as $$
begin
new.update_dt = now();
return new;
end;
$$;
--
-- function: ddl_create_before_update_trigger_on_all_tables
-- returns: Create a before update trigger on all tables.
--
create or replace procedure ddl_create_before_update_trigger_on_all_tables ()
language plpgsql as $$
declare
_sql varchar;
begin
for _sql in select concat (
'create trigger tg_',
quote_ident(table_name),
'_before_update before update on ',
quote_ident(table_name),
' for each row execute procedure tg_any_update_datetime_fn ();'
)
from
information_schema.tables
where
table_schema not in ('pg_catalog', 'information_schema') and
table_schema not like 'pg_toast%'
loop
execute _sql;
end loop;
end;
$$;
-- create before update trigger on all tables
call ddl_create_before_update_trigger_on_all_tables();
On my DDL scripts I use a large number of such ddl_ functions that have only meaning at DDL time. To remove them from the database use
--
-- function: ddl_drop_ddl_functions
-- returns: Drop all DDL functions.
-- since: 1.1.20
--
create or replace procedure ddl_drop_ddl_functions ()
language plpgsql as $$
declare
r record;
_sql varchar;
begin
for r in
select oid, prokind, proname
from pg_proc
where pronamespace = 'public'::regnamespace
and proname ilike 'ddl_%'
loop
case r.prokind
when 'a' then _sql = 'aggregate';
when 'p' then _sql = 'procedure';
else _sql = 'function';
end case;
_sql = format('drop %s %s', _sql, r.oid::regprocedure);
execute _sql;
end loop;
end
$$;
Related
The OBJECT_ID function Returns the database object identification number of a schema-scoped object in SQL SERVER.
Could anyone suggest an equivalent function in Snowflake that can be used inside a stored procedure?
I need to migrate the below code to Snowflake:
CREATE PROCEDURE test_procedure
(#Var1 INT )
AS
BEGIN
IF #Var1 = 1
BEGIN
IF OBJECT_ID('db1.Table1') IS NOT NULL
DROP TABLE Table1;
END;
END;
The pattern:
IF OBJECT_ID('db1.Table1') IS NOT NULL
DROP TABLE Table1;
is the old way to check if table exists before trying to drop it.
Currently both SQL Server and Snowflake supports IF EXISTS clause:
DROP TABLE IF EXISTS <table_name>;
db<>fiddle demo
The closest I can think of will be to use a function like:
CREATE OR REPLACE FUNCTION OBJECT_ID(NAME VARCHAR) RETURNS STRING
LANGUAGE SQL
AS
$$
SELECT OBJECT_SCHEMA || '.' || OBJECT_NAME FROM INFORMATION_SCHEMA.OBJECT_PRIVILEGES WHERE
(OBJECT_SCHEMA || '.' || OBJECT_NAME) = NAME
$$;
The snowflake object_privileges view is the closest information_schema view listing all db elements.
However as noted on previous answers, the IF EXISTS on create and drop statements, makes the usage of this function unnecessary
Let's assume we have a table that contains data as below:
CREATE TABLE tab(i INT PRIMARY KEY);
INSERT INTO tab(i) VALUES(1),(2),(3);
SELECT * FROM tab;
Now my goal is to create SQL script that will add a new column to existing table:
ALTER TABLE IF EXISTS tab ADD COLUMN col VARCHAR(10);
Everything works as intended. Except the fact I would like to be able to run script multiple times but the effect should take place only once(idempotence).
If I try to run it again I will get:
SQL compilation error: column COL already exists
Normally I would use one of these approaches:
a) Using control structure IF to check metadata tables before executing query:
-- (T-SQL)
IF NOT EXISTS(SELECT * FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME='TAB' AND COLUMN_NAME = 'COL')
BEGIN
ALTER TABLE tab ADD col VARCHAR(10);
END;
db<>fiddle demo
I have not found IF statement in Snowflake's documentation.
b) SQL dialect that supports IF NOT EXISTS syntax:
-- PostgreSQL
ALTER TABLE IF EXISTS tab ADD COLUMN IF NOT EXISTS col VARCHAR(10);
db<>fiddle demo
Most of Snowflake SQL commands contain IF EXISTS/OR REPLACE clauses which means it was written in a way to allow running scripts multiple times.
I was considering using code like:
CREATE OR REPLACE TABLE tab
AS
SELECT i, CAST(NULL AS VARCHAR(10)) AS col
FROM tab;
This approach on other hand causes unnecessary table creation and does not preserve metadata(like primary key).
Is there a way to achieve similar effect on Snowflake? Preferably by using conditional code(add column is an example).
You can use something like this. It will report the failure to add the column if it already exists, but it will handle the error so it won't interfere with the execution of a sql script:
create or replace procedure SafeAddColumn(tableName string, columnName string, columnType string)
returns string
language JavaScript
as
$$
var sql_command = "ALTER TABLE IF EXISTS " + TABLENAME + " ADD COLUMN " + COLUMNNAME + " " + COLUMNTYPE + ";";
var strOut;
try {
var stmt = snowflake.createStatement( {sqlText: sql_command} );
var resultSet = stmt.execute();
while (resultSet.next()) {
strOut = resultSet.getColumnValue(1);
}
}
catch (err) {
strOut = "Failed: " + err; // Return a success/error indicator.
}
return strOut;
$$;
CREATE OR REPLACE TABLE tab(i INT PRIMARY KEY);
INSERT INTO tab(i) VALUES(1),(2),(3);
SELECT * FROM tab;
call SafeAddColumn('tab', 'col', 'varchar(10)');
select * from tab;
call SafeAddColumn('tab', 'col', 'varchar(10)');
It is possible to write conditional code using Snowflake Scripting.
Working with Branching Constructs
Snowflake Scripting supports the following branching constructs:
IF-THEN-ELSEIF-ELSE
CASE
Setup:
CREATE OR REPLACE TABLE PUBLIC.tab(i INT PRIMARY KEY);
INSERT INTO tab(i) VALUES(1),(2);
SELECT * FROM tab;
-- i
-- 1
-- 2
Code that can be rerun multiple times(subsequent runs will take no effect):
-- Snowsight
BEGIN
IF (NOT EXISTS(SELECT *
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'TAB'
AND TABLE_SCHEMA = 'PUBLIC'
AND COLUMN_NAME = 'COL')) THEN
ALTER TABLE IF EXISTS tab ADD COLUMN col VARCHAR(10);
END IF;
END;
EXECUTE IMMEDIATE is required is run using "classic web interface":
EXECUTE IMMEDIATE $$
BEGIN
IF (NOT EXISTS(SELECT *
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'TAB'
AND TABLE_SCHEMA = 'PUBLIC'
AND COLUMN_NAME = 'COL')) THEN
ALTER TABLE IF EXISTS tab ADD COLUMN col VARCHAR(10);
END IF;
END;
$$
After:
SELECT * FROM tab;
-- i col
-- 1 NULL
-- 2 NULL
Although Snowflake has implemented a pretty rich mix of DDL and DML for their SQL implementation, when it comes to procedural code they seem to be relying on JavaScript, at least at this point. But you should be able to accomplish your idempotent ALTER script through a JavaScript stored procedure.
I'm afraid I lack the JavaScript skills to provide you with a working sample myself at this point. The organization I'm with recently adopted Snowflake, though, so I'll share some of my research.
Here's a recent blog post on just this question:
Snowflake Control Structures – IF, DO, WHILE, FOR
Snowflake's overview documentation regarding stored procedures:
Stored Procedures
On the page above, what is currently the third link down contains extensive sample code.
Working With Stored Procedures
Building on Lukasz answer, to include database in condition you can use:
execute immediate $$
BEGIN
IF (
NOT EXISTS(
SELECT *
FROM "INFORMATION_SCHEMA"."COLUMNS"
WHERE
"TABLE_CATALOG" = 'DB_NAME'
AND "TABLE_SCHEMA" = 'SCHEMA_NAME'
AND "TABLE_NAME" = 'TABLE_NAME'
AND "COLUMN_NAME" = 'col_name'
)
) THEN
ALTER TABLE IF EXISTS "DB_NAME"."SCHEMA_NAME"."TABLE_NAME"
ADD COLUMN "col_name" VARCHAR NULL;
END IF;
END;
$$;
I am trying to assign all the table names of a schema to a variable. Later, I wish to create an update trigger for each table.
I am extracting the tables using :
select table_name from information_schema.tables;
when I do the following, I get null values:
declare #tablename nvarchar(max)
select table_name = #tablename from information_schema.tables;
I am using mssql in SSMS2016, can someone please tell me how I can assign the table names to a variable?
It is a really good question, if it is a good idea to create a trigger for each and any table, but - whenever I need to create statements - I suggest this:
select CONCAT('CREATE TRIGGER ',QUOTENAME(CONCAT(table_name,'_trigger')),' ON ',QUOTENAME(table_name),' AFTER UPDATE AS BEGIN PRINT ''yeah!''; END',CHAR(13),CHAR(10),'GO' )
from information_schema.tables
where TABLE_TYPE='BASE TABLE';
Send the output to text and you get:
CREATE TRIGGER [SomeTable_trigger] ON [SomeTable] AFTER UPDATE AS BEGIN PRINT 'yeah!'; END
GO
CREATE TRIGGER [Another_trigger] ON [Another] AFTER UPDATE AS BEGIN PRINT 'yeah!'; END
GO
... more of this...
GO
...
The next step was to copy all these statements to a new query window and adapt them to your needs.
I am running the following query:
SELECT * INTO dbo.2015_10_2_cs FROM dbo.2015_10_2
IF NOT EXISTS
(SELECT type FROM sys.indexes WHERE object_id = object_id('dbo.2015_10_2_cs')
AND NAME ='cci' AND type = 5)
BEGIN
CREATE CLUSTERED COLUMNSTORE INDEX cci
ON dbo.2015_10_2_cs
DROP TABLE dbo.2015_10_2
EXEC sp_rename "dbo.2015_10_2_cs" , "dbo.2015_10_2"
END
and I want to make sure that the part where I am renaming the table dbo.2015_10_2_cs to dbo.2015_10_2 is done successfully (without losing any data).
The step inside the loop should be surrounded with SQL transaction to keep the process safe and reliable (in case if any step will fail).
Could anyone help with this? Thanks in advance.
EXEC sp_rename "dbo.2015_10_2_cs" , "dbo.2015_10_2"
This will not do what you expect. The new table will be named [dbo].[dbo.2015_10_2] if you specify the schema name in the new table name. Renamed tables are implicitly in the existing table's schema since one must use ALTER SCHEMA instead of sp_rename to move an object between schemas.
There are a number of other problems with your script. Because the table name starts with a number, it doesn't conform to regular identifier naming rules and must be enclosed in square brackets or double quotes. The literal parameters passed to sp_rename should be single quotes. You can also check to stored procedure return code to ascertain success or failure. The example below performs these tasks in a transaction with structured error handling.
DECLARE #rc int;
BEGIN TRY
BEGIN TRAN;
IF NOT EXISTS
(SELECT type FROM sys.indexes WHERE object_id = object_id(N'dbo.2015_10_2_cs')
AND NAME ='cci' AND type = 5)
BEGIN
CREATE CLUSTERED COLUMNSTORE INDEX cci
ON dbo.[2015_10_2_cs];
DROP TABLE dbo.[2015_10_2];
EXEC #rc = sp_rename 'dbo.[2015_10_2_cs]' , '2015_10_2';
IF #rc <> 0
BEGIN
RAISERROR('sp_rename returned return code %d',16,1);
END;
END;
COMMIT;
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0 ROLLBACK;
THROW;
END CATCH;
You can use an EXISTS checking for the tablename and schema.
IF NOT EXISTS (SELECT 'table does not exist' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = N'2015_10_2'AND TABLE_SCHEMA = 'dbo')
BEGIN
RAISERROR('The table doesn''t exist!!!!', 16, 1)
END
sp_rename won't make you lose table contents, it will just change the table reference name and update all it's contraints and indexes references. It will also raise an error if the table to rename does not exist. Maybe what you want is to wrap your process in a transaction and rollback if something fails.
EDIT:
For basic transaction handling you can use the following. Please read the documentation for using transaction, it might take a while to know how it works correctly.
IF OBJECT_ID('tempdb..#Test') IS NOT NULL
DROP TABLE #Test
CREATE TABLE #Test (Number INT)
SELECT AmountRecords = COUNT(1) FROM #Test -- AmountRecords = 0
BEGIN TRY
BEGIN TRANSACTION
-- Do your statements here
INSERT INTO #Test (Number)
VALUES (1)
DECLARE #errorVariable INT = CONVERT(INT, 'NotAnInteger!!') -- Example of error: can't convert
COMMIT
END TRY
BEGIN CATCH -- If something goes wrong
IF ##TRANCOUNT > 0 -- ... and transaction is still open
ROLLBACK -- Revert statements from the BEGIN TRANSACTION onwards
END CATCH
SELECT AmountRecords = COUNT(1) FROM #Test -- AmountRecords = 0 (the transaction was rolled back and the INSERT reverted)
Basically you use BEGIN TRANSACTION to initiate a restore point to go back to if something fails. Then use a COMMIT once you know everything is OK (from that point onwards, other users will see the changes and modifications will be persisted). If something fails (you need TRY/CATCH block to handle errors) you can issue a ROLLBACK to revert your changes.
How do we write below pseudo code in db2,
If (Proc exists)
Drop Proc
Create Proc
Else
Create Proc
One solution I found, after googling is to ignore the return codes. Do we have a more elegant way to do this?
Thanks
Update: With the help of the answer below we wrote a proc as below to drop the procedures
CREATE PROCEDURE SVCASNDB.DROPSP(IN P_SPECIFICNAME VARCHAR(128))
SPECIFIC DROPSP
P1: BEGIN
-- Drop the SP if it already exists
if exists (SELECT SPECIFICNAME FROM SYSIBM.SYSROUTINES WHERE SPECIFICNAME = trim(upper(p_SpecificName))) then
begin
DECLARE v_StmtString VARCHAR (1024);
SET v_StmtString = 'DROP SPECIFIC PROCEDURE SCHEMA.' || p_SpecificName;
PREPARE stmt1 FROM v_StmtString ;
EXECUTE stmt1;
end;
end if;
END P1
this query:
SELECT DISTINCT ROUTINENAME, RESULT_SETS, REMARKS
FROM SYSIBM.SYSROUTINES
WHERE ROUTINESCHEMA='<schema>' AND FUNCTION_TYPE NOT IN ('S', 'T')
(where you specify your schema name at the placeholder)
gives you all procs in a schema. So the Proc exists part is simply an EXISTS query on that view with the proper proc name.