snowflake check if table exist - snowflake-cloud-data-platform

Is there any in-built function or proc in Snowflake which can return boolean value. if table exist or not.
like
cal IF_table_exist('table_name') or select iftableexist('table_name');
If not then I am planning to write a store proc which will solve the purpose. Any direction will be very helpful.
Thanks in advance.

Minimal implementation for the function (you could add more error handling, etc.)
CREATE OR REPLACE FUNCTION TBL_EXIST(SCH VARCHAR, TBL VARCHAR)
RETURNS BOOLEAN
LANGUAGE SQL
AS
'select to_boolean(count(1)) from information_schema.tables where table_schema = sch and table_name = tbl';

No, there is not a built-in function in Snowflake to check if the table exists and return a boolean value. It's possible to check the table using SHOW TABLES command.
https://docs.snowflake.com/en/sql-reference/sql/show-tables.html
So you may parse the output of the command to return a boolean value.

The function EXISTS is can be used in Snowflake to check if a table exists
CREATE TABLE EXAMPLE_TABLE (
COL1 VARCHAR
);
EXECUTE IMMEDIATE
$$
BEGIN
IF (EXISTS(SELECT * FROM MY_DATABASE.INFORMATION_SCHEMA
WHERE TABLE_NAME = 'EXAMPLE_TABLE'
)
THEN RETURN 'EXISTS';
ELSE
RETURN 'NOT EXISTS'
END IF;
END
$$;

If you are checking whether or not a table exists for DDL, it's easiest to use the following:
CREATE OR REPLACE Table_Name (
col1
col2
.
.
coln
);

Related

Is there a Snowflake equivalent for T-SQL INSERT INTO [Tablename] EXEC [StoredProcedure]?

Using T-SQL in SQL Server I can insert the results of a stored procedure into a table using this syntax:
INSERT INTO <TableName>
EXEC <StoredProcedureName>
GO
Presently there is already a Stored Procedure that returns the data I need so I'd rather not re-invent the existing query to get that data into a table; but I'm not that knowledgeable with the capabilities of Snowflake in this respect.
Is there a similar Snowflake syntax I could use to achieve the same result?
The closest I could find was the Copy Into command but I haven't been able to successfully use it in this manner.
It is possible to use RESULT_SCAN to operate on stored procedure output:
CALL <stored_procedure>;
INSERT INTO <table_name>(....)
SELECT * FROM TABLE(RESULT_SCAN(LAST_QUERY_ID()));
Sample:
CREATE OR REPLACE TABLE tab(i INT);
CREATE OR REPLACE PROCEDURE dummy()
RETURNS TABLE(col INT)
LANGUAGE SQL
AS
$$
DECLARE
res RESULTSET default (select 1 AS c);
BEGIN
RETURN TABLE(res);
END;
$$;
CALL dummy();
INSERT INTO t(i)
SELECT * FROM TABLE(RESULT_SCAN(LAST_QUERY_ID()));
SELECT * FROM t;
-- i
-- 1

Is there any equivalent in Snowflake for OBJECT_ID() in SQL Server?

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

How should I test for the existence of a table and fail a script if the table does not exist?

This is what I came up with. Seems very hacky but it is what someone in the Snowflake forums suggested. Surely there's a better way.
use database baz;
-- divide by zero hack to check the existence of a table and bail if it isn't present
-- improvements to this welcome, snow sql doesn't have a clean way to bail like raiseerror
select 1/(select count(*) from information_schema.tables
where table_schema = 'foo'
and table_name = 'bar')
This script is intended to run after a setup script. This is here to make sure the required tables exist after the script has been run.
The main point is that as for today Snowflake SQL does not support control structures(IF/WHILE/FOR/SWITCH/TRY/CATCH). It will probably change in the future but as for now you could use Java Script stored procedures.
Overview of Stored Procedures
Snowflake stored procedures use JavaScript and, in most cases, SQL:
JavaScript provides the control structures (branching and looping).
SQL is executed by calling functions in a JavaScript API.
The pseudocode:
CREATE OR REPLACE PROCEDURE my_proc(...)
RETURNS STRING
LANGUAGE JAVASCRIPT
AS
$$
var tab_exists = `select count(*) from information_schema.tables
where table_schema ILIKE 'foo'
and table_name ILIKE 'bar'`;
var stmt = snowflake.createStatement( {sqlText: tab_exists} );
var resultSet = stmt.execute();
resultSet.next();
var result = resultSet.getColumnValue(1);
if (result > 0){
...
}
else {
return 'table not found';
};
$$;
-- invocation
use database baz;
call my_proc();
EDIT:
Using Snowflake Scripting is much easier to write such scripts:
DECLARE
my_exception EXCEPTION(-20001, 'table_not_found');
BEGIN
IF (EXISTS (SELECT *
FROM information_schema.tables
WHERE table_schema ILIKE 'PUBLIC'
AND table_name ILIKE 'NON_EXISTING_TAB'))
THEN
RETURN 'TABLE_EXISTS';
ELSE
--RETURN 'TABLE_NOT_FOUND';
RAISE my_exception;
END IF;
END;
If you are testing for table existence by expecting an exception if the table doesn't exist, you could just run a select on the table.
This will generate an exception if the table doesn't exist:
select count(*)
from schema.table
The proposal in the question to look on the information_schema is cool if you need a query that doesn't generate an exception, but once you divide by 0, it's clear that you were asking for one.

Snowflake conditional code: adding new column(idempotent script)

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;
$$;

SQL Server stored procedure that inserts if not exist and returns a specific property (column value)

I have a stored procedure that inserts a record if it doesn't exist, but I would like to adapt this a little. I would like the stored procedure to return a column's value.
So far I have this:
BEGIN
IF NOT EXISTS (SELECT *
FROM tableName
WHERE DATALENGTH(ColumnValueToReturn) = 0
AND property = #property ...)
BEGIN
INSERT INTO tableName (...)
VALUES (...)
END
END
I am confused and unsure on what the best option would be here, there are 3 scenarios:
There is an existing record + ColumnValueToReturn is not empty
There is an existing record + ColumnValueToReturn is NULL/Empty
There is no existing record
I would like that in case 1, the stored procedure returns the ColumnValueToReturn value, and in cases 2 & 3 it returns a custom value (0 or any string).
Can anyone suggest ways to do this?
Mind that this is used in a Microsoft Flow (https://flow.microsoft.com/en-us/) so there are some limitations sometimes.
Thank you all for any help or suggestions !
Kind regards
if exists (SELECT 1 FROM tableName
WHERE datalength(**ColumnValueToReturn**)=0
AND property = #property ...)
select ColumnValueToReturn FROM tableName WHERE datalength(**ColumnValueToReturn**)=0
AND property = #property ...
else
begin
INSERT INTO tableName (...)
VALUES (...)
select defaultvalue
end

Resources