Essential Tips for Mastering Snowflake Scripting
Key Concepts of Snowflake Scripting
Table of contents
- 1. Understanding Stored Procedures in Snowflake
- Why Use Stored Procedures?
- Stored Procedures in Snowflake
- Creating a Stored Procedure in Snowflake
- Executing a Stored Procedure
- Stored Procedure Structure
- Dropping a Stored Procedure
- Advanced Concepts in Snowflake Stored Procedures
- Best Practices for Using Stored Procedures in Snowflake
- 1. Use Descriptive Names
- 2. Optimize Performance Using Indexing and Clustering Keys
- 3. Handle Exceptions Properly
- 4. Use Parameterized Queries to Avoid SQL Injection
- 5. Avoid Excessive Loops – Use Bulk Operations
- Snowflake SQL Procedures and Best Practices for Data Management
- 1. Procedure for Automated Data Loads
- 2. SCD Type-1 Implementation
- 3. SCD Type-2 Implementation
- 4. Procedure for One-to-One View Creation
- 5. Procedure for Dropping All Tables from a Schema
- 6. Procedure for Dropping All Views from a Schema
- 7. Procedure for Migrating Table DDL from One Database to Another
- 8. Procedure for Automating Full Load from Staging to Target Tables
- 9. Procedure for Automating Incremental Load from Staging to Target Tables
- 10. Procedure for Sample Data Setup in Lower Environments
- 11.Automating One-to-One View Creation in Snowflake Using Stored Procedures
- What is a One-to-One View?
- Why Automate View Creation?
- Stored Procedure for Automating One-to-One View Creation
- How This Works
- Running the Procedure
- Example Scenario
- Extending This Procedure
- 12.Stored Procedure to Drop Tables and Views
- How It Works
- Example Usage
- Enhancements
- 13.Stored Procedure to Migrate Tables and Views
- How It Works
- Example Usage
- Enhancements
- 14.Automating Full and Incremental Load in Snowflake Using Stored Procedures
- Overview
- Step 1: Create Database and Schema
- Step 2: Create Error Logging Table
- Step 3: Create Staging and Target Tables
- Step 4: Automate Full Load
- Step 5: Automate Incremental Load (Upsert – Merge)
- Step 6: Automate the Load with Snowflake Tasks
- Step 7: Error Handling and Debugging
- Final Summary
- 15. Code Deployment Using GitHub and Schema Change
- Conclusion
- 2. Understanding Variables, Session Variables, and Execute Immediate in Snowflake
- Introduction
- Variables in Snowflake
- Using Variables in SQL Statements
- Execute Immediate in Snowflake
- 5. Using Variables with Execute Immediate
- 6. Handling Errors with Execute Immediate
- 7. Real-World Use Cases
- Best Practices for Using Variables and Execute Immediate
- Conclusion
- 3. Understanding Table Literals and Identifiers in Snowflake
- 4.Branching Constructs in Snowflake Scripting
- Summary Table
- Final Thoughts
- 5.Cursors and Result sets in Snowflake
- Cursors in Snowflake are essential tools for processing query results row-by-row within stored procedures or scripts. They enable iterative operations on data, which is particularly useful for complex transformations or conditional logic that can't be efficiently handled with set-based operations. Result sets represent the data retrieved by a query, and cursors act as pointers to traverse these result sets.
- Understanding Cursors
- How to Use Cursors in Snowflake
- Dynamic Cursors with EXECUTE IMMEDIATE
- Best Practices
- Conclusion
- 6.Exception Handling in Snowflake Scripting
- 7.Transactions in Snowflake
- 8.User-Defined Functions (UDFs) in Snowflake
1. Understanding Stored Procedures in Snowflake
A stored procedure is a collection of SQL statements that execute in a defined sequence. These statements can include INSERT, DELETE, UPDATE, SELECT, and other SQL operations. Stored procedures help automate and simplify repetitive database tasks.
Why Use Stored Procedures?
Stored procedures are useful when:
1. Executing Multiple SQL Statements Together
📌 Example: Processing salary calculations in payroll
A payroll system needs to update employee salaries, apply bonuses, and deduct taxes in a single execution.
BEGIN; -- Start transaction UPDATE employees SET salary = salary * 1.05 -- Apply a 5% raise to all employees WHERE department = 'Sales'; UPDATE employees SET bonus = 1000 -- Give a $1000 bonus to managers WHERE role = 'Manager'; UPDATE employees SET salary = salary - (salary * 0.10) -- Deduct 10% tax WHERE salary > 50000; COMMIT; -- Save all changes
Why? Ensures all updates are applied together, preventing partial updates if an error occurs.
2. Dynamically Generating SQL Statements
📌 Example: Creating queries based on user input
A reporting system generates an SQL query based on a user-selected department.
DECLARE @department NVARCHAR(50) = 'Sales'; DECLARE @sql NVARCHAR(MAX); SET @sql = 'SELECT * FROM employees WHERE department = ''' + @department + ''''; EXEC sp_executesql @sql;
Why? Helps build flexible queries dynamically based on user input.
3. Running SQL Statements Conditionally
📌 Example: Checking order status before updating shipping details
Before updating an order’s shipping details, check if the order is paid.
IF EXISTS (SELECT 1 FROM orders WHERE order_id = 101 AND status = 'Paid') BEGIN UPDATE orders SET shipping_status = 'Shipped', shipped_date = GETDATE() WHERE order_id = 101; END ELSE BEGIN PRINT 'Order is not paid yet. Cannot update shipping details.' END;
Why? Prevents changes to orders that are not yet paid.
4. Running SQL Statements in Loops
📌 Example: Processing multiple records in batches
Processing customer records in batches of 100 to avoid overloading the system.
DECLARE @batchSize INT = 100; DECLARE @offset INT = 0; WHILE @offset < (SELECT COUNT(*) FROM customers) BEGIN UPDATE customers SET processed = 1 WHERE id IN (SELECT id FROM customers ORDER BY id OFFSET @offset ROWS FETCH NEXT @batchSize ROWS ONLY); SET @offset = @offset + @batchSize; END;
Why? Helps process large datasets efficiently without locking the entire table.
Stored Procedures in Snowflake
Unlike traditional databases, Snowflake allows stored procedures to be written in SQL, JavaScript, Scala, Java, and Python.
Snowflake Scripting
When stored procedures are written entirely in SQL, they are known as Snowflake Scripting.
Creating a Stored Procedure in Snowflake
Stored procedures are created using the CREATE PROCEDURE statement.
Basic Syntax:
CREATE OR REPLACE PROCEDURE procedure_name(parameters)
RETURNS return_type
LANGUAGE SQL
EXECUTE AS CALLER
AS
$$
DECLARE variable_name DATA_TYPE;
BEGIN
-- SQL statements go here
END;
$$;
Example 1: Basic Stored Procedure
Let’s create a stored procedure to return the total number of rows in a given table.
CREATE OR REPLACE PROCEDURE get_row_count(tab_name STRING)
RETURNS INTEGER
LANGUAGE SQL
EXECUTE AS CALLER
AS
$$
DECLARE row_count INTEGER DEFAULT 0;
DECLARE c1 CURSOR FOR SELECT COUNT(*) AS count FROM IDENTIFIER(:tab_name);
BEGIN
FOR row_variable IN c1 DO
row_count := row_variable.count;
END FOR;
RETURN row_count;
END;
$$;
Explanation:
We declare a cursor (c1) to fetch the count of rows from the given table.
We loop through the cursor and assign the count to
row_count
.The final value is returned as output.
Executing a Stored Procedure
Stored procedures are executed using the CALL statement.
Syntax:
CALL procedure_name(parameters);
Example 2: Executing the Procedure
CALL get_row_count('employees');
Output: The procedure returns the total number of rows in the employees
table.
Stored Procedure Structure
A stored procedure in Snowflake consists of four main sections:
1. Create Section
Defines the procedure’s name, parameters, return type, and language.
CREATE OR REPLACE PROCEDURE procedure_name(param1 DATATYPE, param2 DATATYPE)
RETURNS DATATYPE
LANGUAGE SQL
EXECUTE AS CALLER
2. Declaration Section
Declares variables, cursors, result sets, and exceptions.
DECLARE emp_id INTEGER;
DECLARE emp_name STRING;
DECLARE cur CURSOR FOR SELECT id, name FROM employees;
3. Body Section
Contains the logic, SQL statements, and loops.
BEGIN
FOR record IN cur DO
INSERT INTO processed_employees VALUES (record.id, record.name);
END FOR;
END;
4. Exception Handling
Handles errors and exceptions.
EXCEPTION
WHEN STATEMENT_ERROR THEN
RETURN SQLERRM;
WHEN OTHER ERROR THEN
RETURN SQLERRM;
Dropping a Stored Procedure
To delete a stored procedure, use the DROP PROCEDURE command.
Syntax:
DROP PROCEDURE procedure_name(parameters);
Example 3: Dropping a Procedure
DROP PROCEDURE get_row_count(STRING);
Note: The parameter data type must match the procedure definition.
Advanced Concepts in Snowflake Stored Procedures
1. Using Loops in Procedures
Loops allow repetitive execution of statements.
BEGIN
FOR rec IN (SELECT id FROM employees) DO
UPDATE employees SET status = 'Active' WHERE id = rec.id;
END FOR;
END;
2. Conditional Execution (IF Statements)
Stored procedures can include conditional logic.
BEGIN
IF :salary > 50000 THEN
RETURN 'High Salary';
ELSE
RETURN 'Low Salary';
END IF;
END;
3. Using Snowflake Procedures for Automation
Stored procedures are useful for data cleaning, ETL pipelines, and periodic updates.
Example 4: Automating Daily Sales Report
CREATE OR REPLACE PROCEDURE daily_sales_report()
RETURNS STRING
LANGUAGE SQL
EXECUTE AS CALLER
AS
$$
BEGIN
INSERT INTO sales_summary
SELECT region, SUM(sales) FROM sales_data GROUP BY region;
RETURN 'Daily report generated';
END;
$$;
Best Practices for Using Stored Procedures in Snowflake
When working with SQL, following best practices ensures clarity, performance, security, and maintainability. Below are detailed explanations with examples for each best practice.
1. Use Descriptive Names
Why? Clear and meaningful names make it easier to understand SQL procedures, functions, tables, and columns.
📌 Bad Example (Unclear Naming)
CREATE PROCEDURE proc1 (@sal DECIMAL) AS BEGIN UPDATE emp SET sal = sal * 1.1 WHERE sal > @sal; END;
📌 Good Example (Descriptive Naming)
CREATE PROCEDURE apply_salary_increase (@min_salary DECIMAL) AS BEGIN UPDATE employees SET salary = salary * 1.1 WHERE salary > @min_salary; END;
✅ Clearly explains what the procedure does.
2. Optimize Performance Using Indexing and Clustering Keys
Why? Proper indexing speeds up searches, while clustering keys optimize data storage.
📌 Bad Example (No Index, Slow Search Performance)
SELECT * FROM orders WHERE customer_id = 12345;
📌 Good Example (Indexed Column for Faster Lookups)
CREATE INDEX idx_customer_id ON orders (customer_id); SELECT * FROM orders WHERE customer_id = 12345;
✅ Indexing reduces query execution time significantly.
📌 Using a Clustering Key for Faster Table Access
CREATE TABLE sales ( sale_id INT PRIMARY KEY, customer_id INT, sale_date DATE, amount DECIMAL(10,2), CLUSTER BY (sale_date) -- Optimizes storage for date-based queries );
✅ Improves performance for queries filtering by
sale_date
.
3. Handle Exceptions Properly
Why? Prevents SQL scripts from stopping execution due to unexpected errors.
📌 Bad Example (No Error Handling, Script Stops on Error)
INSERT INTO payments (order_id, amount) VALUES (NULL, 100);
📌 Good Example (Using TRY...CATCH for Error Handling)
BEGIN TRY INSERT INTO payments (order_id, amount) VALUES (NULL, 100); END TRY BEGIN CATCH PRINT 'Error: A NULL order_id is not allowed'; END CATCH;
✅ Prevents script failure and provides useful error messages.
4. Use Parameterized Queries to Avoid SQL Injection
Why? Protects against malicious user inputs that can alter queries.
📌 Bad Example (Vulnerable to SQL Injection)
DECLARE @query NVARCHAR(MAX); SET @query = 'SELECT * FROM users WHERE username = ''' + @user_input + ''''; EXEC(@query); -- Dangerous!
📌 Good Example (Using Parameterized Queries for Security)
sqlCopyEditDECLARE @user_input NVARCHAR(100); SET @user_input = 'john_doe'; EXEC sp_executesql N'SELECT * FROM users WHERE username = @username', N'@username NVARCHAR(100)', @user_input;
✅ Prevents SQL injection attacks by safely handling user input.
5. Avoid Excessive Loops – Use Bulk Operations
Why? Loops can slow down performance, while bulk operations process data efficiently.
📌 Bad Example (Looping Through Records One by One, Slow Execution)
DECLARE @id INT; DECLARE cur CURSOR FOR SELECT customer_id FROM customers; OPEN cur; FETCH NEXT FROM cur INTO @id; WHILE @@FETCH_STATUS = 0 BEGIN UPDATE customers SET status = 'Active' WHERE customer_id = @id; FETCH NEXT FROM cur INTO @id; END; CLOSE cur; DEALLOCATE cur;
📌 Good Example (Bulk Update Instead of Looping, Faster Execution)
UPDATE customers SET status = 'Active' WHERE last_purchase_date > '2023-01-01';
✅ Significantly faster than processing records one by one.
Snowflake SQL Procedures and Best Practices for Data Management
Snowflake provides stored procedures to automate complex tasks, making data management efficient, scalable, and repeatable. Below, we will cover essential procedures for automated data loads, Slowly Changing Dimensions (SCD), schema migrations, and deployment in a Snowflake environment with real-world examples.
1. Procedure for Automated Data Loads
Automating data ingestion helps keep staging and target tables up to date. The procedure below automates a full data load from an external stage to a Snowflake table.
📌 Example: Automating file ingestion from AWS S3 into Snowflake
sqlCopyEditCREATE OR REPLACE PROCEDURE load_data()
RETURNS STRING
LANGUAGE SQL
AS
$$
BEGIN
COPY INTO target_table
FROM @my_s3_stage
FILE_FORMAT = (TYPE = 'CSV' FIELD_OPTIONALLY_ENCLOSED_BY='"');
RETURN 'Data Load Successful';
END;
$$;
✅ This procedure loads data from S3 into target_table
and can be scheduled using Snowflake Tasks.
2. SCD Type-1 Implementation
Slowly Changing Dimension Type-1 (SCD-1) replaces old data with new data without tracking historical changes.
📌 Example: Overwriting existing records
sqlCopyEditCREATE OR REPLACE PROCEDURE update_scd1()
RETURNS STRING
LANGUAGE SQL
AS
$$
BEGIN
MERGE INTO customer_dim AS target
USING staging_customer AS source
ON target.customer_id = source.customer_id
WHEN MATCHED THEN
UPDATE SET target.customer_name = source.customer_name,
target.email = source.email;
RETURN 'SCD Type-1 Updated Successfully';
END;
$$;
✅ Existing customer records are updated with new values without maintaining historical data.
3. SCD Type-2 Implementation
SCD Type-2 maintains historical changes by keeping multiple versions of a record with effective date tracking.
📌 Example: Implementing SCD Type-2 in Snowflake
sqlCopyEditCREATE OR REPLACE PROCEDURE update_scd2()
RETURNS STRING
LANGUAGE SQL
AS
$$
BEGIN
MERGE INTO customer_dim AS target
USING staging_customer AS source
ON target.customer_id = source.customer_id AND target.current_flag = TRUE
WHEN MATCHED AND target.customer_name <> source.customer_name THEN
UPDATE SET target.current_flag = FALSE, target.end_date = CURRENT_DATE
WHEN NOT MATCHED THEN
INSERT (customer_id, customer_name, email, start_date, end_date, current_flag)
VALUES (source.customer_id, source.customer_name, source.email, CURRENT_DATE, NULL, TRUE);
RETURN 'SCD Type-2 Updated Successfully';
END;
$$;
✅ This procedure tracks changes by adding new rows and deactivating old ones.
4. Procedure for One-to-One View Creation
Automatically creating views for each table in a schema ensures consistency.
📌 Example: Generating views dynamically for each table in a schema
sqlCopyEditCREATE OR REPLACE PROCEDURE create_views(schema_name STRING)
RETURNS STRING
LANGUAGE SQL
AS
$$
DECLARE table_name STRING;
BEGIN
FOR table_name IN (SELECT table_name FROM information_schema.tables WHERE table_schema = schema_name) DO
EXECUTE IMMEDIATE 'CREATE OR REPLACE VIEW ' || table_name || '_view AS SELECT * FROM ' || table_name;
END FOR;
RETURN 'Views Created Successfully';
END;
$$;
✅ This procedure creates a view for every table in a specified schema.
5. Procedure for Dropping All Tables from a Schema
Removing tables from a schema in a controlled manner prevents orphaned objects.
📌 Example: Dropping all tables in a schema
sqlCopyEditCREATE OR REPLACE PROCEDURE drop_all_tables(schema_name STRING)
RETURNS STRING
LANGUAGE SQL
AS
$$
DECLARE table_name STRING;
BEGIN
FOR table_name IN (SELECT table_name FROM information_schema.tables WHERE table_schema = schema_name) DO
EXECUTE IMMEDIATE 'DROP TABLE ' || schema_name || '.' || table_name;
END FOR;
RETURN 'All Tables Dropped Successfully';
END;
$$;
✅ This procedure removes all tables in a given schema dynamically.
6. Procedure for Dropping All Views from a Schema
Cleaning up views ensures better schema management.
📌 Example: Dropping all views in a schema
sqlCopyEditCREATE OR REPLACE PROCEDURE drop_all_views(schema_name STRING)
RETURNS STRING
LANGUAGE SQL
AS
$$
DECLARE view_name STRING;
BEGIN
FOR view_name IN (SELECT table_name FROM information_schema.views WHERE table_schema = schema_name) DO
EXECUTE IMMEDIATE 'DROP VIEW ' || schema_name || '.' || view_name;
END FOR;
RETURN 'All Views Dropped Successfully';
END;
$$;
✅ This helps remove unnecessary views from a schema.
7. Procedure for Migrating Table DDL from One Database to Another
This is useful when transferring schema structures across environments.
📌 Example: Extracting and executing DDL in a new database
sqlCopyEditCREATE OR REPLACE PROCEDURE migrate_table(source_db STRING, target_db STRING, table_name STRING)
RETURNS STRING
LANGUAGE SQL
AS
$$
DECLARE ddl_statement STRING;
BEGIN
SELECT get_ddl('TABLE', source_db || '.' || table_name) INTO ddl_statement;
EXECUTE IMMEDIATE 'USE DATABASE ' || target_db;
EXECUTE IMMEDIATE ddl_statement;
RETURN 'Table DDL Migrated Successfully';
END;
$$;
✅ This migrates table structures dynamically.
8. Procedure for Automating Full Load from Staging to Target Tables
📌 Example: Full data load automation
sqlCopyEditCREATE OR REPLACE PROCEDURE full_load()
RETURNS STRING
LANGUAGE SQL
AS
$$
BEGIN
TRUNCATE TABLE target_table;
INSERT INTO target_table SELECT * FROM staging_table;
RETURN 'Full Load Completed';
END;
$$;
✅ Completely refreshes the target table with staging data.
9. Procedure for Automating Incremental Load from Staging to Target Tables
📌 Example: Processing only new records
sqlCopyEditCREATE OR REPLACE PROCEDURE incremental_load()
RETURNS STRING
LANGUAGE SQL
AS
$$
BEGIN
MERGE INTO target_table AS target
USING staging_table AS source
ON target.id = source.id
WHEN MATCHED THEN UPDATE SET target.data = source.data
WHEN NOT MATCHED THEN INSERT (id, data) VALUES (source.id, source.data);
RETURN 'Incremental Load Completed';
END;
$$;
✅ Processes only new and updated records, reducing overhead.
10. Procedure for Sample Data Setup in Lower Environments
📌 Example: Copying limited sample data
sqlCopyEditCREATE OR REPLACE PROCEDURE setup_sample_data()
RETURNS STRING
LANGUAGE SQL
AS
$$
BEGIN
INSERT INTO lower_env.customers SELECT * FROM prod.customers SAMPLE(10);
RETURN 'Sample Data Inserted';
END;
$$;
✅ Moves 10% of production data to a lower environment for testing.
11.Automating One-to-One View Creation in Snowflake Using Stored Procedures
What is a One-to-One View?
A one-to-one (1:1) view in Snowflake is a direct mapping of a table, meaning the view selects all columns from the table without any modifications. These views are useful for:
✔ Abstracting the underlying table structure
✔ Providing controlled access (e.g., limiting access to certain columns)
✔ Enhancing security by hiding sensitive data
Why Automate View Creation?
In large databases with hundreds of tables, manually creating views for each table is inefficient. Instead, we can use a stored procedure to automatically generate views for all tables within a schema.
Stored Procedure for Automating One-to-One View Creation
This stored procedure:
Fetches all tables from a specified schema
Dynamically generates a CREATE VIEW statement for each table
Executes the SQL to create the views
Example: Automatic 1:1 View Creation for All Tables in a Schema
sqlCopyEditCREATE OR REPLACE PROCEDURE create_1_1_views(schema_name STRING, view_schema STRING)
RETURNS STRING
LANGUAGE SQL
AS
$$
DECLARE table_name STRING;
DECLARE view_name STRING;
DECLARE create_view_sql STRING;
BEGIN
-- Loop through each table in the given schema
FOR table_name IN
(SELECT table_name FROM information_schema.tables WHERE table_schema = schema_name)
DO
-- Define the view name (Appending _VIEW to table name)
LET view_name = view_schema || '.' || table_name || '_VIEW';
-- Construct the dynamic SQL to create the view
LET create_view_sql = 'CREATE OR REPLACE VIEW ' || view_name || ' AS SELECT * FROM ' || schema_name || '.' || table_name;
-- Execute the dynamically generated SQL
EXECUTE IMMEDIATE create_view_sql;
END FOR;
RETURN 'Views Created Successfully';
END;
$$;
How This Works
The procedure loops through all tables in the specified schema.
It constructs a dynamic SQL statement for creating views.
It executes the SQL using
EXECUTE IMMEDIATE
.The views are created in the view schema with the naming convention
<table_name>_VIEW
.
Running the Procedure
To create views for all tables in the schema source_schema and store them in view_schema, run:
sqlCopyEditCALL create_1_1_views('source_schema', 'view_schema');
This will generate one-to-one views for every table in source_schema
and store them in view_schema
.
Example Scenario
Tables in source_schema
:
Table Name | Columns |
employees | id, name, department |
orders | order_id, customer_id, amount |
Generated Views in view_schema
:
View Name | Definition |
employees_VIEW | SELECT * FROM source_schema.employees; |
orders_VIEW | SELECT * FROM source_schema.orders; |
Extending This Procedure
Exclude specific tables: Modify the
SELECT
query in the loop to filter out certain tables.Restrict columns: Instead of
SELECT *
, manually specify columns for security purposes.Automate execution: Schedule the procedure using Snowflake Tasks to keep views updated.
12.Stored Procedure to Drop Tables and Views
sqlCopyEditCREATE OR REPLACE PROCEDURE drop_objects_from_schema(schema_name STRING)
RETURNS STRING
LANGUAGE SQL
AS
$$
DECLARE object_list RESULTSET;
DECLARE object_name STRING;
DECLARE object_type STRING;
DECLARE sql_command STRING;
BEGIN
-- Step 1: Get all tables and views from the schema
LET object_list = (SELECT table_name, table_type
FROM INFORMATION_SCHEMA.TABLES
WHERE table_schema = :schema_name);
-- Step 2: Loop through each object and construct the DROP statement
FOR record IN object_list DO
LET object_name = record.table_name;
LET object_type = record.table_type; -- 'BASE TABLE' for tables, 'VIEW' for views
-- Construct the DROP statement
IF object_type = 'BASE TABLE' THEN
LET sql_command = 'DROP TABLE ' || :schema_name || '.' || :object_name;
ELSE
LET sql_command = 'DROP VIEW ' || :schema_name || '.' || :object_name;
END IF;
-- Step 3: Execute the DROP command
EXECUTE IMMEDIATE :sql_command;
END FOR;
RETURN 'All tables and views in schema ' || :schema_name || ' have been dropped successfully.';
END;
$$;
How It Works
The procedure retrieves all objects from the schema.
It loops through each table/view and dynamically creates a
DROP
statement.The
EXECUTE IMMEDIATE
command runs the dynamically generated SQL.
Example Usage
To drop all tables and views in the SALES
schema:
sqlCopyEditCALL drop_objects_from_schema('SALES');
This will generate and execute:
sqlCopyEditDROP TABLE SALES.orders;
DROP TABLE SALES.customers;
DROP VIEW SALES.sales_report;
DROP VIEW SALES.top_customers;
Enhancements
Selective Deletion: Modify the procedure to delete only tables or views, based on an input parameter.
Logging & Exception Handling: Capture errors when attempting to drop non-existent objects.
Example: Handling Non-Existent Objects
sqlCopyEditEXCEPTION
WHEN statement_error THEN
RETURN 'Error: Unable to drop object ' || :object_name || ' in schema ' || :schema_name;
13.Stored Procedure to Migrate Tables and Views
sqlCopyEditCREATE OR REPLACE PROCEDURE migrate_ddl(source_schema STRING, target_schema STRING)
RETURNS STRING
LANGUAGE SQL
AS
$$
DECLARE object_list RESULTSET;
DECLARE object_name STRING;
DECLARE object_type STRING;
DECLARE ddl_statement STRING;
DECLARE new_ddl_statement STRING;
BEGIN
-- Step 1: Get all tables and views from the source schema
LET object_list = (SELECT table_name, table_type
FROM INFORMATION_SCHEMA.TABLES
WHERE table_schema = :source_schema);
-- Step 2: Loop through each object and extract DDL
FOR record IN object_list DO
LET object_name = record.table_name;
LET object_type = record.table_type; -- 'BASE TABLE' for tables, 'VIEW' for views
-- Extract DDL statement
LET ddl_statement = (SELECT GET_DDL(object_type, :source_schema || '.' || :object_name));
-- Replace source schema with target schema
LET new_ddl_statement = REPLACE(ddl_statement, :source_schema, :target_schema);
-- Step 3: Execute the modified DDL in the target schema
EXECUTE IMMEDIATE :new_ddl_statement;
END FOR;
RETURN 'All tables and views from ' || :source_schema || ' have been migrated to ' || :target_schema || '.';
END;
$$;
How It Works
The procedure retrieves all objects from the source schema.
It extracts the DDL definition using
GET_DDL()
.It replaces the schema reference with the target schema.
The modified DDL is executed in the target schema.
Example Usage
To migrate tables and views from DEV_SCHEMA
to PROD_SCHEMA
:
sqlCopyEditCALL migrate_ddl('DEV_SCHEMA', 'PROD_SCHEMA');
This will generate and execute:
sqlCopyEditCREATE TABLE PROD_SCHEMA.orders (...);
CREATE TABLE PROD_SCHEMA.customers (...);
CREATE VIEW PROD_SCHEMA.sales_report AS SELECT ... FROM PROD_SCHEMA.orders;
Enhancements
Selective Migration: Modify the procedure to migrate only tables or views, based on an input parameter.
Logging & Exception Handling: Capture errors when attempting to create objects in the target schema.
Exclude Specific Tables/Views: Allow the exclusion of certain objects based on user input.
Example: Handling Errors
sqlCopyEditEXCEPTION
WHEN statement_error THEN
RETURN 'Error: Unable to migrate object ' || :object_name || ' from ' || :source_schema || ' to ' || :target_schema;
14.Automating Full and Incremental Load in Snowflake Using Stored Procedures
Overview
To efficiently manage data in Snowflake, we automate:
✅ Full Load – Replaces the entire target table with new data from the staging table.
✅ Incremental Load – Only inserts new records or updates existing ones without deleting historical data.
Step 1: Create Database and Schema
Before implementing the automation, ensure that the required database and schemas exist.
sqlCopyEditCREATE DATABASE IF NOT EXISTS EMP;
CREATE SCHEMA IF NOT EXISTS EMP.STAGING;
CREATE SCHEMA IF NOT EXISTS EMP.HRDATA;
CREATE SCHEMA IF NOT EXISTS EMP.PROCS;
CREATE SCHEMA IF NOT EXISTS EMP.WORK;
Step 2: Create Error Logging Table
This will capture errors encountered during execution.
sqlCopyEditCREATE TABLE EMP.WORK.SP_ERROR_LOGS
(PROC_NAME VARCHAR, ERROR_TYPE VARCHAR, ERROR_CODE VARCHAR, ERROR_MESSAGE VARCHAR, SQL_STATE VARCHAR);
Step 3: Create Staging and Target Tables
sqlCopyEditCREATE OR REPLACE TABLE emp.staging.employees (
employee_id NUMBER(6),
first_name VARCHAR(20),
last_name VARCHAR(25) NOT NULL,
email VARCHAR(25) NOT NULL,
phone_number VARCHAR(20),
hire_date DATE NOT NULL,
job_id VARCHAR(10) NOT NULL,
salary NUMBER(8,2),
commission_pct NUMBER(2,2),
manager_id NUMBER(6),
dept_id NUMBER(4)
);
CREATE OR REPLACE TABLE emp.hrdata.employees (
employee_id NUMBER(6),
first_name VARCHAR(20),
last_name VARCHAR(25) NOT NULL,
email VARCHAR(25) NOT NULL,
phone_number VARCHAR(20),
hire_date DATE NOT NULL,
job_id VARCHAR(10) NOT NULL,
salary NUMBER(8,2),
commission_pct NUMBER(2,2),
manager_id NUMBER(6),
dept_id NUMBER(4),
PRIMARY KEY (employee_id)
);
Step 4: Automate Full Load
The full load procedure truncates the target table and reloads it completely.
sqlCopyEditCREATE OR REPLACE PROCEDURE emp.procs.full_load()
RETURNS STRING
LANGUAGE SQL
AS
$$
BEGIN
-- Log the procedure start
INSERT INTO EMP.WORK.SP_ERROR_LOGS (PROC_NAME, ERROR_TYPE, ERROR_MESSAGE)
VALUES ('full_load', 'INFO', 'Starting full load process');
-- Truncate the target table before loading new data
TRUNCATE TABLE emp.hrdata.employees;
-- Load data from staging to target
INSERT INTO emp.hrdata.employees
SELECT * FROM emp.staging.employees;
-- Log success
INSERT INTO EMP.WORK.SP_ERROR_LOGS (PROC_NAME, ERROR_TYPE, ERROR_MESSAGE)
VALUES ('full_load', 'INFO', 'Full load completed successfully');
RETURN 'Full Load Completed Successfully';
END;
$$;
Executing the Full Load Procedure
sqlCopyEditCALL emp.procs.full_load();
✅ This will clear all records in hrdata.employees
and reload fresh data from staging.employees
.
Step 5: Automate Incremental Load (Upsert – Merge)
The incremental load procedure updates existing records and inserts new ones from staging.
sqlCopyEditCREATE OR REPLACE PROCEDURE emp.procs.incremental_load()
RETURNS STRING
LANGUAGE SQL
AS
$$
BEGIN
-- Log procedure start
INSERT INTO EMP.WORK.SP_ERROR_LOGS (PROC_NAME, ERROR_TYPE, ERROR_MESSAGE)
VALUES ('incremental_load', 'INFO', 'Starting incremental load process');
-- Merge operation: Update existing records, insert new records
MERGE INTO emp.hrdata.employees AS target
USING emp.staging.employees AS source
ON target.employee_id = source.employee_id
WHEN MATCHED THEN
UPDATE SET
target.first_name = source.first_name,
target.last_name = source.last_name,
target.email = source.email,
target.phone_number = source.phone_number,
target.hire_date = source.hire_date,
target.job_id = source.job_id,
target.salary = source.salary,
target.commission_pct = source.commission_pct,
target.manager_id = source.manager_id,
target.dept_id = source.dept_id
WHEN NOT MATCHED THEN
INSERT (
employee_id, first_name, last_name, email, phone_number,
hire_date, job_id, salary, commission_pct, manager_id, dept_id
)
VALUES (
source.employee_id, source.first_name, source.last_name, source.email, source.phone_number,
source.hire_date, source.job_id, source.salary, source.commission_pct, source.manager_id, source.dept_id
);
-- Log success
INSERT INTO EMP.WORK.SP_ERROR_LOGS (PROC_NAME, ERROR_TYPE, ERROR_MESSAGE)
VALUES ('incremental_load', 'INFO', 'Incremental load completed successfully');
RETURN 'Incremental Load Completed Successfully';
END;
$$;
Executing the Incremental Load Procedure
sqlCopyEditCALL emp.procs.incremental_load();
✅ This procedure:
Updates existing employee records if they already exist in
hrdata.employees
.Inserts new employee records if they don’t exist in
hrdata.employees
.
Step 6: Automate the Load with Snowflake Tasks
We can schedule these procedures to run automatically using Snowflake tasks.
Task for Full Load (Runs Daily at Midnight)
sqlCopyEditCREATE OR REPLACE TASK emp.procs.full_load_task
WAREHOUSE = COMPUTE_WH
SCHEDULE = 'USING CRON 0 0 * * * UTC'
AS
CALL emp.procs.full_load();
Task for Incremental Load (Runs Every Hour)
sqlCopyEditCREATE OR REPLACE TASK emp.procs.incremental_load_task
WAREHOUSE = COMPUTE_WH
SCHEDULE = 'USING CRON 0 * * * * UTC'
AS
CALL emp.procs.incremental_load();
Enable the Tasks
sqlCopyEditALTER TASK emp.procs.full_load_task RESUME;
ALTER TASK emp.procs.incremental_load_task RESUME;
✅ Now, the full load runs daily at midnight, and the incremental load runs every hour.
Step 7: Error Handling and Debugging
You can check for errors using:
sqlCopyEditSELECT * FROM emp.work.sp_error_logs ORDER BY SQL_STATE DESC;
If a procedure fails, it will log the error details in sp_error_logs
.
Final Summary
Process | Action |
Full Load | Clears hrdata.employees and loads fresh data from staging.employees |
Incremental Load | Updates existing employees and inserts new ones |
Snowflake Tasks | Automates scheduling for full and incremental loads |
Error Logging | Captures failures for debugging |
15. Code Deployment Using GitHub and Schema Change
📌 Example: Automating schema deployments with GitHub
Store SQL scripts in GitHub repository.
Use Liquibase or Flyway for version control.
Automate deployments via CI/CD pipeline.
✅ Ensures controlled and trackable schema changes across environments.
Conclusion
By implementing stored procedures, we can:
✔ Automate data loads and schema migrations
✔ Improve performance using SCD-1 and SCD-2
✔ Maintain consistent schema across databases
✔ Implement CI/CD for structured deployments
2. Understanding Variables, Session Variables, and Execute Immediate in Snowflake
Introduction
Variables in Snowflake play an important role in dynamic SQL execution, data manipulation, and control flow within stored procedures and scripting. This guide covers:
Regular Variables (used inside procedures)
Session Variables (persist for a session)
Execute Immediate (for dynamic SQL execution)
Variables in Snowflake
Variables in Snowflake are used inside stored procedures or scripts to store and manipulate values.
How to Declare a Variable?
Variables are declared using the DECLARE
statement.
Example: Declaring a Variable
DECLARE customer_name STRING;
DECLARE order_count INTEGER DEFAULT 0;
customer_name
stores a string.order_count
is an integer with a default value of 0.
Assigning Values to Variables
Values can be assigned using:
:=
OperatorDEFAULT
KeywordSELECT INTO
Statement
Example 1: Assigning Values Using :=
DECLARE price NUMBER(10,2);
BEGIN
LET price := 99.99;
RETURN price;
END;
Example 2: Assigning Default Values
DECLARE discount NUMBER DEFAULT 10;
Example 3: Assigning Values Using SQL Query
DECLARE total_sales NUMBER;
BEGIN
SELECT SUM(sales_amount) INTO :total_sales FROM sales_table;
RETURN total_sales;
END;
- Here,
total_sales
is calculated dynamically.
Session Variables
Session variables persist for the entire session but are NOT available outside it.
Declaring a Session Variable
Session variables are declared using SET
.
SET order_id = 1001;
SET customer_name = 'Jane Doe';
SET (B, C, city) = (100, 35, 'Hyderabad');
order_id
holds an integer.customer_name
stores a string.Multiple variables (
B, C, city
) are assigned values in one command.
Accessing Session Variables
Session variables are accessed using $
.
SELECT $order_id, $customer_name;
Example: Using Session Variables in SQL Query
SELECT * FROM orders WHERE order_id = $order_id;
Key Differences Between Regular and Session Variables
Feature | Regular Variables | Session Variables |
Scope | Available inside stored procedures | Available throughout the session |
Declaration | DECLARE statement | SET statement |
Accessibility | Cannot be accessed outside procedures | Can be accessed using $ |
Modification | Can be modified inside procedures | Cannot be changed inside stored procedures |
Using Variables in SQL Statements
Variables must be prefixed with :
when used inside SQL queries (Binding Variables).
Example: Concatenating Strings
DECLARE first_name STRING DEFAULT 'John';
DECLARE last_name STRING DEFAULT 'Doe';
DECLARE full_name STRING;
BEGIN
LET full_name := first_name || ' ' || last_name;
RETURN full_name;
END;
Example: Using Variables in SELECT
Statement
DECLARE total_sales NUMBER;
BEGIN
SELECT SUM(sales) INTO :total_sales FROM sales_data;
RETURN total_sales;
END;
- The
:
beforetotal_sales
indicates a binding variable.
Execute Immediate in Snowflake
EXECUTE IMMEDIATE
allows dynamic SQL execution in Snowflake. It is used when:
The SQL query is not known in advance.
You need to construct queries dynamically.
Running DDL (Data Definition Language) commands dynamically.
How to Use Execute Immediate?
Example: Creating a Table Dynamically
EXECUTE IMMEDIATE
$$
CREATE TABLE dynamic_table (
id INT,
name STRING
);
$$;
- This dynamically creates a table without writing static SQL.
Example: Inserting Data Dynamically
EXECUTE IMMEDIATE
$$
INSERT INTO orders (order_id, customer_name)
VALUES (1001, 'Alice');
$$;
Example: Using Execute Immediate in a Stored Procedure
CREATE OR REPLACE PROCEDURE create_table_dynamically(tab_name STRING)
RETURNS STRING
LANGUAGE SQL
EXECUTE AS CALLER
AS
$$
BEGIN
EXECUTE IMMEDIATE
$$ CREATE TABLE IDENTIFIER(:tab_name) (id INT, name STRING) $$;
RETURN 'Table Created Successfully';
END;
$$;
Explanation:
The procedure takes a table name as a parameter.
It creates a table dynamically using
EXECUTE IMMEDIATE
.
Calling the Procedure
CALL create_table_dynamically('customer_data');
This creates a table named customer_data
.
5. Using Variables with Execute Immediate
Example: Assigning Values Dynamically
EXECUTE IMMEDIATE
$$
DECLARE revenue NUMBER(10,2);
BEGIN
LET revenue := 5000.50;
RETURN revenue;
END;
$$;
- Here, a variable
revenue
is declared, assigned a value, and returned.
6. Handling Errors with Execute Immediate
Snowflake supports error handling inside dynamic SQL execution.
Example: Handling Errors in Execute Immediate
EXECUTE IMMEDIATE
$$
BEGIN
EXECUTE IMMEDIATE $$ SELECT * FROM non_existing_table $$;
EXCEPTION
WHEN STATEMENT_ERROR THEN
RETURN 'An error occurred in the statement!';
WHEN OTHER ERROR THEN
RETURN 'A general error occurred!';
END;
$$;
- If the table doesn’t exist, the error is caught and a friendly message is returned.
7. Real-World Use Cases
Use Case 1: Generating Daily Sales Report
A retail company needs to generate a sales report every day.
CREATE OR REPLACE PROCEDURE generate_daily_sales_report()
RETURNS STRING
LANGUAGE SQL
EXECUTE AS CALLER
AS
$$
BEGIN
EXECUTE IMMEDIATE
$$ INSERT INTO daily_sales SELECT CURRENT_DATE, SUM(sales) FROM orders WHERE order_date = CURRENT_DATE $$;
RETURN 'Daily Sales Report Generated Successfully';
END;
$$;
Use Case 2: Managing Dynamic User Roles
A corporate database needs to create roles dynamically based on input.
CREATE OR REPLACE PROCEDURE create_role_dynamically(role_name STRING)
RETURNS STRING
LANGUAGE SQL
EXECUTE AS CALLER
AS
$$
BEGIN
EXECUTE IMMEDIATE
$$ CREATE ROLE IDENTIFIER(:role_name) $$;
RETURN 'Role Created Successfully';
END;
$$;
Calling the Procedure
CALL create_role_dynamically('Analyst');
This dynamically creates a role named "Analyst".
Best Practices for Using Variables and Execute Immediate
Use Session Variables for Global Parameters
- Example: Store user preferences in a session.
Avoid Hardcoded Values in Dynamic SQL
- Use binding variables (
:
) for security.
- Use binding variables (
Handle Errors in Dynamic Queries
- Use EXCEPTION blocks to catch errors.
Use
IDENTIFIER(:variable_name)
- When dynamically referencing tables or columns.
Optimize Performance with Auto-Suspend
- Ensure warehouses do not run indefinitely.
Conclusion
Understanding variables, session variables, and Execute Immediate in Snowflake helps in writing flexible, dynamic, and optimized SQL scripts.
✅ Regular Variables are used inside procedures.
✅ Session Variables persist across queries in a session.
✅ Execute Immediate allows dynamic query execution.
✅ Error Handling ensures smooth execution.
✅ Real-World Use Cases include reports, user role management, and automation.
3. Understanding Table Literals and Identifiers in Snowflake
Introduction
In Snowflake, sometimes we need to use variables or parameters as table names dynamically. This is where Table Literals and Identifiers come into play. Table Literals help in referencing tables inside SQL statements, while Identifiers allow referencing various database objects dynamically.
Table Literals in Snowflake
What are Table Literals?
Table Literals allow us to dynamically pass a table name to a query instead of hardcoding it. This is useful when working with dynamic SQL queries where the table name needs to be determined at runtime.
Syntax:
TABLE( <string_literal> | <session_variable> | <bind_variable> )
string_literal: The actual table name or fully qualified table name.
session_variable: A variable storing the table name within a session.
bind_variable: A variable bound at runtime.
Imagine you are running a shop and keep your sales records in different tables based on the year. You have:
sales_2023
sales_2024
sales_2025
Now, let’s say you want to check sales for a particular year, but you don’t want to manually type the table name every time. Instead, you can store the table name in a variable and use Table Literals to dynamically refer to the correct table in your SQL query.
Real-World Example
Suppose you work for an e-commerce website that tracks customer orders in different tables for each year. If a customer service agent wants to check orders for 2023, you would normally run:
SELECT * FROM sales_2023;
But what if they want to check sales for 2024 or 2025? Instead of changing the table name manually, you can use Table Literals to make your query dynamic.
Using Table Literals in SQL
Instead of hardcoding the table name, we store it in a variable and pass it dynamically.
SET my_table = 'sales_2024';
SELECT * FROM TABLE($my_table);
This makes your SQL flexible and reusable—you can change the table name without modifying the query!
Key Features of Table Literals:
They are used to pass the name of a table dynamically.
They appear in the
FROM
clause of a SQL statement.They accept scalar values as input.
They return a set of zero or more rows.
They can be used as a source of rows in a
FROM
clause.They are similar to a table function TABLE().
Examples of Table Literals:
-- Using a string literal as a table name
SELECT * FROM TABLE('table_name');
-- Using a fully qualified table name
SELECT * FROM TABLE('db_name.schema_name.table_name');
-- Using a session variable
SET myvar = 'mytable';
SELECT * FROM TABLE($myvar);
-- Using a bind variable
SELECT * FROM TABLE(?);
SELECT * FROM TABLE(:bind_variable);
Incorrect Usage of Table Literals:
-- These statements are incorrect in Snowflake
SELECT * FROM $myvar; -- Incorrect
SELECT * FROM ?; -- Incorrect
SELECT * FROM :bind_variable; -- Incorrect
Real-World Example:
Imagine a scenario where an ETL process loads data into different tables dynamically every day. Instead of writing separate queries for each table, a Table Literal can be used to dynamically select data from the table corresponding to the current date.
SET current_table = 'sales_20240214';
SELECT * FROM TABLE($current_table);
Identifiers in Snowflake
What are Identifiers?
Identifiers in Snowflake are used to refer to database objects dynamically, such as:
Tables
Schemas
Databases
Functions
Unlike Table Literals, which are restricted to the FROM
clause, Identifiers can be used in any type of SQL statement.
Syntax:
IDENTIFIER( <string_literal> | <session_variable> | <bind_variable> | <SQL Variable> )
Why Use Identifiers?
They allow dynamic referencing of various Snowflake objects.
They support variables and session variables.
They make SQL queries more flexible and reusable.
They allow executing dynamic SQL statements efficiently.
Examples of Identifiers in Snowflake:
-- Creating a database dynamically
CREATE OR REPLACE DATABASE IDENTIFIER('my_db');
-- Setting a schema name as a variable
SET schema_name = 'my_db.my_schema';
-- Using an identifier to switch schema dynamically
USE SCHEMA IDENTIFIER($schema_name);
-- Dropping a table dynamically
DROP TABLE IDENTIFIER(?);
-- Describing a table using an identifier
DESCRIBE TABLE IDENTIFIER(:table_name);
-- Counting records dynamically from a table
SELECT COUNT(*) AS total_records FROM IDENTIFIER(:table_name);
Real-World Example:
Consider a multi-tenant application where each client has their own schema. Instead of writing separate queries for each schema, Identifiers allow querying dynamically.
SET client_schema = 'client_A_schema';
USE SCHEMA IDENTIFIER($client_schema);
SELECT * FROM IDENTIFIER('orders');
This approach allows developers to switch schemas dynamically without modifying the actual query.
Key Differences Between Table Literals and Identifiers
Feature | Table Literals | Identifiers |
Purpose | Used for referencing tables dynamically | Used for referencing any database object dynamically |
Usable In | Only in FROM clause | Anywhere in SQL statements |
Supports Variables | Yes | Yes |
Supports Bind Variables | Yes | Yes |
Example Usage | SELECT * FROM TABLE($var); | USE SCHEMA IDENTIFIER($schema_name); |
Conclusion
Understanding Table Literals and Identifiers in Snowflake is essential for writing dynamic, flexible, and reusable SQL queries.
Use Table Literals when dynamically passing table names in the
FROM
clause.Use Identifiers for dynamically referencing schemas, databases, tables, and functions.
By leveraging these features, Snowflake users can build scalable and efficient data pipelines that handle dynamic queries with ease.
4.Branching Constructs in Snowflake Scripting
Branching constructs help us make decisions in a program based on certain conditions. Snowflake scripting supports two main types of branching constructs:
IF Statement
CASE Statement
1. IF Statement
The IF statement checks a condition and executes a block of code if the condition is true. If the first condition is false, it checks the next condition using ELSEIF, and if no conditions are true, it executes the ELSE block.
Syntax:
IF (Condition1) THEN
< Statements to be executed >
ELSEIF (Condition2) THEN
< Statements to be executed >
ELSEIF (Condition3) THEN
< Statements to be executed >
ELSE
< Statements to be executed >
END IF;
Example:
Imagine you are calculating discounts based on the total amount of a customer's purchase.
DECLARE total_amount NUMBER DEFAULT 500;
DECLARE discount NUMBER;
BEGIN
IF total_amount > 1000 THEN
LET discount := 20; -- 20% discount for orders above 1000
ELSEIF total_amount > 500 THEN
LET discount := 10; -- 10% discount for orders between 500-1000
ELSE
LET discount := 5; -- 5% discount for orders below 500
END IF;
RETURN discount;
END;
📌 Explanation:
If the total amount is greater than 1000, a 20% discount is applied.
If it's between 500 and 1000, a 10% discount is given.
Otherwise, a 5% discount is applied.
2. CASE Statement
The CASE statement is an alternative to IF-ELSEIF conditions. It goes through a set of conditions and executes the block of code where the first condition is true.
Syntax:
CASE
WHEN <Condition1> THEN <Value or Expression>
WHEN <Condition2> THEN <Value or Expression>
WHEN <Condition3> THEN <Value or Expression>
ELSE <Value or Expression>
END;
Example:
You are categorizing employees based on their years of experience.
DECLARE years_experience NUMBER DEFAULT 7;
DECLARE category STRING;
BEGIN
CASE
WHEN years_experience >= 10 THEN
LET category := 'Senior';
WHEN years_experience >= 5 THEN
LET category := 'Mid-Level';
ELSE
LET category := 'Junior';
END CASE;
RETURN category;
END;
📌 Explanation:
If an employee has 10+ years of experience, they are categorized as Senior.
If they have 5-9 years, they are Mid-Level.
If they have less than 5 years, they are Junior.
Looping Constructs in Snowflake Scripting
Loops allow us to execute a block of code multiple times. Snowflake scripting supports four types of loops:
FOR Loop
WHILE Loop
REPEAT Loop
LOOP with BREAK
1. FOR Loop
The FOR loop runs a block of code a fixed number of times.
Syntax:
FOR variable IN start_value .. end_value DO
< Statements to be executed >
END FOR;
Example:
Generating a list of numbers from 1 to 5.
BEGIN
FOR i IN 1..5 DO
PRINT 'Number: ' || i;
END FOR;
END;
📌 Explanation:
This loop runs 5 times, printing numbers 1 to 5.
2. WHILE Loop
The WHILE loop keeps running until a specified condition becomes false.
Syntax:
WHILE ( condition ) DO
< Statements to be executed >
END WHILE;
Example:
You want to process a payment until the balance reaches zero.
DECLARE balance NUMBER DEFAULT 500;
DECLARE payment NUMBER DEFAULT 100;
BEGIN
WHILE balance > 0 DO
LET balance := balance - payment;
PRINT 'Remaining Balance: ' || balance;
END WHILE;
END;
📌 Explanation:
Starts with 500.
Deducts 100 in each loop iteration.
Stops when balance is 0.
3. REPEAT Loop
The REPEAT loop runs at least once and then checks the condition.
Syntax:
REPEAT
< Statements to be executed >
UNTIL ( condition )
END REPEAT;
Example:
You need to generate random numbers until you get a number greater than 90.
DECLARE random_num NUMBER;
BEGIN
REPEAT
LET random_num := RANDOM(1, 100);
PRINT 'Generated Number: ' || random_num;
UNTIL random_num > 90
END REPEAT;
END;
📌 Explanation:
Generates a random number.
If the number is ≤ 90, it keeps running.
Stops when it exceeds 90.
4. LOOP with BREAK
The LOOP statement runs indefinitely until a BREAK condition is met.
Syntax:
LOOP
< Statements to be executed >
....
BREAK;
....
END LOOP;
Example:
Simulating a countdown timer.
DECLARE counter NUMBER DEFAULT 10;
BEGIN
LOOP
PRINT 'Countdown: ' || counter;
LET counter := counter - 1;
IF counter = 0 THEN
BREAK;
END IF;
END LOOP;
PRINT 'Time is up!';
END;
📌 Explanation:
Prints numbers from 10 to 1.
Stops when it reaches 0.
Summary Table
Construct | Use Case | Key Feature |
IF Statement | Checking conditions | Executes first TRUE condition |
CASE Statement | Categorizing values | Works like a switch case |
FOR Loop | Fixed number of iterations | Runs N times |
WHILE Loop | Conditional looping | Runs until condition is false |
REPEAT Loop | At least one iteration | Checks condition after execution |
LOOP with BREAK | Indefinite looping | Stops when BREAK condition is met |
Final Thoughts
Branching and looping constructs simplify complex logic in Snowflake scripting.
By mastering these, you can automate data processing, optimize queries, and build dynamic workflows efficiently.
Let me know if you need any additional explanations or custom examples!
5.Cursors and Result sets in Snowflake
Cursors in Snowflake are essential tools for processing query results row-by-row within stored procedures or scripts. They enable iterative operations on data, which is particularly useful for complex transformations or conditional logic that can't be efficiently handled with set-based operations. Result sets represent the data retrieved by a query, and cursors act as pointers to traverse these result sets.
Understanding Cursors
A cursor is a database object that allows you to:
Retrieve rows from a query result (result set).
Process individual rows sequentially.
Perform operations like updates, inserts, or deletions on a row-by-row basis.
While Snowflake excels at set-based operations (optimized for bulk data processing), cursors are valuable for scenarios requiring per-row logic, such as:
Conditional branching based on row values.
Integrating with external systems row by row.
Iterating through dynamic query results.
How to Use Cursors in Snowflake
Cursors follow a lifecycle: Declaration → Open → Fetch → Close.
Below is a step-by-step guide with examples:
1.Declare a Cursor
Cursors are declared in the DECLARE
section of a Snowflake Scripting block or using LET
for dynamic assignment.
Static Cursor (Fixed Query)
sql
Copy
DECLARE
cur_static CURSOR FOR
SELECT employee_id, name FROM employees WHERE department = 'Engineering';
Dynamic Cursor (Using LET)
sql
Copy
BEGIN
LET query := 'SELECT * FROM products WHERE category = ?';
LET cur_dynamic CURSOR FOR query; -- Query defined dynamically
END;
2. Open a Cursor
Opening a cursor initializes it and binds parameters (if any). This step is optional for static cursors (they auto-open) but required for dynamic cursors.
sql
Copy
OPEN cur_dynamic USING ('Electronics'); -- Bind parameter to dynamic cursor
3. Fetch Rows
Use loops (FOR
, WHILE
) to fetch rows. The FETCH
command retrieves rows into variables.
Example with FOR Loop (Auto-Fetch)
sql
Copy
DECLARE
id INTEGER;
name VARCHAR;
cur CURSOR FOR SELECT employee_id, name FROM employees;
BEGIN
FOR row_variable IN cur DO
id := row_variable.employee_id;
name := row_variable.name;
-- Process row (e.g., log or transform data)
RETURN id || ': ' || name;
END FOR;
END;
Manual Fetching
sql
Copy
DECLARE
cur CURSOR FOR SELECT price FROM products;
price_value FLOAT;
BEGIN
OPEN cur;
FETCH cur INTO price_value; -- Fetch one row
WHILE (SQLCODE = 0) DO -- Loop until no more rows
-- Process price_value
FETCH cur INTO price_value;
END WHILE;
CLOSE cur;
END;
4. Close the Cursor
Explicitly close cursors opened with OPEN
to free resources. Static cursors in FOR
loops auto-close.
sql
Copy
CLOSE cur_dynamic;
Dynamic Cursors with EXECUTE IMMEDIATE
For fully dynamic SQL (e.g., queries built at runtime), use EXECUTE IMMEDIATE
:
sql
Copy
DECLARE
sql_text VARCHAR := 'SELECT * FROM sales WHERE region = ?';
res RESULTSET;
cur CURSOR;
BEGIN
res := (EXECUTE IMMEDIATE :sql_text USING ('West'));
cur := res; -- Assign result set to cursor
FOR row IN cur DO
-- Process each row
END FOR;
END;
Best Practices
Avoid Overuse: Cursors can be slow for large datasets. Prefer set-based operations where possible.
Filter Early: Narrow down result sets in the cursor query (e.g., use
WHERE
clauses) to minimize iterations.Batch Processing: Use
FETCH
withLIMIT
to process rows in batches for better performance.Explicitly Close Cursors: Ensure resources are released promptly.
Conclusion
Cursors in Snowflake provide granular control over result sets, enabling row-by-row processing when necessary. While they should be used sparingly in favor of bulk operations, they are indispensable for specific use cases. Always pair them with efficient query design and resource management to maintain performance.
6.Exception Handling in Snowflake Scripting
What is Exception Handling?
Exception handling in Snowflake Scripting helps manage errors that occur during script execution. If an error occurs while executing a statement, Snowflake raises an exception and stops execution unless the error is handled properly.
How Exceptions Work in Snowflake?
If an error occurs while executing a SQL statement, Snowflake raises an exception.
An exception prevents subsequent statements from executing.
Exception handlers can be defined in the script to manage errors gracefully.
If a handler is found for an exception, the script executes the handler’s logic; otherwise, the error stops execution.
Types of Exceptions
1. Built-in Exceptions
Snowflake provides two built-in exceptions:
Snowflake provides two built-in exceptions to handle errors in SQL execution:
1. STATEMENT_ERROR
Raised when a SQL statement encounters an execution error.
📌 Example 1: Dropping a non-existent table
DROP TABLE non_existent_table;
Error Message:
SQL compilation error: Table 'NON_EXISTENT_TABLE' does not exist
✅ Fix: Check if the table exists before dropping it:
DROP TABLE IF EXISTS non_existent_table;
📌 Example 2: Inserting incorrect data type into a column
CREATE TABLE employees (id INT, name STRING, salary FLOAT); INSERT INTO employees (id, name, salary) VALUES (1, 'John Doe', 'abc');
Error Message:
Numeric value 'abc' is not recognized
✅ Fix: Ensure the correct data type is used:
INSERT INTO employees (id, name, salary) VALUES (1, 'John Doe', 50000);
📌 Example 3: Dividing by zero in a query
SELECT 100 / 0;
Error Message:
Division by zero error
✅ Fix: Use
NULLIF
to avoid division by zero errors:SELECT 100 / NULLIF(0, 0);
2. EXPRESSION_ERROR
Raised when an invalid expression is used in a SQL statement.
📌 Example 1: Assigning a VARCHAR to a FLOAT column
CREATE TABLE products (id INT, price FLOAT); INSERT INTO products (id, price) VALUES (1, 'ten');
Error Message:
Numeric value 'ten' is not recognized
✅ Fix: Ensure only numerical values are inserted:
INSERT INTO products (id, price) VALUES (1, 10.99);
📌 Example 2: Using an aggregate function incorrectly
SELECT SUM(salary) FROM employees WHERE department = 'HR' GROUP BY name;
Error Message:
Expression 'name' must be part of an aggregate function or GROUP BY clause
✅ Fix: Ensure correct
GROUP BY
usage:SELECT name, SUM(salary) FROM employees WHERE department = 'HR' GROUP BY name;
📌 Example 3: Using an undefined variable in a SQL expression
SELECT salary * bonus FROM employees;
Error Message:
Column 'bonus' does not exist
✅ Fix: Ensure all referenced columns exist:
SELECT salary * 0.10 AS bonus FROM employees;
How to Handle These Errors in Snowflake
You can use EXCEPTION HANDLING with
BEGIN...EXCEPTION
blocks in Snowflake procedures to catch these errors.📌 Example: Handling Errors in a Procedure
CREATE OR REPLACE PROCEDURE safe_drop_table(table_name STRING) RETURNS STRING LANGUAGE SQL AS $$ BEGIN BEGIN EXECUTE IMMEDIATE 'DROP TABLE ' || table_name; RETURN 'Table dropped successfully'; EXCEPTION WHEN STATEMENT_ERROR THEN RETURN 'Error: Table does not exist or another issue occurred'; END; END; $$;
✅ Now calling this procedure won't fail if the table does not exist:
CALL safe_drop_table('non_existent_table');
Summary
| Exception Type | Cause | Example | Fix | | --- | --- | --- | --- | | STATEMENT_ERROR | SQL execution error | Dropping a non-existent table, inserting incorrect data types | Check existence before executing, use correct data types | | EXPRESSION_ERROR | Invalid expression | Assigning VARCHAR to FLOAT, incorrect use of aggregate functions | Ensure correct data types and function usage |
2. User-Defined Exceptions
In addition to built-in exceptions, users can define custom exceptions in Snowflake scripting.
Declaring an Exception
A user-defined exception is declared using the EXCEPTION
keyword.
DECLARE my_exception EXCEPTION (-20002, 'Custom Exception Occurred');
The error code should be between -20999 to -20001.
The exception name acts as an identifier that can be used in error handling.
Using Exception Handlers
Exception handlers can be defined inside an EXCEPTION
block within a procedure or script.
Example: Handling Different Types of Errors
BEGIN
-- SQL statements that may raise an error
DELETE FROM employees WHERE emp_id = 100;
EXCEPTION
WHEN statement_error THEN
RETURN OBJECT_CONSTRUCT('Error Type', 'STATEMENT ERROR', 'SQLCODE', sqlcode, 'SQLERRM', sqlerrm, 'SQLSTATE', sqlstate);
WHEN expression_error THEN
RETURN OBJECT_CONSTRUCT('Error Type', 'EXPRESSION ERROR', 'SQLCODE', sqlcode, 'SQLERRM', sqlerrm, 'SQLSTATE', sqlstate);
WHEN my_exception THEN
RETURN OBJECT_CONSTRUCT('Error Type', 'MY_EXCEPTION', 'SQLCODE', sqlcode, 'SQLERRM', sqlerrm, 'SQLSTATE', sqlstate);
WHEN OTHER THEN
RETURN OBJECT_CONSTRUCT('Error Type', 'OTHER ERROR', 'SQLCODE', sqlcode, 'SQLERRM', sqlerrm, 'SQLSTATE', sqlstate);
END;
Exception Handling Components
SQLCODE: A 5-digit signed integer that represents the error code. For user-defined exceptions, the error code must be between -20999 to -20001.
SQLERRM: A message describing the error.
SQLSTATE: A 5-character code based on the ANSI SQL standard for classifying errors.
How Exception Handling Works?
The script executes SQL statements inside the
BEGIN...END
block.If an error occurs, Snowflake looks for a matching
EXCEPTION
handler.If a matching handler is found, the script executes the handler’s logic and prevents the script from stopping abruptly.
If no handler is found, execution stops, and the error message is displayed.
Best Practices for Exception Handling in Snowflake
Always define exception handlers for critical transactions to avoid script failures.
Use meaningful error codes and messages for user-defined exceptions.
Log error details using SQLCODE, SQLERRM, and SQLSTATE to help diagnose issues.
Handle specific exceptions separately before using
WHEN OTHER
to catch generic errors.
Conclusion
Exception handling is an essential feature in Snowflake scripting that ensures smooth execution of SQL scripts by managing errors effectively. By understanding and implementing exception handling correctly, you can prevent unexpected failures and improve the robustness of your scripts.
7.Transactions in Snowflake
Understanding Transactions
A transaction is a sequence of SQL statements executed as a single unit. If any statement within the transaction fails, the entire transaction can be rolled back, preventing partial updates. Transactions ensure data consistency and integrity.
How Transactions Work in Snowflake?
By default, Snowflake auto-commits SQL statements.
If multiple statements need to be committed together, they must be grouped into a transaction.
A transaction can be explicitly started using
BEGIN TRANSACTION
.It can be explicitly ended using either
COMMIT
(to save changes) orROLLBACK
(to undo changes).
Transaction Control Statements
1. BEGIN TRANSACTION
This command starts a transaction and groups subsequent statements into a single unit.
sqlCopyEditBEGIN TRANSACTION;
2. COMMIT
This command saves all changes made within the transaction permanently.
sqlCopyEditCOMMIT;
3. ROLLBACK
This command undoes all changes made within the transaction.
sqlCopyEditROLLBACK;
Example of a Transaction
sqlCopyEditBEGIN TRANSACTION;
UPDATE employees SET salary = salary + 5000 WHERE department = 'HR';
DELETE FROM employees WHERE emp_id = 105;
COMMIT;
If both statements execute successfully, the changes are committed.
If an error occurs in any statement, the transaction can be rolled back.
Rollback on Failure
If any statement within the transaction fails, all statements in that transaction are automatically rolled back to maintain consistency.
Example of Failure Handling
sqlCopyEditBEGIN TRANSACTION;
UPDATE employees SET salary = salary + 5000 WHERE department = 'HR';
DELETE FROM employees WHERE emp_id = 105;
INSERT INTO employees(emp_id, name) VALUES (106); -- Missing required columns, causes failure
ROLLBACK; -- Automatically executed if any statement fails
The failure in the
INSERT
statement rolls back all previous changes.No salary updates or deletions occur.
DDL Statements in Transactions
DDL (Data Definition Language) statements like CREATE
, DROP
, and ALTER
do not follow transactions. If executed inside a transaction, they are treated as separate transactions and committed immediately.
Example
sqlCopyEditBEGIN TRANSACTION;
UPDATE employees SET salary = salary + 5000 WHERE department = 'HR';
CREATE TABLE backup_employees AS SELECT * FROM employees;
COMMIT;
The
UPDATE
statement is part of the transaction.The
CREATE TABLE
statement is auto-committed immediately and cannot be rolled back.
Key Takeaways
Transactions group multiple SQL statements to ensure data consistency.
Use
BEGIN TRANSACTION
to start a transaction.Use
COMMIT
to save changes andROLLBACK
to undo changes.If a transaction fails, all statements within it are automatically rolled back.
DDL statements (CREATE, DROP, ALTER) are committed immediately, even inside a transaction.
By implementing transactions effectively, you can prevent data inconsistencies, ensure rollback on errors, and maintain reliable database operations in Snowflake.
8.User-Defined Functions (UDFs) in Snowflake
User-Defined Functions (UDFs) in Snowflake allow you to encapsulate custom logic for reuse in SQL queries, enhancing modularity and simplifying complex operations. Snowflake supports two types of UDFs: SQL UDFs (written in SQL) and JavaScript UDFs (written in JavaScript). Additionally, UDFs can be scalar (return a single value) or tabular (return a table).
Types of UDFs
1. SQL UDFs
Scalar: Return a single value per row.
Syntax:
sql
Copy
CREATE OR REPLACE FUNCTION function_name (arguments) RETURNS return_type AS $$ -- SQL logic using arguments SELECT expression; $$ ;
Example:
sql
Copy
CREATE FUNCTION add_tax(price FLOAT) RETURNS FLOAT AS $$ SELECT price * 1.07; -- Adds 7% tax $$ ; -- Usage: SELECT add_tax(100); -- Returns 107.0
2. JavaScript UDFs
Scalar: Return a single value using JavaScript logic.
Syntax:
sql
Copy
CREATE OR REPLACE FUNCTION function_name (arguments) RETURNS return_type LANGUAGE JAVASCRIPT AS $$ // JavaScript logic return value; $$ ;
Example:
sql
Copy
CREATE FUNCTION reverse_string(s VARCHAR) RETURNS VARCHAR LANGUAGE JAVASCRIPT AS $$ return S.split("").reverse().join(""); $$ ; -- Usage: SELECT reverse_string('Snowflake'); -- Returns 'ekalfwonS'
3. Tabular UDFs (Table Functions)
Return a table (multiple rows/columns) using the
RETURNS TABLE
clause.Syntax:
sql
Copy
CREATE OR REPLACE FUNCTION function_name (arguments) RETURNS TABLE (column1 TYPE, column2 TYPE, ...) AS $$ -- SQL query returning a table SELECT col1, col2 FROM ... $$ ;
Example:
sql
Copy
CREATE FUNCTION get_employees(dept VARCHAR) RETURNS TABLE (id INTEGER, name VARCHAR) AS $$ SELECT employee_id, name FROM employees WHERE department = dept $$ ; -- Usage: SELECT * FROM TABLE(get_employees('Sales'));
4. Secure UDFs
Protect the function’s logic from being viewed by unauthorized users.
Add the
SECURE
keyword during creation:sql
Copy
CREATE SECURE FUNCTION secure_function(...)
Key Features
Overloading: Multiple UDFs can share the same name but differ in arguments.
Dependencies: Use
SHOW FUNCTIONS
orDESCRIBE FUNCTION
to view dependencies.Error Handling: JavaScript UDFs can include
try/catch
blocks.
When to Use UDFs
Reusable Logic: Encapsulate frequently used calculations (e.g., tax, discounts).
Complex Transformations: String manipulation, JSON parsing, or custom aggregations.
Cross-Table Logic: Simplify joins or filters repeated across queries.
Data Validation: Enforce business rules (e.g., email format checks).
Best Practices
Optimize for Performance:
Use SQL UDFs for better performance (Snowflake optimizes them).
Avoid JavaScript UDFs for large datasets (slower execution).
Avoid Nested UDFs: Deeply nested UDFs can complicate debugging.
Use Scalar UDFs Sparingly: They process rows individually, which can be slow.
Leverage Caching: Snowflake caches UDF results for repeated inputs.
Limitations
JavaScript UDFs cannot execute external APIs or access the file system.
UDFs cannot perform DML operations (use stored procedures instead).
Tabular UDFs cannot be nested in subqueries.
Example: Combined SQL and JavaScript UDF
sql
Copy
-- SQL UDF to calculate discounted price
CREATE FUNCTION apply_discount(price FLOAT, discount_rate FLOAT)
RETURNS FLOAT
AS
$$
SELECT price * (1 - discount_rate)
$$
;
-- JavaScript UDF to generate a UUID
CREATE FUNCTION generate_uuid()
RETURNS VARCHAR
LANGUAGE JAVASCRIPT
AS
$$
return Math.random().toString(36).substring(2, 15);
$$
;
-- Usage in a query:
SELECT
product_id,
apply_discount(price, 0.1) AS discounted_price,
generate_uuid() AS uuid
FROM products;