Inline Stored Procedures for hybrid tables¶
Inline Stored Procedures are a new type of Snowflake stored procedure designed specifically for operational workloads on hybrid tables. By executing the entire procedure body as a single atomic unit pushed directly to the query processing layer, Inline Stored Procedures reduce per-statement overhead and deliver significantly lower latency for OLTP-style workloads. In benchmarks using the TPROC-C workload, Inline Stored Procedures achieve over 7,000 transactions per minute (TPM) on a single XSMALL warehouse, more than a 10x improvement compared to standard Snowflake stored procedures.
Prerequisites¶
This public preview is available on any Snowflake account and warehouse. No request is required.
For the best performance, set the ENABLE_USE_STABLE_PATH parameter to TRUE on the warehouse
that runs your Inline Stored Procedures. For details, see
Relationship to operational query performance.
Relationship to operational query performance¶
Inline Stored Procedures build on the operational query performance improvements available for hybrid tables. Those improvements automatically recognize recurring, short-running queries on hybrid tables and optimize their execution by reducing the per-query overhead from parsing, plan compilation, and scheduling.
To get the best performance, set the ENABLE_USE_STABLE_PATH parameter to TRUE on the warehouse
that runs your Inline Stored Procedures. This is the same parameter that enables the operational
query performance improvements, and it applies those optimizations to the individual statements
inside the procedure body. Enabling this parameter also optimizes single-statement queries on the
same warehouse, so both workload types benefit. These optimizations are transparent: Snowflake
applies them automatically when eligible queries are detected.
For more details on eligible query patterns and how the optimizations work, see Performance improvements for operational queries on hybrid tables.
Overview¶
An Inline Stored Procedure is declared with CREATE INLINE PROCEDURE and uses a
BEGIN ATOMIC block as its body. The ATOMIC keyword signals to Snowflake that all
statements in the procedure should be compiled together, pushed to the execution layer as a
single unit, and run as one atomic block.
Inline Stored Procedures differ from standard stored procedures in the following key ways:
- Execution model: The entire procedure body is pre-compiled as a unit and dispatched to the query processing layer in a single step, avoiding the per-statement overhead of standard stored procedures.
- Atomic execution: Each invocation is treated as an atomic block.
If any statement fails, all prior changes in the same invocation are rolled back automatically.
You can’t use explicit
BEGIN,COMMIT, orROLLBACKinside an Inline Stored Procedure. - Hybrid tables only: Inline Stored Procedures operate exclusively on hybrid tables. Statements that access standard Snowflake tables or other table types aren’t supported within Inline Stored Procedures.
- OLTP-oriented: Inline Stored Procedures are designed for short-running, low-latency operations. Each invocation should touch a small number of rows and aim to complete in a few hundred milliseconds.
Use cases¶
Inline Stored Procedures are best suited for high-throughput, OLTP-style workloads where low latency and reduced round-trip overhead matter:
- Single-statement procedures that wrap a DML operation. A common pattern is to wrap a single
INSERT,UPDATE, orDELETEin a stored procedure for abstraction or access control. With a standard stored procedure, each statement in the body incurs its own round trip between Snowflake layers. An Inline Stored Procedure executes the entire body as one unit, reducing that overhead and significantly lowering latency. - Multi-statement OLTP transactions. Procedures that perform several related DML operations, such as reading a row to compute a value and then writing it back, or updating one row and recording an audit entry, avoid multiple per-statement round trips and run as a single atomic unit.
- Scheduled maintenance from a task. Invoke an Inline Stored Procedure from a
Snowflake task to run recurring DML on hybrid tables. A
CALLstatement is a standard SQL statement, so it works in a task definition the same way as other supported SQL.
Note
Inline Stored Procedures are compiled at CALL time, not at CREATE time. This is the
same behavior as standard Snowflake stored procedures. However, because Inline Stored Procedures
don’t yet support some constructs (see Current limitations),
you may encounter errors only at runtime even though the CREATE statement succeeds.
For example, the following CREATE statement succeeds, but the CALL fails because the
INSERT is compiled independently and can’t see the table created by the preceding
CREATE HYBRID TABLE:
To avoid this class of error, ensure that all tables referenced in the procedure already exist
before creating the procedure, and don’t include DDL inside Inline Stored Procedures. Because
unsupported constructs are only detected at CALL time, Snowflake recommends invoking a
newly created Inline Stored Procedure in a test environment to surface any
incompatible features before deploying it to a production workload.
Atomic blocks¶
Every Inline Stored Procedure body is an ATOMIC block. An atomic block is a group of SQL
statements that Snowflake compiles and executes as a single unit. The block uses the following
syntax:
Execution¶
When an Inline Stored Procedure is called, Snowflake acquires a single read timestamp at the start of the atomic block. All statements in the block share that timestamp, so every read sees a consistent snapshot of the data as of the moment the block began. Writes made by other sessions after the block starts aren’t visible to any statement in the block.
Within the block, statements run sequentially and each statement can see the writes made by earlier statements in the same block (read-your-own-writes). Outside the block, no other session can see the block’s writes until the block completes successfully.
Error handling¶
Either all statements in the block succeed, or the entire block is rolled back:
- If all statements complete without an unhandled error, the block’s changes commit together.
- If any statement raises an unhandled error, all changes made by prior statements in the same block are rolled back automatically.
- You can use
EXCEPTIONhandlers (withWHEN ... THEN) inside the block to catch errors and return a controlled result instead of rolling back. See the error handling example for a working demonstration.
Explicit BEGIN, COMMIT, and ROLLBACK statements aren’t allowed inside an atomic block.
The entire body of an Inline Stored Procedure is an atomic block.
Syntax¶
CREATE INLINE PROCEDURE¶
Note
When creating an Inline Stored Procedure in SnowSQL or
Snowsight, use $$ as the string literal delimiter around the procedure body.
When writing the procedure body:
- Reference procedure arguments inside SQL statements using a colon prefix:
:arg_name. RETURNS TABLErequires an explicit column list with names and data types. The column types must match the types returned by the query exactly.
CALL¶
To invoke an Inline Stored Procedure, use the CALL command:
Examples¶
The following examples use these hybrid tables:
Insert a row¶
This procedure inserts a new order into the orders table and returns a status string:
Update a row¶
This procedure updates the status of an existing order:
Delete a row¶
This procedure deletes an order by primary key:
Retrieve a row¶
This procedure performs a point lookup and returns the matching row. The column types in
RETURNS TABLE must match the table column types exactly:
Atomic multi-statement updates¶
The procedure body runs as a single atomic unit: if any statement fails, all prior changes in the
same invocation are rolled back. This procedure updates an order’s status and writes an audit
record in one call. Both the UPDATE and the INSERT succeed together, or neither takes effect:
Handle errors¶
This procedure catches a duplicate primary key error and returns a message instead of raising an exception to the caller:
Call from a task¶
You can schedule an Inline Stored Procedure to run on a recurring basis by defining a
Snowflake task whose SQL body is a CALL statement.
This example creates a counter table, defines an Inline Stored Procedure that increments the counter, schedules a task to call the procedure every minute, and shows how to test and resume the task:
Complex multi-table procedure¶
The following example shows a more complex Inline Stored Procedure that reads from three tables,
increments a counter, uses IF / ELSEIF / ELSE branching with nested BEGIN ... END
blocks, and inserts into two tables, all within a single atomic block. If any step fails, every
change is rolled back.
The procedure uses these tables:
The procedure reads the warehouse tax rate, retrieves and increments the district’s next order ID, looks up the customer discount, determines the initial order status based on a priority argument, and then inserts both a new order record and an audit record:
Performance best practices¶
To get the best performance from Inline Stored Procedures, apply the following practices:
-
Enable the operational query performance optimizations. Set the
ENABLE_USE_STABLE_PATHparameter toTRUEon the warehouse that runs your Inline Stored Procedures. This parameter enables the operational query performance improvements for hybrid tables, which optimize the individual statements inside the procedure body: -
Use bind variables (prepared statements). Call the procedure with bound parameters from your application code instead of string-interpolating values into the
CALLstatement. Most Snowflake drivers use bind variables automatically when you use prepared statements. Bind variables allow Snowflake to reuse the compiled plan across invocations, which significantly reduces per-call latency. -
Use a multi-cluster XSMALL warehouse for high concurrency. For high-throughput OLTP workloads, a multi-cluster warehouse with an
XSMALLcluster size delivers the best price-performance. Scale out by increasing the maximum cluster count rather than scaling up to a larger size. -
Benchmark from a client in the same region as your Snowflake deployment. Network latency between the client and Snowflake has a direct impact on observed procedure latency and throughput. Run your benchmark or application from a VM or service in the same cloud region as your Snowflake account to measure the actual performance of Inline Stored Procedures rather than cross-region network overhead.
Observability¶
Both the parent procedure call and its child statements appear in QUERY_HISTORY. Query profiles are also available for Inline Stored Procedures. For more information, see Analyze query profiles for hybrid tables.
Current limitations¶
The following limitations apply to Inline Stored Procedures.
| Limitation | Description | Workaround |
|---|---|---|
| Hybrid tables only | Inline Stored Procedures can only access hybrid tables. Statements that reference standard Snowflake tables, Iceberg tables, or other table types aren’t supported. | Use standard stored procedures for workloads that mix table types. |
| No DDL statements | Statements such as CREATE TABLE, ALTER TABLE, and ALTER SESSION aren’t supported. Because all statements are pre-compiled independently, DDL and metadata-affecting statements would produce incorrect compilation results. | Perform schema changes outside the procedure before invoking it. |
| No dynamic SQL | EXECUTE IMMEDIATE and other dynamic SQL constructs aren’t supported. All statements must be fully formed at compile time. | Rewrite logic using static SQL with conditional branching. |
| No explicit transactions | BEGIN TRANSACTION, COMMIT, and ROLLBACK aren’t allowed inside an Inline Stored Procedure. The procedure body runs as a single atomic block that either commits or rolls back in full. | Use EXCEPTION handlers to manage error cases within the atomic block. |
| No nested stored procedures | Calling another stored procedure from within an Inline Stored Procedure isn’t supported. | Consolidate logic into a single procedure. |
| Supported statement types only | Only SELECT, INSERT, UPDATE, DELETE, and MERGE are supported. Bulk load operations and other statement types aren’t allowed. | Use standard stored procedures or run unsupported statements outside the procedure. |
| No nested atomic blocks | The ATOMIC keyword can only appear on the top-level BEGIN block. Nesting BEGIN ATOMIC inside another BEGIN ATOMIC isn’t allowed. | Use plain BEGIN ... END blocks for control flow grouping inside the outer atomic block. |
| Can’t be called in an open transaction | Inline Stored Procedures can’t be invoked from within an existing transaction. They must be called with autocommit enabled. | Commit or roll back any open transaction before calling an Inline Stored Procedure. |
| No cross-database queries | All tables referenced in the procedure must belong to the same database. | Restructure queries to stay within a single database. |
| No OUT arguments | OUT and OUTPUT parameters aren’t supported. | Return values using RETURN or RETURNS TABLE. |
| No CONTINUE exception handler | The CONTINUE exception handler type isn’t supported. Only EXIT (default) handlers are available. | Use EXIT exception handlers and restructure the control flow accordingly. |
| No caller context | Inline Stored Procedures run with owner’s rights only. Caller’s rights execution isn’t supported. | Design procedures so that the owner role has the necessary privileges. |
| No session variables | Session variables set outside the procedure aren’t accessible inside it. | Pass values as explicit procedure arguments instead. |
| No query ID for nested statements | The query ID of a statement executed inside an Inline Stored Procedure isn’t exposed to the caller. Results are still accessible using CURSOR or RESULTSET. | Use RESULTSET to capture and work with query results inside the procedure. |
| No dynamic context functions | The following dynamic context functions aren’t supported inside Inline Stored Procedures: CURRENT_TIMESTAMP, CURRENT_TIME, CURRENT_DATE, SYSDATE, SYSTIMESTAMP, GETDATE, LOCALTIME, and LOCALTIMESTAMP. | Compute the value in the caller and pass it as an explicit argument to the procedure. |
| No sequences | Queries that use sequences, either explicitly or implicitly (auto incrementing columns), aren’t supported. | Generate the identifier value in the caller and pass it as an explicit argument, or use a different key generation strategy such as a UUID. |