<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Krishna Tangudu</title>
    <description>The latest articles on DEV Community by Krishna Tangudu (@swaroop_krishna_e2f4b83b2).</description>
    <link>https://dev.to/swaroop_krishna_e2f4b83b2</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3814074%2F3d7f150a-da32-48c6-9fda-dd6f74b170e2.jpeg</url>
      <title>DEV Community: Krishna Tangudu</title>
      <link>https://dev.to/swaroop_krishna_e2f4b83b2</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/swaroop_krishna_e2f4b83b2"/>
    <language>en</language>
    <item>
      <title>How a Simple Warehouse Resize Saved Us 11% in Daily Credits While Boosting Performance</title>
      <dc:creator>Krishna Tangudu</dc:creator>
      <pubDate>Thu, 07 May 2026 06:56:45 +0000</pubDate>
      <link>https://dev.to/swaroop_krishna_e2f4b83b2/how-a-simple-warehouse-resize-saved-us-11-in-daily-credits-while-boosting-performance-2753</link>
      <guid>https://dev.to/swaroop_krishna_e2f4b83b2/how-a-simple-warehouse-resize-saved-us-11-in-daily-credits-while-boosting-performance-2753</guid>
      <description>&lt;p&gt;Our team hit a familiar Snowflake paradox: slower ETL runs arrived at the same time as FinOps alerts about rising credits.&lt;/p&gt;

&lt;p&gt;The warehouse in question was &lt;code&gt;WH_ETL_BRONZE_01&lt;/code&gt;, a multi-cluster warehouse dedicated to Bronze layer ingestion and merge workloads. What looked like a simple cost problem turned out to be a workload-isolation and concurrency problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;We started with this setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="n"&gt;WAREHOUSE&lt;/span&gt; &lt;span class="n"&gt;WH_ETL_BRONZE_01&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt;
  &lt;span class="n"&gt;WAREHOUSE_SIZE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'XSMALL'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;MAX_CLUSTER_COUNT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;MIN_CLUSTER_COUNT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;SCALING_POLICY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'STANDARD'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;AUTO_SUSPEND&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;MAX_CONCURRENCY_LEVEL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;-- default behavior&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And we saw both of these at once:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Higher queue times (&lt;code&gt;queued_overload_time&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Higher daily credits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At that point, we were loading &lt;strong&gt;1,500+ tables&lt;/strong&gt; through this single warehouse. That meant heavyweight Bronze MERGE workloads and smaller ingestion/utility queries were all competing in the same execution pool.&lt;/p&gt;

&lt;p&gt;The critical issue was workload mix. Long-running MERGE statements (30 to 60 minutes) were running alongside tiny queries. When too many heavy MERGE statements landed on the same node/cluster, they competed for resources and all slowed down.&lt;/p&gt;

&lt;p&gt;In other words, even with multi-cluster enabled, assignment patterns could create unstable performance if too many heavyweight queries were packed together.&lt;/p&gt;

&lt;p&gt;We also learned we needed &lt;strong&gt;workload separation&lt;/strong&gt;: large-table loads should run in a dedicated warehouse so they do not contend with smaller table loads and operational queries.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We Changed
&lt;/h2&gt;

&lt;p&gt;This was not a single before/after discovery from history. We ran controlled experiments in sequence, while splitting the largest table loads into a separate warehouse path.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 1: Baseline
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="n"&gt;WAREHOUSE&lt;/span&gt; &lt;span class="n"&gt;WH_ETL_BRONZE_01&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt;
  &lt;span class="n"&gt;WAREHOUSE_SIZE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'XSMALL'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;MAX_CLUSTER_COUNT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;MIN_CLUSTER_COUNT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;SCALING_POLICY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'STANDARD'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;AUTO_SUSPEND&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;MAX_CONCURRENCY_LEVEL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Phase 2: First Experiment
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="n"&gt;WAREHOUSE&lt;/span&gt; &lt;span class="n"&gt;WH_ETL_BRONZE_01&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt;
  &lt;span class="n"&gt;WAREHOUSE_SIZE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'XSMALL'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;MAX_CLUSTER_COUNT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;MIN_CLUSTER_COUNT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;SCALING_POLICY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'STANDARD'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;AUTO_SUSPEND&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;MAX_CONCURRENCY_LEVEL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Goal: force scale-out behavior earlier and reduce the chance that many heavy MERGE jobs share the same node.&lt;/p&gt;

&lt;p&gt;At the same time, we moved larger-table processing to a separate warehouse so those jobs would not compete with the remaining 1,500+ table ingestion flow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 3: Final Optimization
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="n"&gt;WAREHOUSE&lt;/span&gt; &lt;span class="n"&gt;WH_ETL_BRONZE_01&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt;
  &lt;span class="n"&gt;WAREHOUSE_SIZE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'SMALL'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;MAX_CLUSTER_COUNT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;MIN_CLUSTER_COUNT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;SCALING_POLICY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'STANDARD'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;AUTO_SUSPEND&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;MAX_CONCURRENCY_LEVEL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This became the best balance for our workload profile.&lt;/p&gt;

&lt;p&gt;Final state for this ETL warehouse path:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;SMALL&lt;/code&gt; size&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MAX_CONCURRENCY_LEVEL = 3&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Large-table workloads isolated to a separate warehouse&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why This Helped
&lt;/h2&gt;

&lt;p&gt;From deeper analysis of the workload behavior:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The number of queries and output volume could look similar across runs, but bytes scanned at table level could still rise.&lt;/li&gt;
&lt;li&gt;Heavy MERGE overlap was a key instability driver.&lt;/li&gt;
&lt;li&gt;On slow runs, many heavy MERGE statements could get assigned to the same node, causing resource contention.&lt;/li&gt;
&lt;li&gt;On fast runs, fewer heavy MERGE statements shared a node, leaving room for short-running queries.&lt;/li&gt;
&lt;li&gt;Isolating large-table workloads reduced cross-workload contention in the primary Bronze ETL warehouse.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Lowering concurrency reduced the tendency to over-pack heavyweight merges. Then moving to &lt;code&gt;SMALL&lt;/code&gt; provided enough per-query resources to cut elapsed time and queueing further. Separating large-table loads into a dedicated warehouse stabilized performance for the rest of the pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuration Summary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Phase&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;Max Clusters&lt;/th&gt;
&lt;th&gt;Max Concurrency&lt;/th&gt;
&lt;th&gt;Intent&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Baseline&lt;/td&gt;
&lt;td&gt;XSMALL&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Initial setup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Experiment&lt;/td&gt;
&lt;td&gt;XSMALL&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Force earlier scale-out / reduce heavy-query packing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Final&lt;/td&gt;
&lt;td&gt;SMALL&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Balance throughput, queueing, and cost&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The two final intentional changes vs baseline were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;WAREHOUSE_SIZE&lt;/code&gt;: &lt;code&gt;XSMALL&lt;/code&gt; -&amp;gt; &lt;code&gt;SMALL&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;MAX_CONCURRENCY_LEVEL&lt;/code&gt;: &lt;code&gt;8&lt;/code&gt; -&amp;gt; &lt;code&gt;3&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How We Measured
&lt;/h2&gt;

&lt;p&gt;We used Snowflake &lt;code&gt;ACCOUNT_USAGE&lt;/code&gt; views for both performance and cost, comparing baseline and final optimization windows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Query Performance (Before/After)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="k"&gt;CASE&lt;/span&gt;
    &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;start_time&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="s1"&gt;'2026-05-05'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'XSMALL Period'&lt;/span&gt;
    &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="s1"&gt;'SMALL Period'&lt;/span&gt;
  &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;period&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total_queries&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total_elapsed_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;avg_elapsed_sec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queued_overload_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;avg_queued_sec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total_elapsed_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;max_elapsed_sec&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;SNOWFLAKE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ACCOUNT_USAGE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;QUERY_HISTORY&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;warehouse_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'WH_ETL_BRONZE_01'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;start_time&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2026-04-30'&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;period&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;period&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Credit Consumption (Daily)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="k"&gt;CASE&lt;/span&gt;
    &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;start_time&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;DATE&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="s1"&gt;'2026-05-05'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'XSMALL Period'&lt;/span&gt;
    &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="s1"&gt;'SMALL Period'&lt;/span&gt;
  &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;period&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;credits_used_compute&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;start_time&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;daily_credits&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;SNOWFLAKE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ACCOUNT_USAGE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WAREHOUSE_METERING_HISTORY&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;warehouse_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'WH_ETL_BRONZE_01'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;start_time&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;DATE&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2026-04-30'&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;period&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;period&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Cost
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Period&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;Daily Credits&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Baseline (5 days)&lt;/td&gt;
&lt;td&gt;XSMALL&lt;/td&gt;
&lt;td&gt;15.12/day&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Optimized (3 days)&lt;/td&gt;
&lt;td&gt;SMALL&lt;/td&gt;
&lt;td&gt;13.50/day&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Daily credits dropped by about &lt;strong&gt;11%&lt;/strong&gt; after upsizing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Performance
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;XSMALL&lt;/th&gt;
&lt;th&gt;SMALL&lt;/th&gt;
&lt;th&gt;Improvement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Avg query time&lt;/td&gt;
&lt;td&gt;26.15s&lt;/td&gt;
&lt;td&gt;24.13s&lt;/td&gt;
&lt;td&gt;8% faster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Max query time&lt;/td&gt;
&lt;td&gt;3,475s&lt;/td&gt;
&lt;td&gt;2,850s&lt;/td&gt;
&lt;td&gt;18% faster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total queue time&lt;/td&gt;
&lt;td&gt;2.20 min&lt;/td&gt;
&lt;td&gt;0.28 min&lt;/td&gt;
&lt;td&gt;87% less queuing&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Note: The intermediate &lt;code&gt;XSMALL&lt;/code&gt; plus concurrency &lt;code&gt;2&lt;/code&gt; phase was used to validate behavior and direction. The published KPI table above compares the stable baseline period against the final tuned period.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Counterintuitive Lesson
&lt;/h2&gt;

&lt;p&gt;In some workloads, a bigger warehouse can cost less.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Faster execution means earlier suspend
&lt;/h3&gt;

&lt;p&gt;Queries completed faster on &lt;code&gt;SMALL&lt;/code&gt;, so the warehouse reached &lt;code&gt;AUTO_SUSPEND = 60&lt;/code&gt; sooner. Less runtime plus less idle overhead translated to fewer daily credits.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Higher concurrency can reduce cluster sprawl
&lt;/h3&gt;

&lt;p&gt;Concurrency must match workload shape. In our case, moving from default &lt;code&gt;8&lt;/code&gt; down to &lt;code&gt;2&lt;/code&gt; first improved isolation for heavy MERGE jobs, then landing at &lt;code&gt;3&lt;/code&gt; with &lt;code&gt;SMALL&lt;/code&gt; gave the right balance of throughput and stability.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Less queueing improves throughput and utilization
&lt;/h3&gt;

&lt;p&gt;With 87% less queue time, work finished in tighter windows. The warehouse did useful work and went to sleep sooner.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Do not assume smaller is always cheaper.&lt;/li&gt;
&lt;li&gt;Monitor &lt;code&gt;queued_overload_time&lt;/code&gt; closely. Persistent queueing often means you are under-sized or under-concurrented.&lt;/li&gt;
&lt;li&gt;Tune &lt;code&gt;MAX_CONCURRENCY_LEVEL&lt;/code&gt; with warehouse size and workload type; they should be optimized together.&lt;/li&gt;
&lt;li&gt;Isolate heavyweight large-table workflows into a dedicated warehouse to reduce overlap and contention.&lt;/li&gt;
&lt;li&gt;Use both &lt;code&gt;QUERY_HISTORY&lt;/code&gt; (performance) and &lt;code&gt;WAREHOUSE_METERING_HISTORY&lt;/code&gt; (cost) for before/after decisions.&lt;/li&gt;
&lt;li&gt;Keep aggressive auto-suspend for bursty workloads. Faster queries plus short suspend windows compound savings.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Closing Thought
&lt;/h2&gt;

&lt;p&gt;Warehouse right-sizing is a performance and FinOps exercise, not just a cost-control exercise. In many real workloads, a slightly larger warehouse with slightly higher concurrency can win on both speed and spend.&lt;/p&gt;

&lt;h2&gt;
  
  
  Disclosure and Scope
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Results reflect our specific environment and workload profile; outcomes may vary.&lt;/li&gt;
&lt;/ul&gt;




</description>
      <category>snowflake</category>
      <category>dataengineering</category>
      <category>finops</category>
      <category>optimization</category>
    </item>
    <item>
      <title>From Glue to Horizon: Our Real Journey Building an Iceberg Lakehouse on Snowflake</title>
      <dc:creator>Krishna Tangudu</dc:creator>
      <pubDate>Mon, 04 May 2026 14:18:45 +0000</pubDate>
      <link>https://dev.to/swaroop_krishna_e2f4b83b2/iceberg-lakehouse-on-snowflake-journey-3ln4</link>
      <guid>https://dev.to/swaroop_krishna_e2f4b83b2/iceberg-lakehouse-on-snowflake-journey-3ln4</guid>
      <description>&lt;p&gt;We set out to build an open lakehouse: Iceberg tables on AWS S3, Spark/Glue for pipelines, Snowflake for analytics compute power. What could go wrong? Everything—from query performance implosions to uncloneable dynamic tables. Here's the unfiltered journey, including why we pivoted to Snowflake-managed Iceberg via Horizon Catalog and abandoned automated dynamic tables for explicit, observable incremental processing.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;❌ &lt;strong&gt;Phase 1 failed&lt;/strong&gt;: Glue-generated Iceberg files (32-64MB) or bigger size  caused 5-10x slower Snowflake queries&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Phase 2 wins&lt;/strong&gt;: Snowflake-managed Iceberg auto-compacts to 256-512MB, 2-5x faster, ~35% cost savings&lt;/li&gt;
&lt;li&gt;⚠️ &lt;strong&gt;Dynamic Tables gotcha&lt;/strong&gt;: Cannot clone, opaque refresh timing—unusable for production DevOps&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Solution&lt;/strong&gt;: Explicit Streams + Tasks on log tables—boring, debuggable, production-ready&lt;/li&gt;
&lt;li&gt;🎯 &lt;strong&gt;Key decision&lt;/strong&gt;: Data team owns Bronze→Silver only; business owns Gold (saved endless remodeling debates)&lt;/li&gt;
&lt;li&gt;💰 &lt;strong&gt;Reality check&lt;/strong&gt;: "Cloud-neutral" = readable across engines, not free migration&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Our Team Context (Yours Will Differ)
&lt;/h2&gt;

&lt;p&gt;Before diving into architecture decisions, here's who we are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Skills&lt;/strong&gt;: Strong SQL/dbt, limited Spark/Scala experience&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Priorities&lt;/strong&gt;: Ship fast, avoid operational black boxes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Constraint&lt;/strong&gt;: No dedicated DevOps for Glue cluster tuning&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;This shaped every decision below.&lt;/strong&gt; A team fluent in Spark would have made different trade-offs.&lt;/p&gt;




&lt;h2&gt;
  
  
  🎯 The Goal
&lt;/h2&gt;

&lt;p&gt;Unify data from SAP HANA (change data capture), Salesforce Data Cloud, and raw event streams into a single &lt;strong&gt;cloud-neutral lakehouse&lt;/strong&gt;—no proprietary lock-in, full cross-tool interoperability.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Iceberg?
&lt;/h3&gt;

&lt;p&gt;We evaluated Delta Lake, Apache Hudi, and Apache Iceberg a &lt;strong&gt;couple of years ago&lt;/strong&gt;. Here's the comprehensive comparison that drove our decision:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Criterion&lt;/th&gt;
&lt;th&gt;Iceberg&lt;/th&gt;
&lt;th&gt;Delta Lake&lt;/th&gt;
&lt;th&gt;Hudi&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Partition Evolution&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ &lt;strong&gt;Change without rewrite&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;❌ Requires full table rewrite&lt;/td&gt;
&lt;td&gt;❌ Not supported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Primary Keys&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Native support&lt;/td&gt;
&lt;td&gt;❌ Not supported&lt;/td&gt;
&lt;td&gt;✅ Supported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Automated Compaction&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ &lt;strong&gt;MAINTAIN ICEBERG TABLE&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;⚠️ Manual (OPTIMIZE tuning)&lt;/td&gt;
&lt;td&gt;⚠️ Manual/semi-auto&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Schema Evolution&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Full (add/drop/rename/reorder)&lt;/td&gt;
&lt;td&gt;✅ Add/drop columns&lt;/td&gt;
&lt;td&gt;⚠️ Limited&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Engine Compatibility&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Spark, Trino, Flink, Snowflake, Dremio&lt;/td&gt;
&lt;td&gt;⚠️ Spark-first, limited others&lt;/td&gt;
&lt;td&gt;⚠️ Spark-first&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Platform Support&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ AWS Glue/Athena, Azure, GCP, Snowflake&lt;/td&gt;
&lt;td&gt;✅ AWS, Azure (Fabric native), Databricks&lt;/td&gt;
&lt;td&gt;⚠️ AWS, limited Azure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;File Format Flexibility&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Parquet, ORC, Avro&lt;/td&gt;
&lt;td&gt;⚠️ Parquet only&lt;/td&gt;
&lt;td&gt;✅ Parquet, ORC, Avro&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Community Governance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Apache Foundation (vendor-neutral)&lt;/td&gt;
&lt;td&gt;⚠️ Databricks-controlled&lt;/td&gt;
&lt;td&gt;✅ Apache Foundation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Why We Chose Iceberg (and Ruled Out Delta/Hudi)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Iceberg's winning factors for our context:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Partition evolution without rewrites&lt;/strong&gt;: Our SAP data's partitioning strategy evolved over time (daily → monthly as data matured). Iceberg lets us change partition specs without rewriting billions of rows. Delta requires full table rewrite—a multi-day, multi-TB operation we couldn't afford.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Snowflake-managed support&lt;/strong&gt;: Only Iceberg offers native managed tables in Snowflake Horizon Catalog with automatic compaction. Delta/Hudi would lock us into external table limitations with the Phase 1 performance issues.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Automated compaction&lt;/strong&gt;: Snowflake's &lt;code&gt;MAINTAIN ICEBERG TABLE&lt;/code&gt; handles file optimization automatically. Delta requires manual compaction tuning in Spark—expertise our SQL-first team doesn't have.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Primary key enforcement&lt;/strong&gt;: Iceberg supports primary keys natively, critical for our SAP source data integrity (customer IDs, order numbers). Delta lacks this—you must enforce it in application logic.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Vendor-neutral governance&lt;/strong&gt;: Apache Foundation stewardship means no single vendor controls the spec. Delta's governance is tied to Databricks' business interests.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Bottom line&lt;/strong&gt;: Iceberg was the &lt;strong&gt;safe bet for multi-engine flexibility&lt;/strong&gt; without vendor lock-in. If you're Azure-only with Fabric, Delta is pragmatic. If you're Databricks-native, Delta is the path of least resistance. But for AWS + Snowflake + future optionality, Iceberg was the only choice.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Snowflake for Compute?
&lt;/h3&gt;

&lt;p&gt;Three differentiators that closed the decision:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Separation of Storage &amp;amp; Compute&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Scale workloads independently to meet business demands while enabling detailed chargeback per team or domain—without disrupting other workloads. → &lt;a href="https://dev.to/swaroop_krishna_e2f4b83b2/understanding-snowflake-virtual-warehouses-4p5l"&gt;Deep dive: Understanding Snowflake Virtual Warehouses&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Automatic Caching + Smart Pruning (RELY Operators)&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Sub-second query performance on petabyte-scale data through intelligent result/metadata caching and constraint-based optimization. → &lt;a href="https://dev.to/swaroop_krishna_e2f4b83b2/how-snowflakes-rely-constraint-supercharges-your-star-schema-queries-3n4f"&gt;Deep dive: RELY Constraint for Star-Schema Queries&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Compute Billing Precision&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;POC result&lt;/strong&gt;: Our thousands of BI dashboards were only charged for calculation time—not data transfer to BI tools. &lt;strong&gt;Estimated 40–60% cost savings&lt;/strong&gt; vs. **competitors **billing full query duration.&lt;/p&gt;


&lt;h2&gt;
  
  
  🏛️ Medallion Architecture
&lt;/h2&gt;

&lt;p&gt;Our lakehouse follows the classic three-layer medallion model, tailored for SAP source systems:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────────────────────────┐
│                    DATA SOURCES                             │
│   SAP HANA (CDC)  │  Salesforce Data Cloud  │  Raw Streams │
└────────────┬──────────────────┬─────────────────────┬───────┘
             │                  │                     │
             ▼                  ▼                     ▼
┌─────────────────────────────────────────────────────────────┐
│  🥉 BRONZE LAYER — AWS Glue Spark → Iceberg writes to S3   │
│  Raw ingestion, no transformation, full fidelity            │
└─────────────────────────┬───────────────────────────────────┘
                          │
                          ▼
┌─────────────────────────────────────────────────────────────┐
│  🥈 SILVER LAYER — Iceberg Tables (AWS-hosted, S3)         │
│  Matches source table structures                            │
│  e.g., KNA1 (Customers), MARA (Materials), VBAK (Orders)   │
└─────────────────────────┬───────────────────────────────────┘
                          │  Stream on log_table
                          ▼
┌─────────────────────────────────────────────────────────────┐
│  🥇 GOLD LAYER — Snowflake-Managed Iceberg (Horizon Cat.)  │
│  Proper dimensions &amp;amp; facts with business names             │
│  e.g., DIM_CUSTOMER, FACT_SALES_ORDER, DIM_PRODUCT         │
└─────────────────────────┬───────────────────────────────────┘
                          │
                          ▼
              BI Tools / ML / Fabric Export
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Sounds ideal?&lt;/strong&gt; Early reality: Snowflake choked on our Glue-generated files.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Architecture Decision Nobody Talks About: Layer Ownership
&lt;/h2&gt;

&lt;p&gt;Here's what we learned the hard way: &lt;strong&gt;Don't own Gold if you don't have to.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why this boundary matters:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Silver = Source truth&lt;/strong&gt;: Matches SAP table structures (KNA1, MARA, VBAK). Data engineering controls quality, structure, and change tracking.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gold = Business semantics&lt;/strong&gt;: DIM_CUSTOMER, FACT_SALES_ORDER. Business teams decide how to model, aggregate, and interpret.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Boundary = Contract&lt;/strong&gt;: Silver provides clean, change-tracked source data with explicit SLAs; business teams own downstream transformations.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  This Decision Saved Us From:
&lt;/h3&gt;

&lt;p&gt;❌ Endless "why did the customer count change?" debates (business definition shifts, not data quality issues)&lt;br&gt;&lt;br&gt;
❌ Remodeling dimensions every quarter when business logic evolves&lt;br&gt;&lt;br&gt;
❌ Being the bottleneck for every dashboard request&lt;br&gt;&lt;br&gt;
❌ Owning interpretations of business rules we don't fully understand  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your team may differ&lt;/strong&gt;, but &lt;strong&gt;define the ownership boundary early&lt;/strong&gt; or you'll own every downstream interpretation forever. The tools (dbt, Iceberg, Snowflake) don't enforce this—you must.&lt;/p&gt;


&lt;h2&gt;
  
  
  Phase 1: Glue-Managed Iceberg + Snowflake External Tables ❌
&lt;/h2&gt;
&lt;h3&gt;
  
  
  The Performance Trap
&lt;/h3&gt;

&lt;p&gt;We started simple: Spark jobs in Glue created Iceberg tables stored in Glue Catalog; Snowflake linked them as external Iceberg tables.&lt;/p&gt;
&lt;h3&gt;
  
  
  What Broke: File Size Mismatch
&lt;/h3&gt;

&lt;p&gt;Snowflake's Iceberg scanner is optimized for specific file characteristics per &lt;a href="https://docs.snowflake.com/en/user-guide/tables-external-intro#general-file-sizing-recommendations" rel="noopener noreferrer"&gt;official recommendations&lt;/a&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Snowflake Recommendation&lt;/th&gt;
&lt;th&gt;What Glue Produced&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;File size&lt;/td&gt;
&lt;td&gt;256 – 512 MB&lt;/td&gt;
&lt;td&gt;32 – 64 MB (many small files)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Row group size&lt;/td&gt;
&lt;td&gt;16 – 256 MB&lt;/td&gt;
&lt;td&gt;&amp;lt; 16 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Row groups per file&lt;/td&gt;
&lt;td&gt;Multiple (for parallelism)&lt;/td&gt;
&lt;td&gt;Often 1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The result&lt;/strong&gt;: Full table scans instead of pruned reads, query times &lt;strong&gt;5–10x slower&lt;/strong&gt; than expected.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────────────────────────────────────────────────┐
│              PHASE 1 ARCHITECTURE                    │
│                                                      │
│  AWS Glue Spark ──writes──▶ Iceberg (Glue Catalog)  │
│                                    │                 │
│                             S3 Parquet files         │
│                          (small, fragmented)         │
│                                    │                 │
│  Snowflake ◀──external table──────┘                 │
│  (slow scans, no auto-compaction)                    │
└──────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🔀 Decision Point: Invest in Spark Tuning or Snowflake-Managed?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Option A&lt;/strong&gt;: Hire Spark expertise, tune file compaction settings  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Timeline: 3-6 months
&lt;/li&gt;
&lt;li&gt;Ongoing cost: Maintain Spark expertise, monitor file sizes
&lt;/li&gt;
&lt;li&gt;Risk: Our team lacks Spark internals experience
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Option B&lt;/strong&gt;: Snowflake-managed Iceberg via Horizon Catalog  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Timeline: 2 weeks
&lt;/li&gt;
&lt;li&gt;Ongoing cost: Snowflake MAINTAIN ICEBERG TABLE compute
&lt;/li&gt;
&lt;li&gt;Upside: Handles compaction automatically, team stays in SQL/dbt comfort zone
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;We chose Option B&lt;/strong&gt;: Our team's strength is SQL/dbt, not Spark internals. Let Snowflake handle the file lifecycle.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 2: Snowflake-Managed Iceberg + Horizon Catalog ✅
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Pivot
&lt;/h3&gt;

&lt;p&gt;Snowflake-managed Iceberg tables put Snowflake in charge of the table lifecycle on your S3 bucket—Horizon Catalog governs metadata, access, and interoperability.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────────────────────────────────────────────────────┐
│               PHASE 2 ARCHITECTURE                       │
│                                                          │
│  AWS Glue Spark ──writes──▶ Snowflake Horizon Catalog   │
│                                    │                     │
│                    Horizon manages Iceberg metadata      │
│                    Auto-compaction to 256-512MB files    │
│                                    │                     │
│                             S3 (your bucket)             │
│                          Optimal Parquet layout          │
│                                    │                     │
│  Snowflake ◀──native read─────────┘                     │
│  (2–5x faster, full pruning, cloneable*)                 │
│                                                          │
│  BI Tools ◀── Snowflake compute                         │
│  Glue/Spark ◀── Iceberg open format (bidirectional)     │
└──────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Wins
&lt;/h3&gt;

&lt;p&gt;✅ &lt;strong&gt;2–5x query speedup&lt;/strong&gt; vs. Phase 1 external tables&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Automatic compaction&lt;/strong&gt; to optimal file sizes via &lt;code&gt;MAINTAIN ICEBERG TABLE&lt;/code&gt;&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Bidirectional access&lt;/strong&gt;: Glue/Spark can write, Snowflake reads natively; BI tools use Snowflake compute&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Open format preserved&lt;/strong&gt;: Gold layer exportable to Fabric/Polaris later  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trade-off&lt;/strong&gt;: Snowflake compute is billed for maintenance runs—but total ops cost is lower than Phase 1's slow queries burning warehouse credits.&lt;/p&gt;


&lt;h2&gt;
  
  
  ⚠️ Why We Abandoned Dynamic Tables for Production
&lt;/h2&gt;

&lt;p&gt;Dynamic Tables on Iceberg sounded perfect—zero-code pipelines, automatic refresh. We hit two walls that forced us back to explicit patterns:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problem 1 — No Cloning Support:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- This FAILS silently on dynamic Iceberg tables&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;prod_clone&lt;/span&gt; &lt;span class="n"&gt;CLONE&lt;/span&gt; &lt;span class="n"&gt;prod_db&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- Dynamic Iceberg tables are simply skipped in the clone&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;DB/schema clones skip dynamic Iceberg tables entirely.&lt;/strong&gt; This is a DevOps killer—no dev/test environment parity, no blue-green deploys.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problem 2 — Opaque Refresh Timing:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Incremental refresh latency was unpredictable and nearly impossible to debug for SLA enforcement. Monitoring refresh lag and debugging failures was guesswork with no visibility into what triggered refreshes or why they were delayed.&lt;/p&gt;

&lt;h3&gt;
  
  
  🔀 Trade-off: Zero-Code vs. Zero-Surprise
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Dynamic Tables promise automation but hide:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When refreshes actually run&lt;/li&gt;
&lt;li&gt;What triggered the refresh&lt;/li&gt;
&lt;li&gt;How to debug failures in production at 3am&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Streams + Tasks = More code, but debuggable in production.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architecture decision-makers&lt;/strong&gt;: Optimize for production support, not dev convenience.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resolution&lt;/strong&gt;: Abandoned dynamic tables for Silver → Gold. Back to explicit Streams + Tasks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Our Solution: Log Table Pattern
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="err"&gt;┌─────────────────────────────────────────────────────┐&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;           &lt;span class="n"&gt;SILVER&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;GOLD&lt;/span&gt; &lt;span class="n"&gt;PIPELINE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;Current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;                                                      &lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;  &lt;span class="n"&gt;Silver&lt;/span&gt; &lt;span class="n"&gt;Layer&lt;/span&gt;                                        &lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;     &lt;span class="err"&gt;│&lt;/span&gt;                                               &lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;     &lt;span class="err"&gt;├──▶&lt;/span&gt; &lt;span class="n"&gt;silver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log_table&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tracks&lt;/span&gt; &lt;span class="k"&gt;all&lt;/span&gt; &lt;span class="n"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;      &lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;                &lt;span class="err"&gt;│&lt;/span&gt;                                    &lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;                &lt;span class="err"&gt;▼&lt;/span&gt;                                    &lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;     &lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;STREAM&lt;/span&gt; &lt;span class="n"&gt;log_changes&lt;/span&gt;                       &lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;     &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;silver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log_table&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;          &lt;span class="err"&gt;◀──&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;  &lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;                &lt;span class="err"&gt;│&lt;/span&gt;                                    &lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;                &lt;span class="err"&gt;▼&lt;/span&gt;                                    &lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;     &lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;TASK&lt;/span&gt; &lt;span class="n"&gt;gold_refresh&lt;/span&gt;            &lt;span class="err"&gt;◀──&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;    &lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;       &lt;span class="n"&gt;SCHEDULE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'5 MINUTE'&lt;/span&gt;                         &lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;       &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;MERGE&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;gold&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_360&lt;/span&gt;               &lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;          &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="n"&gt;log_changes&lt;/span&gt; &lt;span class="p"&gt;...;&lt;/span&gt;                     &lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;                &lt;span class="err"&gt;│&lt;/span&gt;                                    &lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;                &lt;span class="err"&gt;▼&lt;/span&gt;                                    &lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;     &lt;span class="n"&gt;Gold&lt;/span&gt; &lt;span class="n"&gt;Layer&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Snowflake&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Managed&lt;/span&gt; &lt;span class="n"&gt;Iceberg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;└─────────────────────────────────────────────────────┘&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- The explicit, cloneable, debuggable pattern&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;STREAM&lt;/span&gt; &lt;span class="n"&gt;log_changes&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;silver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log_table&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;TASK&lt;/span&gt; &lt;span class="n"&gt;gold_refresh&lt;/span&gt;
  &lt;span class="n"&gt;WAREHOUSE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;compute_xs&lt;/span&gt;
  &lt;span class="n"&gt;SCHEDULE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'5 MINUTE'&lt;/span&gt;
  &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="k"&gt;SYSTEM&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;STREAM_HAS_DATA&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'log_changes'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;AS&lt;/span&gt;
  &lt;span class="n"&gt;MERGE&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;gold&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_360&lt;/span&gt; &lt;span class="n"&gt;tgt&lt;/span&gt;
  &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;log_changes&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'INSERT'&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;tgt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;
  &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;MATCHED&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="n"&gt;MATCHED&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="p"&gt;...;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why This Wins:
&lt;/h3&gt;

&lt;p&gt;✅ &lt;strong&gt;Full cloning support&lt;/strong&gt; (dev/prod parity restored)&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Transparent costs&lt;/strong&gt; — every execution logged in TASK_HISTORY&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Debuggable&lt;/strong&gt; — stream offset visible, failures isolated&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Fabric-friendly&lt;/strong&gt; — Gold can be exported as Iceberg/Parquet later&lt;/p&gt;




&lt;h2&gt;
  
  
  🌐 Cross-Platform Reality: Why We Can (and Can't) Pivot
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Current State: AWS + Snowflake, Iceberg on S3
&lt;/h3&gt;

&lt;p&gt;✅ &lt;strong&gt;Open format preserved&lt;/strong&gt;: Can read Iceberg from Spark, Trino, Athena&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Gold exportable&lt;/strong&gt;: Stream to Parquet → Fabric mirroring works&lt;br&gt;&lt;br&gt;
⚠️ &lt;strong&gt;Fabric constraint&lt;/strong&gt;: OneLake wants Delta + same Azure region for zero-copy&lt;br&gt;&lt;br&gt;
⚠️ &lt;strong&gt;Horizon lock-in&lt;/strong&gt;: Snowflake-managed Iceberg metadata tied to Horizon Catalog  &lt;/p&gt;

&lt;h3&gt;
  
  
  What "Cloud-Neutral" Actually Means
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Not&lt;/strong&gt;: "Deploy anywhere tomorrow with zero effort"&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Actually&lt;/strong&gt;: "Readable by multiple engines, movable with effort"&lt;/p&gt;

&lt;h3&gt;
  
  
  If We Had to Migrate to Fabric:
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Migration Effort&lt;/th&gt;
&lt;th&gt;Estimated Timeline&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Bronze → Silver ingestion&lt;/td&gt;
&lt;td&gt;Rewrite Glue jobs to Delta&lt;/td&gt;
&lt;td&gt;2-3 weeks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Silver → Gold dbt models&lt;/td&gt;
&lt;td&gt;Port to Fabric SQL (syntax diffs)&lt;/td&gt;
&lt;td&gt;1-2 months&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gold Iceberg tables&lt;/td&gt;
&lt;td&gt;Export as Parquet, re-create in Fabric Warehouse&lt;/td&gt;
&lt;td&gt;1-2 weeks&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌────────────────────────────────────────────────────────────┐
│                  CATALOG LANDSCAPE                         │
│                                                            │
│  Our Setup: AWS us-east-1                                  │
│  ┌────────────────────────────────────────────────────┐   │
│  │  Snowflake Horizon Catalog (current)               │   │
│  │  + AWS Glue Catalog (bronze/silver ingestion)      │   │
│  └────────────────────────────────────────────────────┘   │
│                                                            │
│  Future Options:                                           │
│  ┌─────────────────┐    ┌──────────────────────────────┐  │
│  │ Polaris Catalog │    │ Microsoft Fabric OneLake      │  │
│  │ (Snowflake SaaS)│    │ (wants Delta; Azure-region   │  │
│  │ Maturing fast   │    │  only for zero-copy)          │  │
│  └─────────────────┘    └──────────────────────────────┘  │
│                                                            │
│  Gold Streams → Parquet export → Fabric compatible ✅      │
└────────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Our Decision:
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;We prioritized Snowflake ecosystem depth over day-1 multi-cloud portability.&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;Iceberg gave us an &lt;strong&gt;exit path, not a free exit&lt;/strong&gt;. For teams needing Fabric OneLake zero-copy, starting with Delta on Azure is the pragmatic choice.&lt;/p&gt;




&lt;h2&gt;
  
  
  💰 Cost Reality Check
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Phase 1 (Glue + External Iceberg):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Glue: costs to write and maintain iceberg tables&lt;/li&gt;
&lt;li&gt;Snowflake query costs: 3x higher due to full scans&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Phase 2 (Snowflake-managed Iceberg):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MAINTAIN ICEBERG TABLE: in warehouse credits&lt;/li&gt;
&lt;li&gt;Query costs: 60% reduction (pruning + caching works correctly)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Net savings: 35% monthly&lt;/strong&gt; vs. Phase 1&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Takeaway&lt;/strong&gt;: Optimize for query performance where your users actually spend time, not just ingestion costs.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔑 Lessons for Your Lakehouse
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. File sizes first
&lt;/h3&gt;

&lt;p&gt;Target 256-512MB Parquets or let Snowflake &lt;code&gt;MAINTAIN ICEBERG TABLE&lt;/code&gt; handle it automatically. Small files kill Snowflake performance.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Clone-test early
&lt;/h3&gt;

&lt;p&gt;Dynamic Iceberg tables are powerful but not clone-safe for databases/schemas. Test your DevOps workflow before committing to production.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Horizon bidirectional is a game-changer
&lt;/h3&gt;

&lt;p&gt;If your team uses both Spark and Snowflake, Horizon gives you the best of both without choosing sides. Write with Spark, read with Snowflake—all on the same Iceberg tables.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Streams &amp;gt; automation black boxes
&lt;/h3&gt;

&lt;p&gt;Explicit Streams + Tasks always win in production ops: observable at 3am, debuggable from logs, cloneable for dev/test.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Phase your migration
&lt;/h3&gt;

&lt;p&gt;Don't try to migrate all layers at once. Start with Gold (highest query frequency), validate performance, then move Silver.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Define ownership boundaries early
&lt;/h3&gt;

&lt;p&gt;Bronze→Silver→Gold isn't just technical layers—it's organizational boundaries. Decide who owns what before the first production table.&lt;/p&gt;




&lt;h2&gt;
  
  
  When to Use What: Decision Matrix
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Your Situation&lt;/th&gt;
&lt;th&gt;Recommendation&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Team strong in Spark, need multi-engine&lt;/td&gt;
&lt;td&gt;Glue-managed Iceberg + External tables&lt;/td&gt;
&lt;td&gt;Keep expertise where it is&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Team SQL-first, Snowflake primary engine&lt;/td&gt;
&lt;td&gt;Snowflake-managed Iceberg&lt;/td&gt;
&lt;td&gt;Let Snowflake handle file lifecycle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Must support Fabric OneLake zero-copy&lt;/td&gt;
&lt;td&gt;Delta Lake on Azure&lt;/td&gt;
&lt;td&gt;Iceberg works but not zero-copy on Fabric&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Need dev/prod clones + SLA guarantees&lt;/td&gt;
&lt;td&gt;Streams + Tasks (avoid Dynamic Tables)&lt;/td&gt;
&lt;td&gt;Observable, debuggable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bronze/Silver only (like us)&lt;/td&gt;
&lt;td&gt;dbt incremental + Developer Toolkit&lt;/td&gt;
&lt;td&gt;Explicit watermark control&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Bottom line&lt;/strong&gt;: Cloud-neutral means readable across tools, not free migration. Choose the platform that matches your team's strengths, use open formats for portability insurance.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next for Us
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Investigate Polaris Catalog or wait for Horizon Catalog for true multi-vendor Iceberg metadata management&lt;/li&gt;
&lt;li&gt;Evaluate cost/performance of streaming directly from Kafka → Snowflake Iceberg&lt;/li&gt;
&lt;li&gt;Explore Iceberg v3 features for enhanced BCDR and CDC capabilities&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🆕 Update: Iceberg v3 Features (March 2026) That Could Change Your Decision
&lt;/h2&gt;

&lt;p&gt;Since our original evaluation, &lt;strong&gt;Snowflake released Apache Iceberg v3 support in public preview (March 2026)&lt;/strong&gt; with capabilities that address several of our pain points and unlock new architectural patterns.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's New in Iceberg v3
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Cross-Region Replication for Snowflake-Managed Tables&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What it enables&lt;/strong&gt;: BCDR failover and replication groups for Iceberg tables across regions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why it matters&lt;/strong&gt;: Previously, our DR strategy required complex Parquet exports. Now, Snowflake-managed Iceberg tables can replicate with full consistency (including row lineage and deletion vectors)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Architecture impact&lt;/strong&gt;: We can now deploy active-passive DR without custom tooling&lt;/li&gt;
&lt;li&gt;📖 &lt;a href="https://docs.snowflake.com/en/user-guide/tables-iceberg-configure-replication" rel="noopener noreferrer"&gt;Replication Config Docs&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. Catalog-Linked Databases&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What it enables&lt;/strong&gt;: Connect to remote Iceberg catalogs (AWS Glue, Polaris, etc.) with automatic namespace discovery and read/write support&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why it matters&lt;/strong&gt;: Our Phase 1 external table limitations are eliminated—we can now write back to Glue Catalog-managed tables from Snowflake&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Architecture impact&lt;/strong&gt;: True bidirectional catalog federation; could unblock our "Glue as ingestion, Snowflake as analytics" hybrid&lt;/li&gt;
&lt;li&gt;📖 &lt;a href="https://docs.snowflake.com/en/sql-reference/sql/create-database-catalog" rel="noopener noreferrer"&gt;CREATE Catalog-Linked Database Docs&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Which Features Would Have Changed Our Decision?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Would have stayed with our choice (Iceberg + Horizon):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;Cross-region replication&lt;/strong&gt; validates our bet on Snowflake-managed Iceberg (Delta still doesn't have this)&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Catalog-linked databases&lt;/strong&gt; eliminate the Phase 1 external table pain without abandoning Glue ingestion&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Bottom Line: Iceberg v3 Strengthens the "Safe Bet"
&lt;/h3&gt;

&lt;p&gt;For SQL-first teams on Snowflake, &lt;strong&gt;Iceberg v3 eliminates the last major operational friction points&lt;/strong&gt; we encountered. The combination of Horizon Catalog + v3 features delivers the "cloud-neutral with vendor optimization" balance we were seeking.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What's your biggest Iceberg-on-Snowflake headache?&lt;/strong&gt; Drop it below 👇&lt;/p&gt;

&lt;h1&gt;
  
  
  Snowflake #ApacheIceberg #Lakehouse #DataEngineering #HorizonCatalog #DataArchitecture #AWS #OpenLakehouse
&lt;/h1&gt;

</description>
      <category>architecture</category>
      <category>aws</category>
      <category>dataengineering</category>
      <category>snowflake</category>
    </item>
    <item>
      <title>Part 4: Clone ++ Parallelization and Production Features</title>
      <dc:creator>Krishna Tangudu</dc:creator>
      <pubDate>Thu, 30 Apr 2026 13:58:45 +0000</pubDate>
      <link>https://dev.to/swaroop_krishna_e2f4b83b2/part-4-parallelization-and-production-features-3b3d</link>
      <guid>https://dev.to/swaroop_krishna_e2f4b83b2/part-4-parallelization-and-production-features-3b3d</guid>
      <description>&lt;p&gt;&lt;strong&gt;Previously:&lt;/strong&gt; In &lt;a href="https://dev.to/swaroop_krishna_e2f4b83b2/part-3-solving-permissions-and-rbac-in-cloned-databases-mo9"&gt;Part 3&lt;/a&gt;, we automated permission management with dynamic RBAC provisioning.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In this post:&lt;/strong&gt; Scale your cloning operations with parallel processing, add resume-from-failure capabilities, implement audit logging, and build production-grade orchestration.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Performance Problem
&lt;/h2&gt;

&lt;p&gt;Our repointing solution works, but doesn't scale:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Sequential processing (6 schemas)&lt;/span&gt;
&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;sp_repoint_schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'dev_db'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'prod_db'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ADMIN'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;-- 5 min&lt;/span&gt;
&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;sp_repoint_schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'dev_db'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'prod_db'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'INTEGRATION'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;     &lt;span class="c1"&gt;-- 8 min&lt;/span&gt;
&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;sp_repoint_schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'dev_db'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'prod_db'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'GOLD'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;       &lt;span class="c1"&gt;-- 12 min&lt;/span&gt;
&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;sp_repoint_schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'dev_db'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'prod_db'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'SILVER'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;            &lt;span class="c1"&gt;-- 10 min&lt;/span&gt;
&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;sp_repoint_schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'dev_db'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'prod_db'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'PLATINUM'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;       &lt;span class="c1"&gt;-- 7 min&lt;/span&gt;
&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;sp_repoint_schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'dev_db'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'prod_db'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ARCHIVE'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;         &lt;span class="c1"&gt;-- 3 min&lt;/span&gt;

&lt;span class="c1"&gt;-- Total: 45 minutes ⏰&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; Each schema blocks the next. We're not using Snowflake's compute parallelism.&lt;/p&gt;




&lt;h2&gt;
  
  
  Solution: ASYNC/AWAIT Pattern
&lt;/h2&gt;

&lt;p&gt;Snowflake's &lt;code&gt;ASYNC&lt;/code&gt; and &lt;code&gt;AWAIT&lt;/code&gt; keywords enable parallel execution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Launch all schemas in parallel&lt;/span&gt;
&lt;span class="n"&gt;ASYNC&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;sp_repoint_schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'dev_db'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'prod_db'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ADMINISTRATION'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="n"&gt;ASYNC&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;sp_repoint_schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'dev_db'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'prod_db'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'INTEGRATION'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="n"&gt;ASYNC&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;sp_repoint_schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'dev_db'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'prod_db'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ANALYTICS'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="n"&gt;ASYNC&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;sp_repoint_schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'dev_db'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'prod_db'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'DATA'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="n"&gt;ASYNC&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;sp_repoint_schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'dev_db'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'prod_db'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'REPORTING'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="n"&gt;ASYNC&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;sp_repoint_schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'dev_db'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'prod_db'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ARCHIVE'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="c1"&gt;-- Wait for all to complete&lt;/span&gt;
&lt;span class="n"&gt;AWAIT&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Result: ~12 minutes (limited by slowest schema)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Speedup:&lt;/strong&gt; 45 minutes → 12 minutes = &lt;strong&gt;73% faster&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Parallel Architecture
&lt;/h2&gt;

&lt;h3&gt;
  
  
  High-Level Flow
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SP_REPOINT_PARALLEL (Orchestrator)
├─ Get all schemas in clone
├─ For each schema:
│  └─ ASYNC (SP_REPOINT_SCHEMA_AND_LOG)
├─ AWAIT ALL
└─ Aggregate results

SP_REPOINT_SCHEMA_AND_LOG (Logging Wrapper)
├─ Call SP_REPOINT_SCHEMA
├─ Capture result
└─ Insert into temp results table

SP_REPOINT_SCHEMA (Worker)
├─ Repoint views
├─ Repoint procedures
├─ Repoint functions
├─ Repoint tasks
└─ Return JSON result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Orchestrator Pattern
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Simplified orchestrator logic&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;PROCEDURE&lt;/span&gt; &lt;span class="n"&gt;sp_clone_repoint_parallel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;clone_db&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;source_db&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;AS&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt;
    &lt;span class="c1"&gt;-- Create temp table for results&lt;/span&gt;
    &lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TEMP&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;temp_repoint_results&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;schema_name&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;result&lt;/span&gt; &lt;span class="n"&gt;VARIANT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;completed_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;-- Get all schemas&lt;/span&gt;
    &lt;span class="n"&gt;LET&lt;/span&gt; &lt;span class="n"&gt;schema_rs&lt;/span&gt; &lt;span class="n"&gt;RESULTSET&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;SCHEMA_NAME&lt;/span&gt; 
        &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;clone_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INFORMATION_SCHEMA&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SCHEMATA&lt;/span&gt; 
        &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;SCHEMA_NAME&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s1"&gt;'INFORMATION_SCHEMA'&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;-- Launch parallel workers&lt;/span&gt;
    &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="n"&gt;rec&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="n"&gt;schema_rs&lt;/span&gt; &lt;span class="k"&gt;DO&lt;/span&gt;
        &lt;span class="n"&gt;ASYNC&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;sp_repoint_schema_and_log&lt;/span&gt;&lt;span class="p"&gt;(:&lt;/span&gt;&lt;span class="n"&gt;clone_db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;source_db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;SCHEMA_NAME&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="k"&gt;FOR&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;-- Wait for all workers&lt;/span&gt;
    &lt;span class="n"&gt;AWAIT&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;-- Aggregate results&lt;/span&gt;
    &lt;span class="n"&gt;LET&lt;/span&gt; &lt;span class="n"&gt;final_result&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;OBJECT_CONSTRUCT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s1"&gt;'parallel_schemas'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="s1"&gt;'total_duration_seconds'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                &lt;span class="n"&gt;DATEDIFF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'second'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;MIN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;completed_at&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;completed_at&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
            &lt;span class="s1"&gt;'schema_results'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ARRAY_AGG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;temp_repoint_results&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;final_result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why temp tables?&lt;/strong&gt; ASYNC procedures can't return values directly. We collect results in a temp table visible to the orchestrator.&lt;/p&gt;

&lt;h3&gt;
  
  
  Usage
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;sp_clone_repoint_parallel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'DEV_PROJECT_DB'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'PRODUCTION_DB'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Result:&lt;/span&gt;
&lt;span class="c1"&gt;-- {&lt;/span&gt;
&lt;span class="c1"&gt;--   "parallel_schemas": 6,&lt;/span&gt;
&lt;span class="c1"&gt;--   "total_duration_seconds": 720,  -- 12 minutes&lt;/span&gt;
&lt;span class="c1"&gt;--   "schema_results": [&lt;/span&gt;
&lt;span class="c1"&gt;--     {"schema": "ADMINISTRATION", "views_fixed": 12, "procedures_fixed": 8},&lt;/span&gt;
&lt;span class="c1"&gt;--     {"schema": "ANALYTICS", "views_fixed": 98, "procedures_fixed": 42},&lt;/span&gt;
&lt;span class="c1"&gt;--     ...&lt;/span&gt;
&lt;span class="c1"&gt;--   ]&lt;/span&gt;
&lt;span class="c1"&gt;-- }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Same Pattern for Streams
&lt;/h2&gt;

&lt;p&gt;Parallel stream recreation follows identical architecture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Orchestrator launches per-schema stream workers&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;PROCEDURE&lt;/span&gt; &lt;span class="n"&gt;sp_clone_recreate_streams_parallel&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;span class="k"&gt;AS&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt;
    &lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TEMP&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;temp_stream_results&lt;/span&gt; &lt;span class="p"&gt;(...);&lt;/span&gt;

    &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;each&lt;/span&gt; &lt;span class="k"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;ASYNC&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;sp_recreate_streams_schema_and_log&lt;/span&gt;&lt;span class="p"&gt;(...));&lt;/span&gt;

    &lt;span class="n"&gt;AWAIT&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="n"&gt;aggregated_results&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Resume-from-Failure: Step-Based Tracking
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Problem
&lt;/h3&gt;

&lt;p&gt;Cloning is multi-step:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Delete old RBAC mappings&lt;/li&gt;
&lt;li&gt;Clone database&lt;/li&gt;
&lt;li&gt;Revoke production grants&lt;/li&gt;
&lt;li&gt;Repoint objects&lt;/li&gt;
&lt;li&gt;Recreate streams&lt;/li&gt;
&lt;li&gt;Create new roles&lt;/li&gt;
&lt;li&gt;Apply RBAC mappings&lt;/li&gt;
&lt;li&gt;Transfer ownership&lt;/li&gt;
&lt;li&gt;Suspend tasks&lt;/li&gt;
&lt;li&gt;Validate clone&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;What happens if Step 5 fails?&lt;/strong&gt; You don't want to start over!&lt;/p&gt;

&lt;h3&gt;
  
  
  The Solution: Step Logging
&lt;/h3&gt;

&lt;p&gt;Track each step in a dedicated table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;clone_step_log&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;log_id&lt;/span&gt; &lt;span class="n"&gt;NUMBER&lt;/span&gt; &lt;span class="n"&gt;AUTOINCREMENT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;audit_id&lt;/span&gt; &lt;span class="n"&gt;NUMBER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;-- Links to clone_audit_log&lt;/span&gt;
    &lt;span class="n"&gt;clone_db&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;step_number&lt;/span&gt; &lt;span class="n"&gt;NUMBER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;step_name&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="s1"&gt;'PENDING'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;-- PENDING, IN_PROGRESS, SUCCESS, FAILED&lt;/span&gt;
    &lt;span class="k"&gt;result&lt;/span&gt; &lt;span class="n"&gt;VARIANT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;started_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;completed_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Logging Pattern
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In master clone procedure&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;executeStep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stepNum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stepName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stepFunction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Log step start&lt;/span&gt;
    &lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;INSERT INTO clone_step_log &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;(audit_id, clone_db, step_number, step_name, status) &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;VALUES (..., &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;stepNum&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;, '&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;stepName&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;', 'IN_PROGRESS')&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Execute the step&lt;/span&gt;
        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;stepFunction&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Log success&lt;/span&gt;
        &lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;UPDATE clone_step_log &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SET status = 'SUCCESS', result = '&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;', &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;    completed_at = CURRENT_TIMESTAMP() &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WHERE step_number = &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;stepNum&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; AND status = 'IN_PROGRESS'&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Log failure&lt;/span&gt;
        &lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;UPDATE clone_step_log &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SET status = 'FAILED', result = OBJECT_CONSTRUCT('error', '&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;'), &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;    completed_at = CURRENT_TIMESTAMP() &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WHERE step_number = &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;stepNum&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; AND status = 'IN_PROGRESS'&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// Re-throw to abort remaining steps&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Resume Logic
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Master procedure signature&lt;/span&gt;
&lt;span class="nx"&gt;CREATE&lt;/span&gt; &lt;span class="nx"&gt;PROCEDURE&lt;/span&gt; &lt;span class="nf"&gt;sp_clone_create_master&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;clone_type&lt;/span&gt; &lt;span class="nx"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;name_part1&lt;/span&gt; &lt;span class="nx"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;name_part2&lt;/span&gt; &lt;span class="nx"&gt;VARCHAR&lt;/span&gt; &lt;span class="nx"&gt;DEFAULT&lt;/span&gt; &lt;span class="nx"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;resume_from_step&lt;/span&gt; &lt;span class="nx"&gt;FLOAT&lt;/span&gt; &lt;span class="nx"&gt;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;  &lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="err"&gt;👈&lt;/span&gt; &lt;span class="nx"&gt;Resume&lt;/span&gt; &lt;span class="nx"&gt;parameter&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// Execution logic&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;steps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;num&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DELETE_RBAC&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;deleteRBACMappings&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;num&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CLONE_DATABASE&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cloneDatabase&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;num&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;REVOKE_GRANTS&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;revokeGrants&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;num&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;REPOINT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;repointParallel&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;num&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;STREAMS&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;recreateStreamsParallel&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;num&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CREATE_ROLES&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;createRoles&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;num&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;APPLY_RBAC&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;applyRBAC&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;num&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ICEBERG&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;handleIceberg&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;num&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SUSPEND_TASKS&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;suspendTasks&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;num&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;VALIDATE&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;validateClone&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;num&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;resume_from_step&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// Skip this step&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;executeStep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;num&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Resuming
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Initial attempt fails at step 5&lt;/span&gt;
&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;sp_clone_create_master&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'PROJECT'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ANALYTICS'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'DEV'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;-- ERROR at step 5: Stream recreation failed&lt;/span&gt;

&lt;span class="c1"&gt;-- Check what happened&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;step_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;step_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;result&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;clone_step_log&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;clone_db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'DEV_ANALYTICS_DB'&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;step_number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Fix the issue, then resume from step 5&lt;/span&gt;
&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;sp_clone_create_master&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'PROJECT'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ANALYTICS'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'DEV'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;-- ✅ Steps 1-4 skipped, execution resumes from step 5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key benefit:&lt;/strong&gt; No need to wait another 30 minutes to re-clone. Just fix and resume.&lt;/p&gt;




&lt;h2&gt;
  
  
  Audit Logging: Observability
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Audit Table
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;clone_audit_log&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;audit_id&lt;/span&gt; &lt;span class="n"&gt;NUMBER&lt;/span&gt; &lt;span class="n"&gt;AUTOINCREMENT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;clone_db&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;clone_type&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;-- PROJECT, RELEASE&lt;/span&gt;
    &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;-- CREATE, DROP, UPDATE&lt;/span&gt;
    &lt;span class="n"&gt;project_name&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;env_name&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                 &lt;span class="c1"&gt;-- DEV, QA, STAGING&lt;/span&gt;
    &lt;span class="n"&gt;source_db&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="s1"&gt;'PRODUCTION_DB'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;-- IN_PROGRESS, SUCCESS, FAILED&lt;/span&gt;
    &lt;span class="n"&gt;error_msg&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4000&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;created_by&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;CURRENT_USER&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;completed_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Key Metrics
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Clone success rate (last 30 days)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; 
    &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total_clones&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'SUCCESS'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;successful&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;successful&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;total_clones&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;success_rate&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;clone_audit_log&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;DATEADD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'day'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'CREATE'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Average duration by environment&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; 
    &lt;span class="n"&gt;env_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DATEDIFF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'minute'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;completed_at&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;avg_minutes&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;clone_audit_log&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'SUCCESS'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;completed_at&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;env_name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Most common failure points&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; 
    &lt;span class="n"&gt;step_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;failure_count&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;clone_step_log&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'FAILED'&lt;/span&gt;
&lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;started_at&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;DATEADD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'day'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;step_name&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;failure_count&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Task Suspension: Cost Control
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Problem
&lt;/h3&gt;

&lt;p&gt;Cloned databases inherit &lt;strong&gt;active tasks&lt;/strong&gt; from production:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SHOW&lt;/span&gt; &lt;span class="n"&gt;TASKS&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;dev_project_db&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- Result: 23 tasks, STATE = 'started' ⚠️&lt;/span&gt;
&lt;span class="c1"&gt;-- Running hourly, daily, etc. in DEV!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Cost:&lt;/strong&gt; $200-500/month per clone in wasted compute.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Solution
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Auto-suspend all tasks in clone&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;suspendTasks&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;schemas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getAllSchemas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cloneDb&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;tasksSuspended&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;tasks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SHOW TASKS IN SCHEMA &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt; &lt;span class="nx"&gt;task&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;started&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ALTER TASK &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; SUSPEND&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="nx"&gt;tasksSuspended&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;tasks_suspended&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tasksSuspended&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Integration:&lt;/strong&gt; Add as Step 9 in clone pipeline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; All tasks suspended by default in non-prod clones.&lt;/p&gt;




&lt;h2&gt;
  
  
  Iceberg Table Handling
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Auto-Grant Volume Access
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Step 8: Handle Iceberg tables&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleIcebergTables&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;icebergTables&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT DISTINCT external_volume &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FROM information_schema.tables &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WHERE table_type IN ('ICEBERG TABLE', 'DYNAMIC ICEBERG TABLE') &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AND external_volume IS NOT NULL&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt; &lt;span class="nx"&gt;volume&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;icebergTables&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GRANT USAGE ON EXTERNAL VOLUME &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;volume&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; TO DATABASE &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;cloneDb&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;volume_grants&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;volume_grants_applied&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;volume_grants&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Master Orchestration
&lt;/h2&gt;

&lt;p&gt;Bringing it all together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- One command to rule them all&lt;/span&gt;
&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;sp_clone_create_master&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'PROJECT'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ANALYTICS'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'DEV'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Behind the scenes:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Step 1: DELETE_RBAC_MAPPINGS      [3 sec]
Step 2: CLONE_DATABASE             [3 sec]  ← Snowflake native
Step 3: REVOKE_GRANTS              [45 sec]
Step 4: REPOINT_PARALLEL           [8 min]  ← ASYNC/AWAIT
Step 5: RECREATE_STREAMS_PARALLEL  [2 min]  ← ASYNC/AWAIT
Step 6: CREATE_ROLES               [1 min]
Step 7: APPLY_RBAC_MAPPINGS        [15 sec]
Step 8: HANDLE_ICEBERG             [5 sec]
Step 9: SUSPEND_TASKS              [10 sec]
Step 10: VALIDATE                  [30 sec]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Total: ~12 minutes

Result:
✅ Fully functional dev environment
✅ Correct permissions
✅ All references updated
✅ Streams recreated
✅ Tasks suspended
✅ Iceberg configured
✅ Validated and ready
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Performance Tuning Tips
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Warehouse Sizing
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Use larger warehouse for parallel operations&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="n"&gt;WAREHOUSE&lt;/span&gt; &lt;span class="n"&gt;clone_wh&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;WAREHOUSE_SIZE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'MEDIUM'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- After clone completes&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="n"&gt;WAREHOUSE&lt;/span&gt; &lt;span class="n"&gt;clone_wh&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;WAREHOUSE_SIZE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'SMALL'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Batch Processing
&lt;/h3&gt;

&lt;p&gt;For 50+ schemas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Process in batches to avoid overwhelming warehouse&lt;/span&gt;
&lt;span class="c1"&gt;-- Batch 1: Schemas 1-10&lt;/span&gt;
&lt;span class="c1"&gt;-- Batch 2: Schemas 11-20&lt;/span&gt;
&lt;span class="c1"&gt;-- etc.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Query Optimization
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- ❌ Scans entire database&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;information_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;views&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;view_definition&lt;/span&gt; &lt;span class="k"&gt;ILIKE&lt;/span&gt; &lt;span class="s1"&gt;'%prod_db%'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- ✅ Filter by schema first&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;information_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;views&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;table_schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'ANALYTICS'&lt;/span&gt;
&lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;view_definition&lt;/span&gt; &lt;span class="k"&gt;ILIKE&lt;/span&gt; &lt;span class="s1"&gt;'%prod_db%'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Production Metrics: The Full Picture
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Manual&lt;/th&gt;
&lt;th&gt;Semi-Auto&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Fully Automated&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Time to clone&lt;/td&gt;
&lt;td&gt;1-2 days&lt;/td&gt;
&lt;td&gt;4-6 hours&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;8-12 minutes&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Human intervention&lt;/td&gt;
&lt;td&gt;Constant&lt;/td&gt;
&lt;td&gt;Occasional&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;None&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Error rate&lt;/td&gt;
&lt;td&gt;15-20%&lt;/td&gt;
&lt;td&gt;5-8%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&amp;lt;1%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Concurrent clones&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2-3&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;10+&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Resume capability&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Full&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost per clone&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Low&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Audit trail&lt;/td&gt;
&lt;td&gt;Manual&lt;/td&gt;
&lt;td&gt;Basic&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Complete&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Parallelization is essential&lt;/strong&gt; - ASYNC/AWAIT delivers 73% speedup&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resume-from-failure saves hours&lt;/strong&gt; - Step tracking enables smart recovery&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Observability matters&lt;/strong&gt; - Audit logs provide accountability and insights&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Task suspension prevents waste&lt;/strong&gt; - Auto-suspend saves $200-500/month per clone&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Iceberg needs attention&lt;/strong&gt; - External volumes and dynamic tables require special handling&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  What We've Built
&lt;/h2&gt;

&lt;p&gt;Over this 4-part series, we created a production-grade cloning solution:&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Handles permissions&lt;/strong&gt; - Dynamic RBAC provisioning&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Repoints references&lt;/strong&gt; - All object types updated&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Recreates streams&lt;/strong&gt; - With correct offsets&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Processes in parallel&lt;/strong&gt; - 73% faster&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Resumes from failure&lt;/strong&gt; - No starting over&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Logs everything&lt;/strong&gt; - Complete observability&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Suspends tasks&lt;/strong&gt; - Cost control&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Handles Iceberg&lt;/strong&gt; - External volumes and dynamic tables&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Validates results&lt;/strong&gt; - Health checks  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One command:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;sp_clone_create_master&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'PROJECT'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'myproject'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'DEV'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; Fully functional dev environment in ~8 minutes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Going Further
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Clone Scheduling
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Weekly QA refresh&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;TASK&lt;/span&gt; &lt;span class="n"&gt;refresh_qa_clone&lt;/span&gt;
  &lt;span class="n"&gt;SCHEDULE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'USING CRON 0 6 * * 1 America/Los_Angeles'&lt;/span&gt;
&lt;span class="k"&gt;AS&lt;/span&gt;
  &lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;sp_clone_update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'PROJECT'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'myproject'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'QA'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Self-Service UI
&lt;/h3&gt;

&lt;p&gt;Build a web interface for teams to request/manage clones.&lt;/p&gt;

&lt;h3&gt;
  
  
  Data Masking
&lt;/h3&gt;

&lt;p&gt;Apply dynamic masking policies after cloning for PII protection.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cost Tracking
&lt;/h3&gt;

&lt;p&gt;Tag clones with cost centers for chargeback.&lt;/p&gt;

&lt;h3&gt;
  
  
  Auto-Expiration
&lt;/h3&gt;

&lt;p&gt;Drop clones after N days to control costs.&lt;/p&gt;




&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Code Repository:&lt;/strong&gt; &lt;a href="https://github.com/LALITHASWAROOPK/snowflake_cloning" rel="noopener noreferrer"&gt;github.com/LALITHASWAROOPK/snowflake_cloning&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Blog Series:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/swaroop_krishna_e2f4b83b2/part-1-understanding-snowflake-cloning-and-why-we-need-clone-4flk"&gt;Part 1: The Problem and the Promise&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/swaroop_krishna_e2f4b83b2/part-2-snowflake-clone-repointing-database-references-and-recreating-streams-3f60"&gt;Part 2: Repointing References and Recreating Streams&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/swaroop_krishna_e2f4b83b2/part-3-solving-permissions-and-rbac-in-cloned-databases-mo9"&gt;Part 3: Solving Permissions and RBAC&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  - Part 4: Parallelization and Production Features (this post)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Did this help?&lt;/strong&gt; Star the repo and share with your team!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Questions?&lt;/strong&gt; Open an issue on &lt;a href="https://github.com/LALITHASWAROOPK/snowflake_cloning" rel="noopener noreferrer"&gt;GitHub &lt;/a&gt;or comment below.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Previous:&lt;/strong&gt; &lt;a href="https://dev.to/swaroop_krishna_e2f4b83b2/part-3-solving-permissions-and-rbac-in-cloned-databases-mo9"&gt;Part 3: Solving Permissions and RBAC in Cloned Databases&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Series Start:&lt;/strong&gt; &lt;a href="https://dev.to/swaroop_krishna_e2f4b83b2/part-1-understanding-snowflake-cloning-and-why-we-need-clone-4flk"&gt;Introduction and Overview&lt;/a&gt;&lt;/p&gt;




</description>
      <category>automation</category>
      <category>database</category>
      <category>sql</category>
      <category>snowflake</category>
    </item>
    <item>
      <title>Part 3: Solving Permissions and RBAC in Cloned Databases</title>
      <dc:creator>Krishna Tangudu</dc:creator>
      <pubDate>Tue, 28 Apr 2026 14:51:22 +0000</pubDate>
      <link>https://dev.to/swaroop_krishna_e2f4b83b2/part-3-solving-permissions-and-rbac-in-cloned-databases-mo9</link>
      <guid>https://dev.to/swaroop_krishna_e2f4b83b2/part-3-solving-permissions-and-rbac-in-cloned-databases-mo9</guid>
      <description>&lt;p&gt;&lt;strong&gt;Previously:&lt;/strong&gt; In &lt;a href="https://dev.to/swaroop_krishna_e2f4b83b2/part-2-snowflake-clone-repointing-database-references-and-recreating-streams-3f60"&gt;Part 2&lt;/a&gt;, we fixed all the broken database references. But even with correct references, you still can't access anything without proper permissions!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In this post:&lt;/strong&gt; Learn how to programmatically manage permissions in cloned databases with dynamic role creation, ownership transfers, and automated RBAC provisioning.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Permission Problem (Recap)
&lt;/h2&gt;

&lt;p&gt;After cloning:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;dev_project_db&lt;/span&gt; &lt;span class="n"&gt;CLONE&lt;/span&gt; &lt;span class="n"&gt;production_db&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SHOW&lt;/span&gt; &lt;span class="n"&gt;GRANTS&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;dev_project_db&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;| privilege | grantee_name   |
|-----------|----------------|
| OWNERSHIP | PROD_ADMIN     |  ⚠️ Wrong environment!
| USAGE     | PROD_READ_ONLY |  ⚠️ Dev needs different roles
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates three immediate problems:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Wrong Roles&lt;/strong&gt; - Production roles shouldn't access dev&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No Access&lt;/strong&gt; - Dev team roles aren't granted anything&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ownership Lock&lt;/strong&gt; - Can't modify without PROD_ADMIN privileges&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Solution: Four-Stage Automation
&lt;/h2&gt;

&lt;p&gt;Our approach automates permission management in four stages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Stage 1: Temporary Ownership Transfer
   ↓
Stage 2: Permission Cleanup
   ↓
Stage 3: Dynamic Role Creation
   ↓
Stage 4: RBAC Mapping Application
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's dive into each.&lt;/p&gt;




&lt;h2&gt;
  
  
  Stage 1: Temporary Ownership Transfer
&lt;/h2&gt;

&lt;p&gt;First, we need control. Grant temporary ownership to a privileged service account:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Pseudocode for understanding&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;clone_database&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nx"&gt;GRANT&lt;/span&gt; &lt;span class="nx"&gt;OWNERSHIP&lt;/span&gt; &lt;span class="nx"&gt;ON&lt;/span&gt; &lt;span class="nx"&gt;SCHEMA&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;SERVICE_ROLE&lt;/span&gt; &lt;span class="nx"&gt;COPY&lt;/span&gt; &lt;span class="nx"&gt;CURRENT&lt;/span&gt; &lt;span class="nx"&gt;GRANTS&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt; &lt;span class="nx"&gt;object_type&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;TABLES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;VIEWS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;PROCEDURES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...]:&lt;/span&gt;
        &lt;span class="nx"&gt;GRANT&lt;/span&gt; &lt;span class="nx"&gt;OWNERSHIP&lt;/span&gt; &lt;span class="nx"&gt;ON&lt;/span&gt; &lt;span class="nx"&gt;ALL&lt;/span&gt; &lt;span class="nx"&gt;object_type&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;SERVICE_ROLE&lt;/span&gt; &lt;span class="nx"&gt;COPY&lt;/span&gt; &lt;span class="nx"&gt;CURRENT&lt;/span&gt; &lt;span class="nx"&gt;GRANTS&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key insight:&lt;/strong&gt; &lt;code&gt;COPY CURRENT GRANTS&lt;/code&gt; preserves existing permissions while changing ownership.&lt;/p&gt;

&lt;h3&gt;
  
  
  Usage
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;sp_grant_temp_ownership&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'dev_project_db'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Result:&lt;/span&gt;
&lt;span class="c1"&gt;-- {&lt;/span&gt;
&lt;span class="c1"&gt;--   "schemas_granted": 4,&lt;/span&gt;
&lt;span class="c1"&gt;--   "objects_transferred": 1250,&lt;/span&gt;
&lt;span class="c1"&gt;--   "errors": []&lt;/span&gt;
&lt;span class="c1"&gt;-- }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we have control to make changes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Stage 2: Permission Cleanup
&lt;/h2&gt;

&lt;p&gt;Revoke production-specific grants, especially &lt;strong&gt;future grants&lt;/strong&gt; that auto-apply to new objects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Example: Revoke future ownership from production roles&lt;/span&gt;
&lt;span class="k"&gt;REVOKE&lt;/span&gt; &lt;span class="n"&gt;OWNERSHIP&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;FUTURE&lt;/span&gt; &lt;span class="n"&gt;TABLES&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="n"&gt;dev_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;analytics&lt;/span&gt; 
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="n"&gt;PROD_ANALYTICS_OWNER&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For multiple schemas and object types, we automate this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Pseudocode&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;schemas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nx"&gt;prod_role&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;source_database&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;schema_owner_suffix&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt; &lt;span class="nx"&gt;object_type&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;TABLES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;VIEWS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;PROCEDURES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...]:&lt;/span&gt;
        &lt;span class="nx"&gt;REVOKE&lt;/span&gt; &lt;span class="nx"&gt;OWNERSHIP&lt;/span&gt; &lt;span class="nx"&gt;ON&lt;/span&gt; &lt;span class="nx"&gt;FUTURE&lt;/span&gt; &lt;span class="nx"&gt;object_type&lt;/span&gt; &lt;span class="nx"&gt;IN&lt;/span&gt; &lt;span class="nx"&gt;SCHEMA&lt;/span&gt; 
          &lt;span class="nx"&gt;FROM&lt;/span&gt; &lt;span class="nx"&gt;ROLE&lt;/span&gt; &lt;span class="nx"&gt;prod_role&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Stage 3: Dynamic Role Creation
&lt;/h2&gt;

&lt;p&gt;Instead of hardcoding role names, we generate them dynamically based on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Database name&lt;/strong&gt; (environment-specific)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Schema name&lt;/strong&gt; (domain-specific)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Access level&lt;/strong&gt; (READ, READ_WRITE, ADMIN)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Pattern
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;DATABASE&amp;gt;_&amp;lt;SCHEMA&amp;gt;_&amp;lt;LEVEL&amp;gt;

Examples:
DEV_PROJECT_DB_ANALYTICS_READ
DEV_PROJECT_DB_ANALYTICS_READ_WRITE
DEV_PROJECT_DB_DATA_ADMIN
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Logic
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Simplified version for understanding&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;schemas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt; &lt;span class="nx"&gt;access_level&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;READ&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;READ_WRITE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ADMIN&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="nx"&gt;role_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;clone_db&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;_&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;_&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;access_level&lt;/span&gt;

        &lt;span class="nx"&gt;CREATE&lt;/span&gt; &lt;span class="nx"&gt;ROLE&lt;/span&gt; &lt;span class="nx"&gt;IF&lt;/span&gt; &lt;span class="nx"&gt;NOT&lt;/span&gt; &lt;span class="nx"&gt;EXISTS&lt;/span&gt; &lt;span class="nx"&gt;role_name&lt;/span&gt;
        &lt;span class="nx"&gt;GRANT&lt;/span&gt; &lt;span class="nx"&gt;USAGE&lt;/span&gt; &lt;span class="nx"&gt;ON&lt;/span&gt; &lt;span class="nx"&gt;DATABASE&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;role_name&lt;/span&gt;
        &lt;span class="nx"&gt;GRANT&lt;/span&gt; &lt;span class="nx"&gt;USAGE&lt;/span&gt; &lt;span class="nx"&gt;ON&lt;/span&gt; &lt;span class="nx"&gt;SCHEMA&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;role_name&lt;/span&gt;

        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;access_level&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nx"&gt;READ&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="nx"&gt;GRANT&lt;/span&gt; &lt;span class="nx"&gt;SELECT&lt;/span&gt; &lt;span class="nx"&gt;ON&lt;/span&gt; &lt;span class="nx"&gt;ALL&lt;/span&gt; &lt;span class="nx"&gt;TABLES&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;role_name&lt;/span&gt;
            &lt;span class="nx"&gt;GRANT&lt;/span&gt; &lt;span class="nx"&gt;SELECT&lt;/span&gt; &lt;span class="nx"&gt;ON&lt;/span&gt; &lt;span class="nx"&gt;FUTURE&lt;/span&gt; &lt;span class="nx"&gt;TABLES&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;role_name&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;access_level&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nx"&gt;READ_WRITE&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="nx"&gt;GRANT&lt;/span&gt; &lt;span class="nx"&gt;SELECT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;INSERT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;UPDATE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;DELETE&lt;/span&gt; &lt;span class="nx"&gt;ON&lt;/span&gt; &lt;span class="nx"&gt;ALL&lt;/span&gt; &lt;span class="nx"&gt;TABLES&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;role_name&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;access_level&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nx"&gt;ADMIN&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="nx"&gt;GRANT&lt;/span&gt; &lt;span class="nx"&gt;ALL&lt;/span&gt; &lt;span class="nx"&gt;ON&lt;/span&gt; &lt;span class="nx"&gt;SCHEMA&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;role_name&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this scales:&lt;/strong&gt; From 3 schemas to 300, the pattern stays the same.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Full implementation:&lt;/strong&gt; &lt;a href="//../../sql/04_clone_rbac.sql"&gt;sql/04_clone_rbac.sql#L97-L185&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Example Usage
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;sp_create_clone_roles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;'DEV_PROJECT_DB'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ARRAY_CONSTRUCT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'ADMINISTRATION'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ANALYTICS'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'DATA'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Result:&lt;/span&gt;
&lt;span class="c1"&gt;-- {&lt;/span&gt;
&lt;span class="c1"&gt;--   "roles_created": [&lt;/span&gt;
&lt;span class="c1"&gt;--     "DEV_PROJECT_DB_ADMINISTRATION_READ",&lt;/span&gt;
&lt;span class="c1"&gt;--     "DEV_PROJECT_DB_ADMINISTRATION_READ_WRITE",&lt;/span&gt;
&lt;span class="c1"&gt;--     "DEV_PROJECT_DB_ADMINISTRATION_ADMIN",&lt;/span&gt;
&lt;span class="c1"&gt;--     "DEV_PROJECT_DB_ANALYTICS_READ",&lt;/span&gt;
&lt;span class="c1"&gt;--     "DEV_PROJECT_DB_ANALYTICS_READ_WRITE",&lt;/span&gt;
&lt;span class="c1"&gt;--     "DEV_PROJECT_DB_ANALYTICS_ADMIN",&lt;/span&gt;
&lt;span class="c1"&gt;--     "DEV_PROJECT_DB_DATA_READ",&lt;/span&gt;
&lt;span class="c1"&gt;--     "DEV_PROJECT_DB_DATA_READ_WRITE",&lt;/span&gt;
&lt;span class="c1"&gt;--     "DEV_PROJECT_DB_DATA_ADMIN"&lt;/span&gt;
&lt;span class="c1"&gt;--   ],&lt;/span&gt;
&lt;span class="c1"&gt;--   "total_created": 9&lt;/span&gt;
&lt;span class="c1"&gt;-- }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Stage 4: RBAC Mapping
&lt;/h2&gt;

&lt;p&gt;The final piece: map clone-specific roles to &lt;strong&gt;functional roles&lt;/strong&gt; that users actually have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CLONE ROLE                           FUNCTIONAL ROLE
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
DEV_PROJECT_DB_ANALYTICS_READ   →    DATA_ANALYST_ROLE
DEV_PROJECT_DB_ANALYTICS_WRITE  →    DATA_ENGINEER_ROLE  
DEV_PROJECT_DB_ANALYTICS_ADMIN  →    PROJECT_ADMIN_ROLE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Configuration Table
&lt;/h3&gt;

&lt;p&gt;Store mappings in a table (not hardcoded):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;rbac_mapping&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;schema_role&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;         &lt;span class="c1"&gt;-- Clone-specific role&lt;/span&gt;
    &lt;span class="n"&gt;environment&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;           &lt;span class="c1"&gt;-- DEV, QA, STAGING&lt;/span&gt;
    &lt;span class="n"&gt;functional_role&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;     &lt;span class="c1"&gt;-- User's actual role&lt;/span&gt;
    &lt;span class="k"&gt;operation&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;            &lt;span class="c1"&gt;-- GRANT or REVOKE&lt;/span&gt;
    &lt;span class="n"&gt;execute_indicator&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="s1"&gt;'Y'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Example mappings with placeholder&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;rbac_mapping&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;schema_role&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;functional_role&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt; 
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'{{CLONE_DB}}_ANALYTICS_READ'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'DEV'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'DATA_ANALYST_ROLE'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'GRANT'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'{{CLONE_DB}}_ANALYTICS_WRITE'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'DEV'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'DATA_ENGINEER_ROLE'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'GRANT'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'{{CLONE_DB}}_ANALYTICS_ADMIN'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'DEV'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'PROJECT_ADMIN_ROLE'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'GRANT'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;code&gt;{{CLONE_DB}}&lt;/code&gt; is replaced dynamically at runtime.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Mapping Logic
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Pseudocode&lt;/span&gt;
&lt;span class="nx"&gt;mappings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;FROM&lt;/span&gt; &lt;span class="nx"&gt;rbac_mapping&lt;/span&gt; &lt;span class="nx"&gt;WHERE&lt;/span&gt; &lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;target_env&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt; &lt;span class="nx"&gt;mapping&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;mappings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nx"&gt;schema_role&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mapping&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;schema_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;{{CLONE_DB}}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;actual_clone_db&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nx"&gt;mapping&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;operation&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GRANT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nx"&gt;GRANT&lt;/span&gt; &lt;span class="nx"&gt;ROLE&lt;/span&gt; &lt;span class="nx"&gt;schema_role&lt;/span&gt; &lt;span class="nx"&gt;TO&lt;/span&gt; &lt;span class="nx"&gt;ROLE&lt;/span&gt; &lt;span class="nx"&gt;mapping&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;functional_role&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nx"&gt;REVOKE&lt;/span&gt; &lt;span class="nx"&gt;ROLE&lt;/span&gt; &lt;span class="nx"&gt;schema_role&lt;/span&gt; &lt;span class="nx"&gt;FROM&lt;/span&gt; &lt;span class="nx"&gt;ROLE&lt;/span&gt; &lt;span class="nx"&gt;mapping&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;functional_role&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Full implementation:&lt;/strong&gt; &lt;a href="//../../sql/04_clone_rbac.sql"&gt;sql/04_clone_rbac.sql#L187-L245&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Result
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;sp_apply_rbac_mapping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'DEV_PROJECT_DB'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'DEV'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Now developers can use their normal roles:&lt;/span&gt;
&lt;span class="n"&gt;USE&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="n"&gt;DATA_ANALYST_ROLE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;dev_project_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;analytics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_summary&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;-- ✅ Works!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Putting It All Together
&lt;/h2&gt;

&lt;p&gt;Complete permission setup in one command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;sp_setup_clone_permissions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;clone_db&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'DEV_PROJECT_DB'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;source_db&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'PRODUCTION_DB'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;environment&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'DEV'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Behind the scenes:&lt;/span&gt;
&lt;span class="c1"&gt;-- ✅ Temporary ownership transferred&lt;/span&gt;
&lt;span class="c1"&gt;-- ✅ Production grants revoked&lt;/span&gt;
&lt;span class="c1"&gt;-- ✅ 9 new environment-specific roles created&lt;/span&gt;
&lt;span class="c1"&gt;-- ✅ RBAC mappings applied&lt;/span&gt;
&lt;span class="c1"&gt;-- ✅ Developers have appropriate access&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Key Design Principles
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Configuration Over Code
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- ❌ Hardcoded in procedures&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="n"&gt;dev_analytics_read&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="n"&gt;data_analyst&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- ✅ Configuration-driven&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;rbac_mapping&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(...);&lt;/span&gt;
&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;sp_apply_rbac_mapping&lt;/span&gt;&lt;span class="p"&gt;(...);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Benefits:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Non-developers can manage permissions&lt;/li&gt;
&lt;li&gt;Different mappings per environment&lt;/li&gt;
&lt;li&gt;Audit trail of changes&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Dynamic Role Generation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// This scales from 3 schemas to 300&lt;/span&gt;
&lt;span class="nx"&gt;role_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;database&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;_&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;_&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;access_level&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Temporary Ownership Pattern
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User requests clone
   ↓
Service role takes ownership
   ↓
Service role makes all changes
   ↓
Service role transfers ownership to target roles
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Never&lt;/strong&gt; make permission changes as the requesting user.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Future Grants Are Critical
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- ❌ Only current objects&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="n"&gt;TABLES&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="n"&gt;analytics&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="n"&gt;analyst&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- ✅ Current AND future objects&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="n"&gt;TABLES&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="n"&gt;analytics&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="n"&gt;analyst&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;FUTURE&lt;/span&gt; &lt;span class="n"&gt;TABLES&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="n"&gt;analytics&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="n"&gt;analyst&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Common Pitfalls (And How We Avoid Them)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Pitfall 1: Not Using COPY CURRENT GRANTS
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- ❌ Drops all existing grants during ownership transfer&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="n"&gt;OWNERSHIP&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="n"&gt;my_schema&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="n"&gt;new_owner&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- ✅ Preserves grants during transfer&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="n"&gt;OWNERSHIP&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="n"&gt;my_schema&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="n"&gt;new_owner&lt;/span&gt; &lt;span class="k"&gt;COPY&lt;/span&gt; &lt;span class="k"&gt;CURRENT&lt;/span&gt; &lt;span class="n"&gt;GRANTS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Our code:&lt;/strong&gt; Always uses &lt;code&gt;COPY CURRENT GRANTS&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Pitfall 2: Forgetting Object Types
&lt;/h3&gt;

&lt;p&gt;Don't forget:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;DYNAMIC TABLES&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ICEBERG TABLES&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;EVENT TABLES&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;STAGES&lt;/code&gt;, &lt;code&gt;FILE FORMATS&lt;/code&gt;, &lt;code&gt;SEQUENCES&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Our code:&lt;/strong&gt; Comprehensive object type list in &lt;a href="//../../sql/04_clone_rbac.sql"&gt;&lt;code&gt;sql/04_clone_rbac.sql&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Pitfall 3: Not Handling Missing Roles
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ Fails if role doesn't exist&lt;/span&gt;
&lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GRANT ROLE clone_role TO ROLE functional_role&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ Graceful error handling (in our procedures)&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GRANT ROLE ...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;success_count&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Our code:&lt;/strong&gt; Try-catch blocks around all grant operations&lt;/p&gt;




&lt;h2&gt;
  
  
  Production Metrics
&lt;/h2&gt;

&lt;p&gt;After implementing automated RBAC:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Time to grant permissions&lt;/td&gt;
&lt;td&gt;45-90 min&lt;/td&gt;
&lt;td&gt;&amp;lt; 2 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Permission errors per clone&lt;/td&gt;
&lt;td&gt;8-12&lt;/td&gt;
&lt;td&gt;0-1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Security audit failures&lt;/td&gt;
&lt;td&gt;4/mo&lt;/td&gt;
&lt;td&gt;0/mo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Developer self-service&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Concurrent clone setup&lt;/td&gt;
&lt;td&gt;1 at a time&lt;/td&gt;
&lt;td&gt;10+ parallel&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;We've solved both major challenges:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Database reference repointing (Part 2)&lt;/li&gt;
&lt;li&gt;✅ Permissions and RBAC (Part 3)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But our solution still processes schemas &lt;strong&gt;sequentially&lt;/strong&gt;. In Part 4, we'll make it production-ready with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Parallel processing&lt;/strong&gt; with ASYNC/AWAIT (73% faster)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resume-from-failure&lt;/strong&gt; capabilities&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit logging&lt;/strong&gt; and observability&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Task suspension&lt;/strong&gt; for cost control&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Production-grade&lt;/strong&gt; orchestration&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Next: Part 4: Parallelization and Production Features →&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Previous:&lt;/strong&gt; &lt;a href="https://dev.to/swaroop_krishna_e2f4b83b2/part-2-snowflake-clone-repointing-database-references-and-recreating-streams-3f60"&gt;Part 2: Repointing Database References&lt;/a&gt;  &lt;/p&gt;




&lt;p&gt;&lt;strong&gt;About This Series&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is Part 3 of a 4-part series on production-grade Snowflake database cloning. All code is available in the &lt;a href="https://github.com/LALITHASWAROOPK/snowflake_cloning" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt; with complete documentation and examples.&lt;/p&gt;

</description>
      <category>snowflake</category>
      <category>cloning</category>
      <category>datawarehouse</category>
      <category>lakehouse</category>
    </item>
    <item>
      <title>Part 2: Snowflake Clone++: Repointing Database References and Recreating Streams</title>
      <dc:creator>Krishna Tangudu</dc:creator>
      <pubDate>Fri, 24 Apr 2026 15:40:39 +0000</pubDate>
      <link>https://dev.to/swaroop_krishna_e2f4b83b2/part-2-snowflake-clone-repointing-database-references-and-recreating-streams-3f60</link>
      <guid>https://dev.to/swaroop_krishna_e2f4b83b2/part-2-snowflake-clone-repointing-database-references-and-recreating-streams-3f60</guid>
      <description>&lt;p&gt;&lt;strong&gt;Previously:&lt;/strong&gt; In &lt;a href="https://dev.to/swaroop_krishna_e2f4b83b2/part-1-understanding-snowflake-cloning-and-why-we-need-clone-4flk"&gt;Part 1&lt;/a&gt;, we saw how zero-copy cloning is revolutionary but leaves us with broken references everywhere.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In this post:&lt;/strong&gt; Learn how to find and fix hardcoded database references in views, stored procedures, functions, tasks, and Iceberg tables — plus how to recreate streams that broke during cloning.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Reference Problem
&lt;/h2&gt;

&lt;p&gt;Your cloned database has perfect permissions, but nothing works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Try to query a view in the clone&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;dev_project_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;analytics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_metrics&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Error: Object 'PRODUCTION_DB.SILVER.CUSTOMERS' does not exist&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The view definition is still pointing to production:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;GET_DDL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'VIEW'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'dev_project_db.analytics.customer_metrics'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Result:&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;VIEW&lt;/span&gt; &lt;span class="n"&gt;dev_project_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;analytics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_metrics&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; 
    &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;order_count&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;production_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;silver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customers&lt;/span&gt;  &lt;span class="c1"&gt;-- ⚠️ Wrong database!&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The code was copied exactly as-is.&lt;/strong&gt; Every database reference needs updating.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Challenge: Finding All References
&lt;/h2&gt;

&lt;p&gt;Database references hide everywhere:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Views (The Easy Ones)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;view_definition&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;dev_project_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;information_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;views&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;view_definition&lt;/span&gt; &lt;span class="k"&gt;ILIKE&lt;/span&gt; &lt;span class="s1"&gt;'%production_db%'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- Result: 186 views need fixing&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Stored Procedures (The Tricky Ones)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;procedure_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;procedure_definition&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;dev_project_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;information_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;procedures&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;procedure_definition&lt;/span&gt; &lt;span class="k"&gt;ILIKE&lt;/span&gt; &lt;span class="s1"&gt;'%production_db%'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- Problem: JavaScript, SQL, Python, Scala, Java...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Functions
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;function_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;function_definition&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;dev_project_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;information_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;functions&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;function_definition&lt;/span&gt; &lt;span class="k"&gt;ILIKE&lt;/span&gt; &lt;span class="s1"&gt;'%production_db%'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Tasks (The Sneaky Ones)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;definition&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;dev_project_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;information_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tasks&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;definition&lt;/span&gt; &lt;span class="k"&gt;ILIKE&lt;/span&gt; &lt;span class="s1"&gt;'%production_db%'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- Tasks can call procedures that call views...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Streams (The Broken Ones)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SHOW&lt;/span&gt; &lt;span class="n"&gt;STREAMS&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;dev_project_db&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- Result: Every stream shows STALE = TRUE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Strategy: GET_DDL + String Replacement
&lt;/h2&gt;

&lt;p&gt;Our battle plan:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Identify&lt;/strong&gt; objects with stale references (INFORMATION_SCHEMA)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extract&lt;/strong&gt; full DDL using &lt;code&gt;GET_DDL()&lt;/code&gt; function&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Replace&lt;/strong&gt; all occurrences of source database with clone database&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Execute&lt;/strong&gt; updated DDL to recreate the object&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Simple concept, but devils in the details.&lt;/p&gt;




&lt;h2&gt;
  
  
  Repointing Views
&lt;/h2&gt;

&lt;p&gt;Views are straightforward because their definitions are directly accessible:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Conceptual flow&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;viewRS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT TABLE_NAME, VIEW_DEFINITION &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FROM clone_db.INFORMATION_SCHEMA.VIEWS &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WHERE TABLE_SCHEMA = '&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;' &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AND VIEW_DEFINITION ILIKE '%source_db%'&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;viewRS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;viewDef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;viewRS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getColumnValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Replace database references (case-insensitive)&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;newDef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;viewDef&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sourceDb&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cloneDb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sourceDb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cloneDb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

    &lt;span class="c1"&gt;// Execute updated DDL&lt;/span&gt;
    &lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newDef&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;views_fixed&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this works:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;VIEW_DEFINITION&lt;/code&gt; contains full CREATE statement&lt;/li&gt;
&lt;li&gt;Simple string replacement updates all references&lt;/li&gt;
&lt;li&gt;Re-executing DDL replaces the view atomically&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Full implementation:&lt;/strong&gt; &lt;a href="https://github.com/LALITHASWAROOPK/snowflake_cloning/blob/main/sql/02_clone_repoint.sql" rel="noopener noreferrer"&gt;&lt;code&gt;sql/02_clone_repoint.sql#L75-L130&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Repointing Procedures (The Hard Part)
&lt;/h2&gt;

&lt;p&gt;Procedures are tricky because:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;INFORMATION_SCHEMA truncates long definitions&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Must use &lt;strong&gt;GET_DDL()&lt;/strong&gt; for full text&lt;/li&gt;
&lt;li&gt;Must handle &lt;strong&gt;parameter signatures&lt;/strong&gt; correctly&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The Parameter Signature Problem
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- INFORMATION_SCHEMA shows:&lt;/span&gt;
&lt;span class="n"&gt;PROCEDURE_NAME&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calculate_metrics&lt;/span&gt;
&lt;span class="n"&gt;ARGUMENT_SIGNATURE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start_date&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end_date&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="n"&gt;NUMBER&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;-- But GET_DDL requires type-only signature:&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;GET_DDL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'PROCEDURE'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'schema.calculate_metrics(DATE, DATE, NUMBER)'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;--                                                      ^^^^ Types only!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We must parse signatures to extract just the types.&lt;/p&gt;

&lt;h3&gt;
  
  
  Parsing Logic
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Convert "(start_date DATE, end_date DATE)" &lt;/span&gt;
&lt;span class="c1"&gt;// To: "(DATE, DATE)"&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;stripParamNames&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;sig&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;sig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;()&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;()&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;()&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;types&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;parts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;+/&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// "start_date DATE" → ["start_date", "DATE"]&lt;/span&gt;
        &lt;span class="nx"&gt;types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// Take type part&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Repointing Flow
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Get procedures that reference source database&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;procRS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT PROCEDURE_NAME, ARGUMENT_SIGNATURE &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FROM clone_db.INFORMATION_SCHEMA.PROCEDURES &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WHERE PROCEDURE_DEFINITION ILIKE '%source_db%'&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;procRS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;procName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;procRS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getColumnValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;procSig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;procRS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getColumnValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;typeSig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;stripParamNames&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;procSig&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// Key step!&lt;/span&gt;

    &lt;span class="c1"&gt;// Get full DDL with correct signature&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;ddl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT GET_DDL('PROCEDURE', '&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; 
        &lt;span class="nx"&gt;cloneDb&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;procName&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;typeSig&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;')&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Replace database references&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;newDDL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ddl&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sourceDb&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cloneDb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sourceDb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cloneDb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

    &lt;span class="c1"&gt;// Recreate procedure&lt;/span&gt;
    &lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newDDL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;procedures_fixed&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Full implementation:&lt;/strong&gt; &lt;a href="https://github.com/LALITHASWAROOPK/snowflake_cloning/blob/main/sql/02_clone_repoint.sql" rel="noopener noreferrer"&gt;&lt;code&gt;sql/02_clone_repoint.sql#L132-L195&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Repointing Functions
&lt;/h2&gt;

&lt;p&gt;Functions work exactly like procedures (same signature challenge):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Same pattern as procedures&lt;/span&gt;
&lt;span class="c1"&gt;// 1. Find functions with stale references&lt;/span&gt;
&lt;span class="c1"&gt;// 2. Strip parameter names from signatures&lt;/span&gt;
&lt;span class="c1"&gt;// 3. Get full DDL using GET_DDL('FUNCTION', ...)&lt;/span&gt;
&lt;span class="c1"&gt;// 4. Replace database references&lt;/span&gt;
&lt;span class="c1"&gt;// 5. Execute updated DDL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Full implementation:&lt;/strong&gt; &lt;a href="https://github.com/LALITHASWAROOPK/snowflake_cloning/blob/main/sql/02_clone_repoint.sql" rel="noopener noreferrer"&gt;&lt;code&gt;sql/02_clone_repoint.sql#L197-L250&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Repointing Tasks
&lt;/h2&gt;

&lt;p&gt;Tasks have an additional requirement: &lt;strong&gt;SUSPEND first&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Get tasks that reference source database&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;taskRS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT NAME FROM clone_db.INFORMATION_SCHEMA.TASKS &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WHERE DEFINITION ILIKE '%source_db%'&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;taskRS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;taskName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;taskRS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getColumnValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// IMPORTANT: Suspend before modifying&lt;/span&gt;
    &lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ALTER TASK &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;taskName&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; SUSPEND&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Get DDL, replace references, recreate&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;taskDDL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT GET_DDL('TASK', '&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;taskName&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;')&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;newTaskDDL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;taskDDL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sourceDb&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cloneDb&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newTaskDDL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;tasks_fixed&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// Tasks remain SUSPENDED (intentional for non-prod)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key Point:&lt;/strong&gt; Tasks remain SUSPENDED after repointing. Perfect for dev/test environments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Full implementation:&lt;/strong&gt; &lt;a href="https://github.com/LALITHASWAROOPK/snowflake_cloning/blob/main/sql/02_clone_repoint.sql" rel="noopener noreferrer"&gt;&lt;code&gt;sql/02_clone_repoint.sql#L252-L295&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Recreating Streams (The Special Case)
&lt;/h2&gt;

&lt;p&gt;Streams can't be "repointed" — they must be &lt;strong&gt;dropped and recreated&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Streams Are Different
&lt;/h3&gt;

&lt;p&gt;When you clone:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Production Stream                 Cloned Stream (BROKEN)
  ├─ Tracks: prod_db.data.orders    ├─ Still tracks: prod_db.data.orders ⚠️
  ├─ Offset: Transaction 12,456     ├─ Offset: LOST ⚠️
  └─ Status: Current                └─ Status: STALE ⚠️
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The stream object clones but:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Still tracks the &lt;strong&gt;source table in production&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Loses its &lt;strong&gt;offset position&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Becomes immediately &lt;strong&gt;STALE&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Recreation Flow
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Get all streams in schema&lt;/span&gt;
&lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SHOW STREAMS IN SCHEMA &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;cloneDb&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;streamRS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SELECT "name", "base_tables", "stale" &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;FROM TABLE(RESULT_SCAN(LAST_QUERY_ID()))&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;streamRS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;streamName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;streamRS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getColumnValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Get stream DDL&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;ddl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT GET_DDL('STREAM', '&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; 
        &lt;span class="nx"&gt;cloneDb&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;streamName&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;')&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Replace database references&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;newDDL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ddl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sourceDb&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cloneDb&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Drop and recreate&lt;/span&gt;
    &lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DROP STREAM IF EXISTS &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;streamName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newDDL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;streams_recreated&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Full implementation:&lt;/strong&gt; &lt;a href="https://github.com/LALITHASWAROOPK/snowflake_cloning/blob/main/sql/03_clone_streams.sql" rel="noopener noreferrer"&gt;&lt;code&gt;sql/03_clone_streams.sql#L65-L140&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Important: Stream Offset Behavior
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Original stream (in production)&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;STREAM&lt;/span&gt; &lt;span class="n"&gt;prod_stream&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;production_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- Has been tracking changes for weeks, offset at transaction 12345&lt;/span&gt;

&lt;span class="c1"&gt;-- After recreating in clone&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;STREAM&lt;/span&gt; &lt;span class="n"&gt;dev_stream&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;dev_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- Offset starts at CURRENT_TIMESTAMP (no historical changes)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Implication:&lt;/strong&gt; Cloned streams don't have historical change data. They start fresh.&lt;/p&gt;




&lt;h2&gt;
  
  
  Validation: Ensuring Everything Worked
&lt;/h2&gt;

&lt;p&gt;After repointing, verify the clone is healthy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Check for views still referencing production&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;staleViews&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT TABLE_SCHEMA, TABLE_NAME &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FROM clone_db.INFORMATION_SCHEMA.VIEWS &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WHERE VIEW_DEFINITION ILIKE '%source_db%'&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Check for procedures still referencing production&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;staleProcs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT PROCEDURE_SCHEMA, PROCEDURE_NAME &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FROM clone_db.INFORMATION_SCHEMA.PROCEDURES &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WHERE PROCEDURE_DEFINITION ILIKE '%source_db%'&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Check for stale streams&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;staleStreams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SHOW STREAMS WHERE stale = 'true' &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;OR base_tables ILIKE '%source_db%'&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Generate report&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;staleViews&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;staleProcs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;staleStreams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
        &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PASS&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;WARN&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;stale_views&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;staleViews&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;stale_procedures&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;staleProcs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;stale_streams&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;staleStreams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Full implementation:&lt;/strong&gt; &lt;a href="https://github.com/LALITHASWAROOPK/snowflake_cloning/blob/main/sql/02_clone_repoint.sql" rel="noopener noreferrer"&gt;&lt;code&gt;sql/02_clone_repoint.sql#L297-L365&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Usage
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;sp_validate_clone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'DEV_PROJECT_DB'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'PRODUCTION_DB'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Result:&lt;/span&gt;
&lt;span class="c1"&gt;-- {&lt;/span&gt;
&lt;span class="c1"&gt;--   "status": "PASS",&lt;/span&gt;
&lt;span class="c1"&gt;--   "stale_views": 0,&lt;/span&gt;
&lt;span class="c1"&gt;--   "stale_procedures": 0,&lt;/span&gt;
&lt;span class="c1"&gt;--   "stale_streams": 0&lt;/span&gt;
&lt;span class="c1"&gt;-- }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Performance: Sequential vs Parallel
&lt;/h2&gt;

&lt;p&gt;For large databases with many schemas:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Time for 6 schemas&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Sequential (one at a time)&lt;/td&gt;
&lt;td&gt;~45 minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Parallel (ASYNC/AWAIT)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~8 minutes&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;We'll cover parallelization in detail in Part 4.&lt;/p&gt;




&lt;h2&gt;
  
  
  Common Gotchas
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Case Sensitivity
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ Won't catch lowercase references&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;newDDL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ddl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sourceDb&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cloneDb&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ Handle both cases&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;newDDL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ddl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sourceDb&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cloneDb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sourceDb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cloneDb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Partial Matches
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// If source_db = "PROD"&lt;/span&gt;
&lt;span class="c1"&gt;// This accidentally replaces "PRODUCTION_TABLE" → "DEV_DUCTION_TABLE"&lt;/span&gt;

&lt;span class="c1"&gt;// Solution: Use specific database names or word boundaries&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Hardcoded Strings in Procedures
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// This won't be caught by simple string replacement&lt;/span&gt;
&lt;span class="nx"&gt;CREATE&lt;/span&gt; &lt;span class="nx"&gt;PROCEDURE&lt;/span&gt; &lt;span class="nf"&gt;my_proc&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;AS&lt;/span&gt;
&lt;span class="nx"&gt;$$&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PRODUCTION_DB&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// Hardcoded!&lt;/span&gt;
    &lt;span class="nx"&gt;snowflake&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;sqlText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT * FROM &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.schema.table&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;$$&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Best practice:&lt;/strong&gt; Use parameters or configuration tables, not hardcoded strings.&lt;/p&gt;




&lt;h2&gt;
  
  
  Handling Iceberg Tables
&lt;/h2&gt;

&lt;p&gt;Iceberg tables introduce additional complexity when repointing because they reference external volumes.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Challenge
&lt;/h3&gt;

&lt;p&gt;After cloning, Iceberg tables still point to production external volumes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Check Iceberg table after cloning&lt;/span&gt;
&lt;span class="k"&gt;SHOW&lt;/span&gt; &lt;span class="n"&gt;ICEBERG&lt;/span&gt; &lt;span class="n"&gt;TABLES&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="n"&gt;dev_db&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- EXTERNAL_VOLUME: prod_iceberg_volume ⚠️&lt;/span&gt;

&lt;span class="c1"&gt;-- Try to query&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;dev_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;events_iceberg&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- Error: Database DEV_DB does not have READ access to EXTERNAL VOLUME 'prod_iceberg_volume'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Solution: Grant Volume Access
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// During repoint, identify and grant Iceberg volume access&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;icebergTables&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT DISTINCT external_volume &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FROM clone_db.INFORMATION_SCHEMA.TABLES &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WHERE table_type IN ('ICEBERG TABLE', 'DYNAMIC ICEBERG TABLE') &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AND external_volume IS NOT NULL&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt; &lt;span class="nx"&gt;volume&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;icebergTables&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GRANT READ ON EXTERNAL VOLUME &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;volume&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; TO DATABASE &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;clone_db&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Security consideration:&lt;/strong&gt; Dev environment now has read access to production Iceberg storage. This is usually acceptable for clones since:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;They share the same data anyway (zero-copy)&lt;/li&gt;
&lt;li&gt;Cost tracking separates dev/prod compute&lt;/li&gt;
&lt;li&gt;Any writes are isolated to a dedicated storage location and do not interfere with production data.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Dynamic Iceberg Tables
&lt;/h3&gt;

&lt;p&gt;Dynamic Iceberg tables lose their "dynamic" status after cloning:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Flag dynamic Iceberg tables for manual review&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;dynamicIceberg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT table_schema, table_name &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FROM clone_db.INFORMATION_SCHEMA.TABLES &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WHERE table_type = 'DYNAMIC ICEBERG TABLE'&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Log warning: These tables exist but won't refresh automatically&lt;/span&gt;
&lt;span class="c1"&gt;// Must be recreated or converted to static if needed in clone&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://docs.snowflake.com/en/user-guide/tables-iceberg-manage" rel="noopener noreferrer"&gt;check known limitations here: &lt;br&gt;
&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Production Metrics
&lt;/h2&gt;

&lt;p&gt;After implementing automated repointing:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Before (Manual)&lt;/th&gt;
&lt;th&gt;After (Automated)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Time to repoint 6 schemas&lt;/td&gt;
&lt;td&gt;8-12 hours&lt;/td&gt;
&lt;td&gt;8 minutes (parallel)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Missed references&lt;/td&gt;
&lt;td&gt;10-15 per clone&lt;/td&gt;
&lt;td&gt;0-1 per clone&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Failed views&lt;/td&gt;
&lt;td&gt;5-8&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Human errors&lt;/td&gt;
&lt;td&gt;Several per clone&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Success rate&lt;/td&gt;
&lt;td&gt;80-85%&lt;/td&gt;
&lt;td&gt;99%+&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;We've now solved the most visible problem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Database reference repointing&lt;/li&gt;
&lt;li&gt;✅ Stream recreation&lt;/li&gt;
&lt;li&gt;✅ Iceberg table handling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But even with all references fixed, you &lt;strong&gt;still can't access anything&lt;/strong&gt; without proper permissions! In Part 3, we'll tackle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Permission management&lt;/strong&gt; - Dynamic role creation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RBAC automation&lt;/strong&gt; - Configuration-driven grants&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ownership transfers&lt;/strong&gt; - Breaking free from production roles&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then in Part 4:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Parallel processing&lt;/strong&gt; with ASYNC/AWAIT (73% faster)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resume-from-failure&lt;/strong&gt; capabilities&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Production-grade&lt;/strong&gt; orchestration&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Next:&lt;/strong&gt; Part 3: Solving Permissions and RBAC &lt;br&gt;
&lt;strong&gt;Previous:&lt;/strong&gt; &lt;a href="https://dev.to/swaroop_krishna_e2f4b83b2/part-1-understanding-snowflake-cloning-and-why-we-need-clone-4flk"&gt;Part 1: The Problem and the Promise&lt;/a&gt;  &lt;/p&gt;




&lt;p&gt;&lt;strong&gt;About This Series&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is Part 2 of a 4-part series on production-grade Snowflake database cloning. All code is available in the &lt;a href="https://github.com/LALITHASWAROOPK/snowflake_cloning" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt; with complete documentation and examples.&lt;/p&gt;

</description>
      <category>snowflake</category>
      <category>clone</category>
      <category>zerocopy</category>
      <category>clouddatawarehouse</category>
    </item>
    <item>
      <title>Part 1: Understanding Snowflake Cloning and why we need Clone++</title>
      <dc:creator>Krishna Tangudu</dc:creator>
      <pubDate>Fri, 24 Apr 2026 15:35:20 +0000</pubDate>
      <link>https://dev.to/swaroop_krishna_e2f4b83b2/part-1-understanding-snowflake-cloning-and-why-we-need-clone-4flk</link>
      <guid>https://dev.to/swaroop_krishna_e2f4b83b2/part-1-understanding-snowflake-cloning-and-why-we-need-clone-4flk</guid>
      <description>&lt;h1&gt;
  
  
  Mastering Snowflake Database Cloning: A Production Guide
&lt;/h1&gt;

&lt;p&gt;A 4-part series exploring enterprise-scale Snowflake database cloning with real-world solutions for permissions, parallel processing, and automation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Series?
&lt;/h2&gt;

&lt;p&gt;Snowflake's zero-copy cloning is one of its most powerful features—but getting from a simple &lt;code&gt;CREATE DATABASE ... CLONE&lt;/code&gt; command to a production-grade cloning system requires solving numerous challenges that aren't obvious until you try.&lt;/p&gt;

&lt;p&gt;This series shares battle-tested patterns from managing 20+ database clones .&lt;/p&gt;

&lt;h2&gt;
  
  
  What You'll Learn
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Why simple cloning fails in production environments&lt;/li&gt;
&lt;li&gt;How to handle permissions and RBAC across environments&lt;/li&gt;
&lt;li&gt;Strategies for updating database references at scale&lt;/li&gt;
&lt;li&gt;Parallel processing techniques that deliver 73% performance improvements&lt;/li&gt;
&lt;li&gt;Resume-from-failure capabilities for reliability&lt;/li&gt;
&lt;li&gt;Production-grade observability and cost controls&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Part 1: The Problem and the Promise
&lt;/h1&gt;

&lt;h2&gt;
  
  
  The Zero-Copy Clone Revolution
&lt;/h2&gt;

&lt;p&gt;Picture this: You need a complete copy of your 2TB production database for testing. In traditional databases, this means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hours or days&lt;/strong&gt; of data export/import&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2TB of additional storage&lt;/strong&gt; costs immediately&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expensive backup windows&lt;/strong&gt; impacting production&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stale data&lt;/strong&gt; by the time the copy finishes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Risk&lt;/strong&gt; of impacting production performance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Snowflake changed everything with zero-copy cloning:&lt;br&gt;
&lt;strong&gt;Note:&lt;/strong&gt; This only works within same snowflake account&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;dev_db&lt;/span&gt; &lt;span class="n"&gt;CLONE&lt;/span&gt; &lt;span class="n"&gt;production_db&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Three seconds later&lt;/strong&gt;, you have a complete copy. Let that sink in: &lt;strong&gt;few seconds for 2TB&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Magic: How Zero-Copy Works
&lt;/h2&gt;

&lt;p&gt;Unlike traditional databases that copy data blocks, Snowflake clones share the underlying data files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Production DB                     Dev DB (Clone)
  ├─ Table metadata ───────────────&amp;gt; Table metadata (new)
  ├─ Data files ←──────────────────── Points to same files
  └─ Micro-partitions                 (shared, not copied)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key benefits:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Instant creation&lt;/strong&gt; - Metadata operation, not data copy&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Zero initial storage cost&lt;/strong&gt; - Only pays for changes (deltas)&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;No performance impact&lt;/strong&gt; - No data movement from production&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Perfect point-in-time copy&lt;/strong&gt; - Exact snapshot at clone time&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Independent evolution&lt;/strong&gt; - Changes don't affect each other  &lt;/p&gt;
&lt;h2&gt;
  
  
  Real-World Value
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Use Case 1: Dev/Test Environments
&lt;/h3&gt;

&lt;p&gt;Before Snowflake cloning:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Setup time&lt;/strong&gt;: 2-3 days&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage cost&lt;/strong&gt;: $150/month per environment&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data freshness&lt;/strong&gt;: 1-2 weeks old&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manual effort&lt;/strong&gt;: 6+ hours per refresh&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After Snowflake cloning:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Setup time&lt;/strong&gt;: 8 minutes (automated)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage cost&lt;/strong&gt;: ~$10/month (only deltas)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data freshness&lt;/strong&gt;: Real-time (clone anytime)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manual effort&lt;/strong&gt;: Zero&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Use Case 2: Release Testing
&lt;/h3&gt;

&lt;p&gt;We maintain parallel release environments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Create isolated environment for Q2 release testing&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;release_q2_2026&lt;/span&gt; &lt;span class="n"&gt;CLONE&lt;/span&gt; &lt;span class="n"&gt;production_db&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Test new features without impacting production or other releases&lt;/span&gt;
&lt;span class="c1"&gt;-- Drop when release is complete - zero long-term storage cost&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Use Case 3: Data Science Sandboxes
&lt;/h3&gt;

&lt;p&gt;Data scientists can experiment freely:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Personal sandbox for ML model development&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;ds_sarah_experiment&lt;/span&gt; &lt;span class="n"&gt;CLONE&lt;/span&gt; &lt;span class="n"&gt;production_db&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Try new transformations, test hypotheses, break things&lt;/span&gt;
&lt;span class="c1"&gt;-- Drop when done - total freedom, zero risk&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Use Case 4: Incident Investigation
&lt;/h3&gt;

&lt;p&gt;Production issue at 2 AM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Clone production state at incident time&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;incident_20260424_02am&lt;/span&gt; &lt;span class="n"&gt;CLONE&lt;/span&gt; &lt;span class="n"&gt;production_db&lt;/span&gt; &lt;span class="k"&gt;AT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'2026-04-24 02:00:00'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Investigate in isolated environment&lt;/span&gt;
&lt;span class="c1"&gt;-- No risk of further production impact&lt;/span&gt;
&lt;span class="c1"&gt;-- Preserve exact state for forensics&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Numbers: ROI of Cloning
&lt;/h2&gt;

&lt;p&gt;For a 2TB database, comparing traditional vs Snowflake cloning:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Traditional Approach&lt;/th&gt;
&lt;th&gt;Snowflake Clone&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Initial copy time&lt;/td&gt;
&lt;td&gt;8-24 hours&lt;/td&gt;
&lt;td&gt;3 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Storage cost (month 1)&lt;/td&gt;
&lt;td&gt;$300&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Storage cost (month 3)&lt;/td&gt;
&lt;td&gt;$300&lt;/td&gt;
&lt;td&gt;$45 (15% changed)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Setup automation effort&lt;/td&gt;
&lt;td&gt;High (complex)&lt;/td&gt;
&lt;td&gt;Low (simple SQL)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Refresh frequency&lt;/td&gt;
&lt;td&gt;Weekly&lt;/td&gt;
&lt;td&gt;Daily/on-demand&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Number of concurrent envs&lt;/td&gt;
&lt;td&gt;2-3 (cost prohibitive)&lt;/td&gt;
&lt;td&gt;10+ (economical)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Risk to production&lt;/td&gt;
&lt;td&gt;High (load impact)&lt;/td&gt;
&lt;td&gt;None (metadata only)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Annual savings per clone&lt;/strong&gt;: ~$2,500 in storage + thousands in engineering time&lt;/p&gt;

&lt;h2&gt;
  
  
  The Wake-Up Call: When Reality Hits
&lt;/h2&gt;

&lt;p&gt;So we eagerly created our first production clone:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;dev_project_db&lt;/span&gt; &lt;span class="n"&gt;CLONE&lt;/span&gt; &lt;span class="n"&gt;production_db&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- ✅ Success! (3 seconds)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Excited, we told the dev team their environment was ready.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Five minutes later:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Everything is broken. We can't access anything."&lt;br&gt;&lt;br&gt;
"The views are reading production data!"&lt;br&gt;&lt;br&gt;
"Streams are all stale."&lt;br&gt;&lt;br&gt;
"Why are tasks running in dev?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Three days of troubleshooting&lt;/strong&gt; revealed the harsh truth: Zero-copy cloning is only 10% of the solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem 1: Your "Dev" Database Is Secretly Reading Production
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Check a simple view in the clone&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;GET_DDL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'VIEW'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'dev_project_db.analytics.customer_summary'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;VIEW&lt;/span&gt; &lt;span class="n"&gt;dev_project_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;analytics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_summary&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; 
    &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;order_count&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;production_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;silver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customers&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;        &lt;span class="c1"&gt;-- ⚠️ Still reading PRODUCTION!&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;production_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;silver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;           &lt;span class="c1"&gt;-- ⚠️ Still reading PRODUCTION!&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Views hardcode database names&lt;/li&gt;
&lt;li&gt;Procedures execute against production&lt;/li&gt;
&lt;li&gt;Functions reference production tables&lt;/li&gt;
&lt;li&gt;Tasks trigger production workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Out of 280 views, &lt;strong&gt;186 had hardcoded production references&lt;/strong&gt;. Our "dev" database was actually a production read client.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Impact:&lt;/strong&gt; A developer testing a new ETL procedure almost deleted production data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem 2: Streams Are Dead
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Check stream health&lt;/span&gt;
&lt;span class="k"&gt;SHOW&lt;/span&gt; &lt;span class="n"&gt;STREAMS&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;dev_project_db&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Result: Every stream showed &lt;code&gt;STALE = TRUE&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why?&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Production Stream                 Cloned Stream (BROKEN)
  ├─ Tracks: prod_db.data.orders    ├─ Still tracks: prod_db.data.orders ⚠️
  ├─ Offset: Transaction 12,456     ├─ Offset: LOST ⚠️
  └─ Status: Current                └─ Status: STALE ⚠️
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Streams don't get "repointed" during cloning. They still reference the source table and lose their offset position.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Impact:&lt;/strong&gt; 47 CDC pipelines broken, 3 hours to identify and recreate all streams.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem 3: The Iceberg Table Trap
&lt;/h2&gt;

&lt;p&gt;Our newest challenge: We use &lt;strong&gt;Iceberg tables managed by Snowflake&lt;/strong&gt; for high-volume data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Try to clone database with Iceberg tables&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;dev_project_db&lt;/span&gt; &lt;span class="n"&gt;CLONE&lt;/span&gt; &lt;span class="n"&gt;production_db&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- ✅ Success&lt;/span&gt;

&lt;span class="c1"&gt;-- But check the Iceberg tables&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;dev_project_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;events_iceberg&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- ❌ Error: Cannot access external volume 'prod_iceberg_volume'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The Iceberg gotchas:&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Dynamic Iceberg Tables Don't Clone
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Production has dynamic Iceberg table&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;DYNAMIC&lt;/span&gt; &lt;span class="n"&gt;ICEBERG&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;production_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;streaming_events&lt;/span&gt;
  &lt;span class="n"&gt;TARGET_LAG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'1 minute'&lt;/span&gt;
  &lt;span class="n"&gt;EXTERNAL_VOLUME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'prod_iceberg_volume'&lt;/span&gt;
  &lt;span class="k"&gt;CATALOG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'iceberg_catalog'&lt;/span&gt;
&lt;span class="k"&gt;AS&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;stream_source&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- After cloning&lt;/span&gt;
&lt;span class="k"&gt;SHOW&lt;/span&gt; &lt;span class="k"&gt;DYNAMIC&lt;/span&gt; &lt;span class="n"&gt;TABLES&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="n"&gt;dev_project_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- Result: Table exists but is NOT dynamic anymore ⚠️&lt;/span&gt;
&lt;span class="c1"&gt;-- Must be recreated as dynamic or converted to static&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  External Volumes Need Cross-Database Access
&lt;/h3&gt;

&lt;p&gt;Even static Iceberg tables have a challenge:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Cloned Iceberg table still references production external volume&lt;/span&gt;
&lt;span class="k"&gt;SHOW&lt;/span&gt; &lt;span class="n"&gt;ICEBERG&lt;/span&gt; &lt;span class="n"&gt;TABLES&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="n"&gt;dev_project_db&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- EXTERNAL_VOLUME: prod_iceberg_volume ⚠️&lt;/span&gt;

&lt;span class="c1"&gt;-- Must grant clone access to production volume&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;READ&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;EXTERNAL&lt;/span&gt; &lt;span class="n"&gt;VOLUME&lt;/span&gt; &lt;span class="n"&gt;prod_iceberg_volume&lt;/span&gt; 
  &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;dev_project_db&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Security implication:&lt;/strong&gt; Dev environment now has read access to production Iceberg storage. Not ideal, but necessary unless you:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Copy Iceberg data to dev storage (expensive, slow)&lt;/li&gt;
&lt;li&gt;Create Iceberg tables as regular tables in clones (losesmeta benefits)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Impact:&lt;/strong&gt; 2 additional hours per clone for Iceberg table handling, ongoing security audit concerns.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem 4: Tasks Running Wild and failing
&lt;/h2&gt;

&lt;p&gt;Our production database had 23 scheduled tasks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hourly metric refreshes&lt;/li&gt;
&lt;li&gt;Daily aggregations&lt;/li&gt;
&lt;li&gt;Real-time alert processing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The clone inherited all these tasks—and they started executing!&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SHOW&lt;/span&gt; &lt;span class="n"&gt;TASKS&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;dev_project_db&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- Result: 23 tasks, STATE = 'started' ⚠️&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Consequences:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tasks trying to write to production (errors)&lt;/li&gt;
&lt;li&gt;Unnecessary compute costs (~$240/month per clone)&lt;/li&gt;
&lt;li&gt;Alerts flooding wrong channels&lt;/li&gt;
&lt;li&gt;Confusion about which environment is which&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Impact:&lt;/strong&gt; $240 wasted in first month before we noticed, plus several false production alerts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem 5: Developers Locked Out of Their Own Database
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Check permissions after cloning&lt;/span&gt;
&lt;span class="k"&gt;SHOW&lt;/span&gt; &lt;span class="n"&gt;GRANTS&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;dev_project_db&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;| privilege | grantee_name      |
|-----------|-------------------|
| OWNERSHIP | PROD_ADMIN_ROLE   |  ⚠️ Production role!
| USAGE     | PROD_READ_ONLY    |  ⚠️ Production role!
| MONITOR   | PROD_DBA_ROLE     |  ⚠️ Production role!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clone inherited all production role grants&lt;/li&gt;
&lt;li&gt;Dev team roles aren't granted anything&lt;/li&gt;
&lt;li&gt;Can't modify permissions without production admin role&lt;/li&gt;
&lt;li&gt;Production roles shouldn't exist in dev environment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Impact:&lt;/strong&gt; Developers locked out of their own database for 4 hours while we manually fixed 450+ object permissions.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Cost of "Simple" Cloning
&lt;/h2&gt;

&lt;p&gt;Let's tally what a "3-second clone" actually cost us:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Issue&lt;/th&gt;
&lt;th&gt;Time to Fix&lt;/th&gt;
&lt;th&gt;Compute Cost&lt;/th&gt;
&lt;th&gt;Risk&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Manual permission fixes&lt;/td&gt;
&lt;td&gt;4 hours&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Finding/fixing view references&lt;/td&gt;
&lt;td&gt;6 hours&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Critical&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Troubleshooting procedures&lt;/td&gt;
&lt;td&gt;8 hours&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Critical&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recreating streams&lt;/td&gt;
&lt;td&gt;3 hours&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Disabling errant tasks&lt;/td&gt;
&lt;td&gt;2 hours&lt;/td&gt;
&lt;td&gt;$240&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Iceberg table handling&lt;/td&gt;
&lt;td&gt;2 hours&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Medium (security)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;25 person-hours&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$240&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Multiple critical risks&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;And this was for &lt;strong&gt;one&lt;/strong&gt; clone. We needed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;8 project teams × 3 environments (DEV, QA, STAGING) = 24 clones&lt;/li&gt;
&lt;li&gt;3 release testing environments&lt;/li&gt;
&lt;li&gt;Ad-hoc data science sandboxes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;At scale, manual cloning wasn't sustainable&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does This Happen?
&lt;/h2&gt;

&lt;p&gt;Snowflake's clone operation does exactly what it promises: &lt;strong&gt;physically copy metadata&lt;/strong&gt;. It clones:&lt;/p&gt;

&lt;p&gt;✅ Table structures and data pointers&lt;br&gt;&lt;br&gt;
✅ View definitions (as-is, with all hardcoded references)&lt;br&gt;&lt;br&gt;
✅ Stored procedure code (as-is, with all hardcoded references)&lt;br&gt;&lt;br&gt;
✅ Function definitions (unchanged)&lt;br&gt;&lt;br&gt;
✅ Stream objects (but not their offset state)&lt;br&gt;&lt;br&gt;
✅ Task objects (including schedules and state!)&lt;br&gt;&lt;br&gt;
✅ Grants and ownership (exactly as they were)&lt;br&gt;&lt;br&gt;
✅ Iceberg table metadata (but not dynamic status)  &lt;/p&gt;

&lt;p&gt;It does NOT:&lt;/p&gt;

&lt;p&gt;❌ Rewrite database references in code&lt;br&gt;&lt;br&gt;
❌ Adjust role assignments for target environment&lt;br&gt;&lt;br&gt;
❌ Fix stream offsets or source references&lt;br&gt;&lt;br&gt;
❌ Suspend tasks for non-prod use&lt;br&gt;&lt;br&gt;
❌ Handle Iceberg external volume permissions&lt;br&gt;&lt;br&gt;
❌ Validate that everything works in new context  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cloning is metadata surgery, not environment provisioning.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  What We Needed: Clone++
&lt;/h2&gt;

&lt;p&gt;Take the incredible power of zero-copy cloning and add:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Permission Management&lt;/strong&gt; → Dynamically create and assign environment-appropriate roles&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reference Repointing&lt;/strong&gt; → Find and fix ALL database references automatically&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stream Recreation&lt;/strong&gt; → Drop and recreate streams with updated references&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Task Control&lt;/strong&gt; → Suspend tasks in non-production clones&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Iceberg Handling&lt;/strong&gt; → Manage external volumes and dynamic table conversions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validation&lt;/strong&gt; → Verify the clone is actually usable&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Observability&lt;/strong&gt; → Track operations, failures, and health&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recovery&lt;/strong&gt; → Resume failed clones without starting over&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance&lt;/strong&gt; → Parallel processing for large databases&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  From 3 Seconds to 8 Minutes (And Worth It)
&lt;/h2&gt;

&lt;p&gt;The final solution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- One command handles everything&lt;/span&gt;
&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;sp_clone_create_master&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'PROJECT'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'customer360'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'DEV'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- 8 minutes later:&lt;/span&gt;
&lt;span class="c1"&gt;-- ✅ Database cloned (still 3 seconds!)&lt;/span&gt;
&lt;span class="c1"&gt;-- ✅ Permissions fixed (DEV roles, not PROD)&lt;/span&gt;
&lt;span class="c1"&gt;-- ✅ All references repointed (186 views, 64 procedures, 23 functions)&lt;/span&gt;
&lt;span class="c1"&gt;-- ✅ Streams recreated (47 streams)&lt;/span&gt;
&lt;span class="c1"&gt;-- ✅ Tasks suspended (23 tasks)&lt;/span&gt;
&lt;span class="c1"&gt;-- ✅ Iceberg tables configured (external volume access granted)&lt;/span&gt;
&lt;span class="c1"&gt;-- ✅ Validated (zero stale references)&lt;/span&gt;
&lt;span class="c1"&gt;-- ✅ Audit logged (complete tracking)&lt;/span&gt;
&lt;span class="c1"&gt;-- ✅ Ready to use&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The trade-off:&lt;/strong&gt; 3 seconds → 8 minutes&lt;br&gt;&lt;br&gt;
&lt;strong&gt;The benefit:&lt;/strong&gt; 25 person-hours → 0 person-hours&lt;/p&gt;

&lt;h2&gt;
  
  
  The Journey Ahead
&lt;/h2&gt;

&lt;p&gt;We've seen the problem, now let's solve it. Over the next posts, I'll show you exactly how we built this:&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Zero-copy cloning is revolutionary&lt;/strong&gt; - In few seconds database copies change everything&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;But it's only 10% of the solution&lt;/strong&gt; - Production needs automation around it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database references are everywhere&lt;/strong&gt; - Views, procedures, functions, tasks, streams&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Permissions are environment-specific&lt;/strong&gt; - Production roles don't belong in dev&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Iceberg adds complexity&lt;/strong&gt; - External volumes and dynamic tables don't get cloned&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manual fixes don't scale&lt;/strong&gt; - Automation is essential for multi-environment operations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The ROI is massive&lt;/strong&gt; - 8 minutes vs 25 hours per clone&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  👉 Continue Reading
&lt;/h2&gt;

&lt;p&gt;The most immediately visible problem? &lt;strong&gt;Views and procedures pointing to production.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In &lt;strong&gt;Part 2&lt;/strong&gt;, I'll show you how to automatically find and fix every database reference using GET_DDL, string replacement, and parallel processing—plus how to handle Iceberg tables and recreate streams properly.&lt;/p&gt;




&lt;p&gt;All code is available in the &lt;a href="https://github.com/LALITHASWAROOPK/snowflake_cloning" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt; with comprehensive documentation.&lt;/p&gt;

</description>
      <category>snowflake</category>
      <category>clone</category>
      <category>zerocopy</category>
      <category>cloudwarehouse</category>
    </item>
    <item>
      <title>From One Agent to Many: Building a Multi-Agent Team for Snowflake Administration (Part 2)</title>
      <dc:creator>Krishna Tangudu</dc:creator>
      <pubDate>Tue, 21 Apr 2026 05:50:00 +0000</pubDate>
      <link>https://dev.to/swaroop_krishna_e2f4b83b2/from-one-agent-to-many-building-a-multi-agent-team-for-snowflake-administration-part-2-379d</link>
      <guid>https://dev.to/swaroop_krishna_e2f4b83b2/from-one-agent-to-many-building-a-multi-agent-team-for-snowflake-administration-part-2-379d</guid>
      <description>&lt;h1&gt;
  
  
  From One Agent to Many: Building a Multi-Agent Team for Snowflake Administration (Part 2)
&lt;/h1&gt;

&lt;p&gt;In &lt;a href="https://dev.to/swaroop_krishna_e2f4b83b2/ask-your-snowflake-account-anything-build-an-ai-admin-agent-with-cortex-github-copilot-1mk6"&gt;Part 1&lt;/a&gt;, we built an &lt;strong&gt;Admin Agent&lt;/strong&gt; that answers questions about Snowflake usage, credits, and query history. It's incredibly useful for monitoring operational metrics.&lt;/p&gt;

&lt;p&gt;But what if you want to go beyond monitoring and actually &lt;strong&gt;find waste&lt;/strong&gt; and &lt;strong&gt;recommend optimizations&lt;/strong&gt;?&lt;/p&gt;

&lt;p&gt;In this post, I'll show you how to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Build a &lt;strong&gt;Cost Optimizer Agent&lt;/strong&gt; specialized in finding inefficiencies&lt;/li&gt;
&lt;li&gt;Create an &lt;strong&gt;Orchestrator Agent&lt;/strong&gt; that intelligently routes questions to the right specialist&lt;/li&gt;
&lt;li&gt;Make them work together as a team&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;By the end, you'll have a multi-agent system where you can ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;"How much could I save?"&lt;/em&gt; → Routes to Cost Optimizer&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;"Show me last month's credits"&lt;/em&gt; → Routes to Admin Agent
&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;"Give me a complete analysis"&lt;/em&gt; → Routes to both agents&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's build it! 🚀&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Add a Second Agent?
&lt;/h2&gt;

&lt;p&gt;The Admin Agent from Part 1 is great at answering &lt;em&gt;"what happened?"&lt;/em&gt; questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"How many credits did we use?"&lt;/li&gt;
&lt;li&gt;"Show me query counts by warehouse"&lt;/li&gt;
&lt;li&gt;"What's our storage trend?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But it doesn't analyze patterns or recommend actions. For that, we need a &lt;strong&gt;specialist&lt;/strong&gt; focused on optimization.&lt;/p&gt;

&lt;p&gt;Enter the &lt;strong&gt;Cost Optimizer Agent&lt;/strong&gt; - designed to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Detect idle warehouses wasting credits&lt;/li&gt;
&lt;li&gt;Recommend auto-suspend settings&lt;/li&gt;
&lt;li&gt;Identify oversized warehouses&lt;/li&gt;
&lt;li&gt;Find queries that need optimization&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why separate agents instead of one big agent?&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;**FoArchitecture: Three Agents Working Together&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here's what we're building:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User Question (natural language)
        ↓
  Orchestrator Agent
    ↙           ↘
Admin Agent   Cost Optimizer Agent
    ↘           ↙
  Combined Response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Admin Agent&lt;/strong&gt; (from Part 1):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Already built and working&lt;/li&gt;
&lt;li&gt;Handles operational metrics&lt;/li&gt;
&lt;li&gt;Answers "what happened?" questions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cost Optimizer Agent&lt;/strong&gt; (new):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Analyzes patterns for waste&lt;/li&gt;
&lt;li&gt;Recommends optimizations&lt;/li&gt;
&lt;li&gt;Answers "what should I fix?" questions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Orchestrator Agent&lt;/strong&gt; (new):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Routes questions to appropriate specialist&lt;/li&gt;
&lt;li&gt;Can call multiple agents for comprehensive answers&lt;/li&gt;
&lt;li&gt;Users don't need to know which agent to ask&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example Questions:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Which warehouses have high idle time?"&lt;/li&gt;
&lt;li&gt;"Are any warehouses oversized for their workload?"&lt;/li&gt;
&lt;li&gt;"Show me queries with excessive spillage"&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Technical Example: Analyzing Warehouse Idle Time
&lt;/h2&gt;

&lt;p&gt;Let me show you how these agents work together to identify and quantify optimization opportunities.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Pattern (Same as Part 1)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Base View&lt;/strong&gt; - Calculate idle time by comparing warehouse uptime vs. query execution&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Join &lt;code&gt;WAREHOUSE_METERING_HISTORY&lt;/code&gt; (credits consumed) with &lt;code&gt;QUERY_ATTRIBUTION_HISTORY&lt;/code&gt; (actual work done)&lt;/li&gt;
&lt;li&gt;The gap = idle credits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Semantic View&lt;/strong&gt; - Map the data to natural language&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;FACTS: &lt;code&gt;idle_credits&lt;/code&gt;, &lt;code&gt;idle_percentage&lt;/code&gt;, &lt;code&gt;idle_cost_usd&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;DIMENSIONS: &lt;code&gt;warehouse_name&lt;/code&gt;, &lt;code&gt;uptime_date&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;METRICS: sum of idle credits, average idle percentage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Create Agent&lt;/strong&gt; - Connect the semantic view to the Cortex agent&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Agent instructions: "Find waste and recommend fixes"&lt;/li&gt;
&lt;li&gt;Tool: &lt;code&gt;IdleWarehouseAnalyst&lt;/code&gt; using the semantic view&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 4: Test It&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;SNOWFLAKE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CORTEX&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DATA_AGENT_RUN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;'COST_OPTIMIZER_AGENT'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'{"messages":[{"role":"user","content":[{"type":"text","text":"Which warehouses have high idle time?"}]}]}'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;📁 &lt;strong&gt;Full SQL Implementation:&lt;/strong&gt; &lt;a href="https://github.com/LALITHASWAROOPK/agent_snowflake_admin/blob/main/sql/06_create_cost_optimizer_views.sql" rel="noopener noreferrer"&gt;sql/06_create_cost_optimizer_views.sql&lt;/a&gt; | &lt;a href="https://github.com/LALITHASWAROOPK/agent_snowflake_admin/blob/main/sql/07_create_cost_optimizer_semantic_views.sql" rel="noopener noreferrer"&gt;sql/07_create_cost_optimizer_semantic_views.sql&lt;/a&gt; | &lt;a href="https://github.com/LALITHASWAROOPK/agent_snowflake_admin/blob/main/sql/10_create_cost_optimizer_agent.sql" rel="noopener noreferrer"&gt;sql/10_create_cost_optimizer_agent.sql&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Example Response:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BRONZE_LOADER_WH Analysis (Last 30 Days):

Idle Time: 68% 
Idle Credits: 892 credits
Pattern: Warehouse runs continuously but data loads are batch-based

Recommendation: Set AUTO_SUSPEND = 300 seconds (5 minutes)
Reasoning: Bronze data loads complete, then warehouse sits idle 
           waiting for next batch

Estimated savings: ~600 credits/month
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great! Now we have two specialized agents. But there's a problem...&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 2: Adding the Orchestrator
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The Problem:&lt;/strong&gt; Now users need to know which agent to ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Questions about credits → Ask Admin Agent&lt;/li&gt;
&lt;li&gt;Questions about optimization → Ask Cost Optimizer Agent&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That doesn't scale. What if someone asks &lt;em&gt;"Why are my costs high?"&lt;/em&gt; - which agent should handle that?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Solution:&lt;/strong&gt; An Orchestrator Agent that routes questions automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Pattern
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Routing Function&lt;/strong&gt; - Python UDF that lets one agent call another&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Takes agent FQN and question as parameters&lt;/li&gt;
&lt;li&gt;Calls &lt;code&gt;SNOWFLAKE.CORTEX.DATA_AGENT_RUN&lt;/code&gt; programmatically&lt;/li&gt;
&lt;li&gt;Returns the agent's response&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Orchestrator Agent&lt;/strong&gt; - Meta-agent that routes to specialists&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Instructions define routing logic (keywords: "idle"→Cost, "credits"→Admin)&lt;/li&gt;
&lt;li&gt;Tools reference the routing function&lt;/li&gt;
&lt;li&gt;Can call multiple agents for comprehensive analysis&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Grant Permissions&lt;/strong&gt; - Allow orchestrator to invoke specialist agents&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;📁 &lt;strong&gt;Full Implementation:&lt;/strong&gt; &lt;a href="https://github.com/LALITHASWAROOPK/agent_snowflake_admin/blob/main/sql/12_create_orchestrator_agent.sql" rel="noopener noreferrer"&gt;sql/12_create_orchestrator_agent.sql&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Part 3: Seeing It All Work Together
&lt;/h2&gt;

&lt;p&gt;Now let's test the complete system.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example 1: Simple Routing
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Question:&lt;/strong&gt; &lt;em&gt;"How many credits did BRONZE_LOADER_WH use last month?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What Happens:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Orchestrator receives question&lt;/li&gt;
&lt;li&gt;Identifies this as operational metrics (keywords: "credits", "use")&lt;/li&gt;
&lt;li&gt;Routes to &lt;strong&gt;Admin Agent&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Admin Agent queries &lt;code&gt;SV_WAREHOUSE_COST_ANALYSIS&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Returns credit numbers&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Response:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BRONZE_LOADER_WH Credit Usage (Last 30 Days):
Total Credits: 1,312
Average Daily: 43.7 credits
Active Days: 30 of 30 (runs daily for ETL loads)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Example 2: Optimization Routing
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Question:&lt;/strong&gt; &lt;em&gt;"Is BRONZE_LOADER_WH wasting credits?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What Happens:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Orchestrator receives question&lt;/li&gt;
&lt;li&gt;Identifies this as optimization (keywords: "wasting")&lt;/li&gt;
&lt;li&gt;Routes to &lt;strong&gt;Cost Optimizer Agent&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Cost Optimizer queries &lt;code&gt;SV_IDLE_WAREHOUSE_ANALYSIS&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Returns analysis with recommendations&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Response:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;BRONZE_LOADER_WH&lt;/span&gt; &lt;span class="n"&gt;Waste&lt;/span&gt; &lt;span class="n"&gt;Analysis&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

&lt;span class="n"&gt;Idle&lt;/span&gt; &lt;span class="n"&gt;Percentage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;68&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;
&lt;span class="n"&gt;Idle&lt;/span&gt; &lt;span class="n"&gt;Credits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;892&lt;/span&gt; &lt;span class="n"&gt;credits&lt;/span&gt;
&lt;span class="n"&gt;Pattern&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Batch&lt;/span&gt; &lt;span class="n"&gt;ETL&lt;/span&gt; &lt;span class="n"&gt;jobs&lt;/span&gt; &lt;span class="n"&gt;complete&lt;/span&gt; &lt;span class="n"&gt;quickly&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;warehouse&lt;/span&gt; &lt;span class="n"&gt;stays&lt;/span&gt; &lt;span class="n"&gt;running&lt;/span&gt;

&lt;span class="n"&gt;Recommendation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="n"&gt;WAREHOUSE&lt;/span&gt; &lt;span class="n"&gt;BRONZE_LOADER_WH&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;AUTO_SUSPEND&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;Estimated&lt;/span&gt; &lt;span class="n"&gt;Monthly&lt;/span&gt; &lt;span class="n"&gt;Savings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="mi"&gt;600&lt;/span&gt; &lt;span class="n"&gt;credits&lt;/span&gt;
&lt;span class="n"&gt;Reasoning&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bronze&lt;/span&gt; &lt;span class="n"&gt;loads&lt;/span&gt; &lt;span class="k"&gt;are&lt;/span&gt; &lt;span class="n"&gt;periodic&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;warehouse&lt;/span&gt; &lt;span class="n"&gt;doesn&lt;/span&gt;&lt;span class="s1"&gt;'t need to 
           stay running between batch jobs
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Example 3: Multi-Agent Analysis
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Question:&lt;/strong&gt; &lt;em&gt;"Give me a complete analysis of BRONZE_LOADER_WH"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What Happens:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Orchestrator recognizes need for comprehensive analysis&lt;/li&gt;
&lt;li&gt;Routes question to &lt;strong&gt;BOTH&lt;/strong&gt; agents:

&lt;ul&gt;
&lt;li&gt;Admin Agent: Get usage metrics&lt;/li&gt;
&lt;li&gt;Cost Optimizer: Get efficiency analysis&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Aggregates both responses&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Response:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BRONZE_LOADER_WH Complete Analysis:

📊 USAGE (from Admin Agent):
- Total Credits (30 days): 1,312
- Average Daily: 43.7 credits
- Primary Workload: Bronze layer ETL (raw data ingestion)
- Load Schedule: Hourly batches from source systems
- Active Days: 30 of 30

💰 EFFICIENCY (from Cost Optimizer):
- Idle Time: 68%
- Wasted Credits: 892 credits
- Pattern: Loads complete in ~15 min, then idle for 45 min
- Current auto_suspend: Not set (or &amp;gt;30 min)

RECOMMENDATION:
ETL warehouse with batch workload pattern. Jobs complete quickly 
but warehouse stays running between batches.

Action: SET AUTO_SUSPEND = 300 (5 minutes)
Impact: ~600 credits/month savings with no performance impact
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Beautiful! The user just asked one question and got insights from two specialists.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technical Architecture: How It Actually Works
&lt;/h2&gt;

&lt;p&gt;Let me break down the three layers that make these agents work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 1: Base Views (Data Calculation)
&lt;/h3&gt;

&lt;p&gt;These views do the actual computational work by querying &lt;code&gt;ACCOUNT_USAGE&lt;/code&gt;. For example, idle warehouse detection compares:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;WAREHOUSE_METERING_HISTORY&lt;/code&gt; (when warehouse was running)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;QUERY_ATTRIBUTION_HISTORY&lt;/code&gt; (when queries were executing)&lt;/li&gt;
&lt;li&gt;The gap = wasted compute&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Layer 2: Semantic Views (Natural Language Mapping)
&lt;/h3&gt;

&lt;p&gt;These teach the agent what the data means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;FACTS&lt;/strong&gt;: Raw values (idle_credits, idle_percentage)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DIMENSIONS&lt;/strong&gt;: Grouping columns (warehouse_name, date)
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;METRICS&lt;/strong&gt;: Aggregations (SUM, AVG)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Layer 3: Agents (Natural Language Interface)
&lt;/h3&gt;

&lt;p&gt;Each agent:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Receives natural language question&lt;/li&gt;
&lt;li&gt;Determines which semantic view to query&lt;/li&gt;
&lt;li&gt;Cortex Analyst generates SQL automatically&lt;/li&gt;
&lt;li&gt;Executes and returns natural language answer&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  MCP Integration (Optional)
&lt;/h3&gt;

&lt;p&gt;For easier access, you can build an &lt;strong&gt;MCP (Model Context Protocol) server&lt;/strong&gt; to integrate with GitHub Copilot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Simplified MCP Server Pattern
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;ask_agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;agent_fqn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;messages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;}]}]}&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SELECT SNOWFLAKE.CORTEX.DATA_AGENT_RUN(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;agent_fqn&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchone&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;📁 &lt;strong&gt;Full MCP Server:&lt;/strong&gt; &lt;a href="https://github.com/LALITHASWAROOPK/agent_snowflake_admin/blob/main/mcp/server.py" rel="noopener noreferrer"&gt;mcp/server.py&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  How the Agents Work Together
&lt;/h2&gt;

&lt;p&gt;Here's the beautiful part: &lt;strong&gt;the Admin Agent provides context, and the Cost Optimizer provides action&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: Analyzing Bronze ETL Warehouse
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Question:&lt;/strong&gt; &lt;em&gt;"Should I optimize BRONZE_LOADER_WH?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Admin Agent&lt;/strong&gt; provides context:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BRONZE_LOADER_WH Usage:
  - Credits (30 days): 1,312
  - Workload: Bronze layer ETL
  - Schedule: Hourly batch loads
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Cost Optimizer&lt;/strong&gt; provides analysis:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Efficiency Analysis:
  - Idle time: 68%
  - Pattern: 15-min loads, 45-min idle gaps
  - Recommendation: SET AUTO_SUSPEND = 300
  - Expected savings: ~600 credits/month
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Together, they provide:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Context&lt;/strong&gt; - What the warehouse does (bronze ETL)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Root cause&lt;/strong&gt; - Batch pattern creates idle gaps&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Solution&lt;/strong&gt; - Auto-suspend between batches&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Impact&lt;/strong&gt; - Savings estimate without performance impact&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This combination ensures you get both &lt;strong&gt;what's happening&lt;/strong&gt; and &lt;strong&gt;what to do about it&lt;/strong&gt; in one conversation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Start: Adding to Your Existing Agent
&lt;/h2&gt;

&lt;p&gt;If you built the Admin Agent from Part 1, here's the deployment sequence:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Cost Optimizer Agent&lt;/span&gt;
sql/06_create_cost_optimizer_views.sql          &lt;span class="c"&gt;# Base views&lt;/span&gt;
sql/07_create_cost_optimizer_semantic_views.sql &lt;span class="c"&gt;# Semantic layer&lt;/span&gt;
sql/10_create_cost_optimizer_agent.sql          &lt;span class="c"&gt;# Agent&lt;/span&gt;

&lt;span class="c"&gt;# 2. Orchestrator Agent  &lt;/span&gt;
sql/12_create_orchestrator_agent.sql            &lt;span class="c"&gt;# Routing agent + function&lt;/span&gt;

&lt;span class="c"&gt;# 3. Test it&lt;/span&gt;
SELECT SNOWFLAKE.CORTEX.DATA_AGENT_RUN&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;'ORCHESTRATOR_AGENT'&lt;/span&gt;,
    &lt;span class="s1"&gt;'{"messages":[{"role":"user","content":[{"type":"text","text":"Which warehouses are idle?"}]}]}'&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;📁 &lt;strong&gt;All SQL scripts:&lt;/strong&gt; &lt;a href="https://github.com/LALITHASWAROOPK/agent_snowflake_admin/tree/main/sql" rel="noopener noreferrer"&gt;GitHub Repository&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You now have a working multi-agent system!&lt;/p&gt;

&lt;h2&gt;
  
  
  Agent Execution Costs
&lt;/h2&gt;

&lt;p&gt;Each agent query executes on a Snowflake warehouse and consumes credits:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Typical costs:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Simple query (1 warehouse, 1 dimension): ~0.001-0.002 credits&lt;/li&gt;
&lt;li&gt;Complex query (joins, aggregations): ~0.005-0.01 credits&lt;/li&gt;
&lt;li&gt;Very complex analysis (multiple time ranges): ~0.01-0.05 credits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example usage:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;100 agent queries/day&lt;/li&gt;
&lt;li&gt;Average 0.005 credits per query&lt;/li&gt;
&lt;li&gt;Total: 0.5 credits/day = ~15 credits/month&lt;/li&gt;
&lt;li&gt;At $3.50/credit: ~$52/month&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The agents themselves cost very little to run compared to the insights they provide.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Lessons Learned
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Orchestration Makes It Seamless
&lt;/h3&gt;

&lt;p&gt;Without the Orchestrator, users need to know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which agent handles which questions&lt;/li&gt;
&lt;li&gt;How to call different agents&lt;/li&gt;
&lt;li&gt;When to use multiple agents&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With the Orchestrator:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Just ask naturally&lt;/li&gt;
&lt;li&gt;Routing happens automatically&lt;/li&gt;
&lt;li&gt;Multi-agent queries work transparently&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Agent Communication Is Key
&lt;/h3&gt;

&lt;p&gt;The routing function enables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One agent calling another&lt;/li&gt;
&lt;li&gt;Aggregating responses&lt;/li&gt;
&lt;li&gt;Building complex workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pattern: &lt;strong&gt;Function → Agent calls other agents via that function&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Start with Two, Scale to Many
&lt;/h3&gt;

&lt;p&gt;This architecture scales easily:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each new agent is independent&lt;/li&gt;
&lt;li&gt;Orchestrator routing rules are additive&lt;/li&gt;
&lt;li&gt;No changes needed to existing agents&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Test Routing Logic
&lt;/h3&gt;

&lt;p&gt;Make sure to test:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Edge cases (ambiguous questions)&lt;/li&gt;
&lt;li&gt;Multi-agent scenarios&lt;/li&gt;
&lt;li&gt;Direct vs. orchestrated access&lt;/li&gt;
&lt;li&gt;Response aggregation quality&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;In &lt;strong&gt;Part 3&lt;/strong&gt;, I'll show you how to add a &lt;strong&gt;Security &amp;amp; Governance Agent&lt;/strong&gt; that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Audits role assignments and privileges&lt;/li&gt;
&lt;li&gt;Detects failed login patterns (brute force attacks)&lt;/li&gt;
&lt;li&gt;Identifies inactive users with access&lt;/li&gt;
&lt;li&gt;Monitors for compliance violations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then we'll have a complete &lt;strong&gt;three-agent team&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🔧 Admin - Operational metrics&lt;/li&gt;
&lt;li&gt;💰 Cost Optimizer - Waste detection&lt;/li&gt;
&lt;li&gt;🔐 Security - Compliance and access control&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All coordinated by the Orchestrator.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code Repository
&lt;/h2&gt;

&lt;p&gt;All SQL scripts and complete implementation available at:&lt;br&gt;
&lt;a href="https://github.com/LALITHASWAROOPK/agent_snowflake_admin" rel="noopener noreferrer"&gt;GitHub Repository&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Base view SQL for idle detection&lt;/li&gt;
&lt;li&gt;Additional views for warehouse sizing, query optimization&lt;/li&gt;
&lt;li&gt;Semantic view definitions&lt;/li&gt;
&lt;li&gt;All three agent configurations&lt;/li&gt;
&lt;li&gt;MCP server code for Copilot integration&lt;/li&gt;
&lt;li&gt;Step-by-step deployment guide&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API endpoints&lt;/strong&gt; - Integrate with existing dashboards&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scheduled queries&lt;/strong&gt; - Automated monitoring with alerts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Copilot&lt;/strong&gt; - Ask questions directly from your IDE&lt;a href="https://github.com/LALITHASWAROOPK/agent_snowflake_admin" rel="noopener noreferrer"&gt;github.com/LALITHASWAROOPK/agent_snowflake_admin&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Includes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All SQL scripts (base views, semantic views, agents)&lt;/li&gt;
&lt;li&gt;MCP server for GitHub Copilot integration&lt;/li&gt;
&lt;li&gt;Deployment guide&lt;/li&gt;
&lt;li&gt;Example questions and expected outputs&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In Part 1, we built one agent. In Part 2, we:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;✅ Added a specialized &lt;strong&gt;Cost Optimizer Agent&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;✅ Created an &lt;strong&gt;Orchestrator&lt;/strong&gt; for intelligent routing&lt;/li&gt;
&lt;li&gt;✅ Made them work together as a team&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;The multi-agent pattern is powerful because:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each agent specializes in one domain&lt;/li&gt;
&lt;li&gt;Orchestrator handles routing automatically&lt;/li&gt;
&lt;li&gt;Easy to add new agents without changing existing ones&lt;/li&gt;
&lt;li&gt;Users get comprehensive answers from one question&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;From the user's perspective:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Before: "Which agent should I ask about idle warehouses?"
After: "Which warehouses are idle?" (Orchestrator figures it out)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is just the beginning. You can extend this architecture with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Security agents for compliance&lt;/li&gt;
&lt;li&gt;Performance agents for query optimization
&lt;/li&gt;
&lt;li&gt;Data quality agents for monitoring freshness&lt;/li&gt;
&lt;li&gt;Custom agents for your specific needs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The pattern stays the same: &lt;strong&gt;Views → Semantic Views → Agent → Orchestrator&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Questions or feedback?&lt;/strong&gt; Drop a comment below!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Find this helpful?&lt;/strong&gt; Give it a ❤️ and stay tuned for Part 3: Security &amp;amp; Governance Agent&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Want to discuss?&lt;/strong&gt; Connect with me on &lt;a href="https://dev.toyour-linkedin"&gt;LinkedIn&lt;/a&gt; or &lt;a href="https://dev.toyour-twitter"&gt;Twitter&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Quick Reference: Agent Responsibilities
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Agent&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Example Questions&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Admin&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Operational metrics&lt;/td&gt;
&lt;td&gt;"How many credits did BRONZE_LOADER_WH use?"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cost Optimizer&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Waste detection&lt;/td&gt;
&lt;td&gt;"Is BRONZE_LOADER_WH running idle?"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Orchestrator&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Intelligent routing&lt;/td&gt;
&lt;td&gt;"Analyze BRONZE_LOADER_WH efficiency"&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;p&gt;&lt;em&gt;Part 1: &lt;a href="https://dev.to/swaroop_krishna_e2f4b83b2/ask-your-snowflake-account-anything-build-an-ai-admin-agent-with-cortex-github-copilot-1mk6"&gt;Build an AI Admin Agent&lt;/a&gt;&lt;/em&gt;&lt;br&gt;&lt;br&gt;
&lt;em&gt;Part 2: Multi-Agent Architecture (this post)&lt;/em&gt;&lt;br&gt;&lt;br&gt;
&lt;em&gt;Part 3: Security &amp;amp; Governance Agent (coming soon)&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>snowflake</category>
      <category>agentskills</category>
      <category>mcp</category>
    </item>
    <item>
      <title>Ask Your Snowflake Account Anything — Build an AI Admin Agent with Cortex + GitHub Copilot ( Part 1 )</title>
      <dc:creator>Krishna Tangudu</dc:creator>
      <pubDate>Tue, 31 Mar 2026 15:57:14 +0000</pubDate>
      <link>https://dev.to/swaroop_krishna_e2f4b83b2/ask-your-snowflake-account-anything-build-an-ai-admin-agent-with-cortex-github-copilot-1mk6</link>
      <guid>https://dev.to/swaroop_krishna_e2f4b83b2/ask-your-snowflake-account-anything-build-an-ai-admin-agent-with-cortex-github-copilot-1mk6</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; — Spin up a Snowflake Cortex Agent that can answer admin questions like &lt;em&gt;"Which warehouses burned the most credits last month?"&lt;/em&gt; or &lt;em&gt;"Who are my top 5 spenders?"&lt;/em&gt; — all from a natural-language prompt inside GitHub Copilot Chat.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Snowflake &lt;code&gt;ACCOUNT_USAGE&lt;/code&gt; contains everything you need to understand spend, performance, and security. The catch? You have to know the right table, the right join, and write it correctly every time.&lt;/p&gt;

&lt;p&gt;What if you could just ask?&lt;/p&gt;




&lt;h2&gt;
  
  
  What You'll Build
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GitHub Copilot Chat
      │  (natural language)
      ▼
MCP Server (Python, local)
      │  ask_admin tool
      ▼
Snowflake Cortex Agent
      │  routes to the right semantic view
      ▼
SNOWFLAKE.ACCOUNT_USAGE (+ optional ORGANIZATION_USAGE)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By the end you'll be able to type questions like these into Copilot Chat and get back answers with real numbers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;What are total compute credits by warehouse in the last 30 days?&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Which users consumed the most credits this month?&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;How many failed logins occurred in the last 7 days?&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;What are serverless credits by service type?&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Requirement&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Snowflake account (any paid edition)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ACCOUNTADMIN&lt;/code&gt; or a role with &lt;code&gt;ACCOUNT_USAGE&lt;/code&gt; access&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Python 3.9+&lt;/td&gt;
&lt;td&gt;For the MCP bridge&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VS Code + GitHub Copilot Chat&lt;/td&gt;
&lt;td&gt;Agent mode enabled&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Git&lt;/td&gt;
&lt;td&gt;To clone the template&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Step 1 — Clone the Template
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/LALITHASWAROOPK/agent_snowflake_admin_assistant.git
&lt;span class="nb"&gt;cd &lt;/span&gt;agent_snowflake_admin_assistant/agent_snowflake_admin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open the folder in VS Code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;code &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 2 — Deploy the SQL Objects (5 scripts, ~5 minutes)
&lt;/h2&gt;

&lt;p&gt;All SQL is in the &lt;code&gt;sql/&lt;/code&gt; folder. Replace the four placeholders before running:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Placeholder&lt;/th&gt;
&lt;th&gt;What to set&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;APP_DB&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A database to hold your views/agent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;APP_SCHEMA&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Schema inside that database&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;ADMIN_ROLE&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Role that owns the objects&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;DEVELOPER_ROLE&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Role that will query the agent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;EXEC_WAREHOUSE&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Warehouse the agent will use at runtime&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Run them in order in a Snowflake worksheet or SnowSQL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="k"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;01&lt;/span&gt;&lt;span class="n"&gt;_create_views&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;sql&lt;/span&gt;        &lt;span class="c1"&gt;-- Base views over ACCOUNT_USAGE&lt;/span&gt;
&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="k"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;02&lt;/span&gt;&lt;span class="n"&gt;_create_semantic_views&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;sql&lt;/span&gt;  &lt;span class="c1"&gt;-- Semantic layer (natural-language metadata)&lt;/span&gt;
&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="k"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;03&lt;/span&gt;&lt;span class="n"&gt;_create_agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;sql&lt;/span&gt;        &lt;span class="c1"&gt;-- Cortex Agent wired to those views&lt;/span&gt;
&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="k"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;04&lt;/span&gt;&lt;span class="n"&gt;_create_budget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;sql&lt;/span&gt;       &lt;span class="c1"&gt;-- Optional: spending guardrails&lt;/span&gt;
&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="k"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;05&lt;/span&gt;&lt;span class="n"&gt;_grants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;sql&lt;/span&gt;              &lt;span class="c1"&gt;-- RBAC grants&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What the Views Cover
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;01_create_views.sql&lt;/code&gt; builds seven base views, all sourced exclusively from &lt;code&gt;SNOWFLAKE.ACCOUNT_USAGE&lt;/code&gt; — no custom tables, no proprietary schemas:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;View&lt;/th&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;V_WAREHOUSE_COST_BY_TAG&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;WAREHOUSE_METERING_HISTORY&lt;/code&gt; + &lt;code&gt;TAG_REFERENCES&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;V_QUERY_PERFORMANCE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;QUERY_HISTORY&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;V_USER_SPEND&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;QUERY_ATTRIBUTION_HISTORY&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;V_SERVERLESS_COSTS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tasks, pipes, clustering, search, MV refresh&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;V_STORAGE_USAGE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;STORAGE_USAGE&lt;/code&gt; + &lt;code&gt;DATABASE_STORAGE_USAGE_HISTORY&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;V_LOGIN_HISTORY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;LOGIN_HISTORY&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;V_DATA_TRANSFER&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;DATA_TRANSFER_HISTORY&lt;/code&gt; + &lt;code&gt;REPLICATION_USAGE_HISTORY&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;02_create_semantic_views.sql&lt;/code&gt; wraps each base view with a &lt;code&gt;COMMENT&lt;/code&gt; block that tells the Cortex Agent what the view is for — this is how the agent knows which view to query for any given question.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Semantic Views?
&lt;/h3&gt;

&lt;p&gt;Snowflake Cortex Agents use &lt;strong&gt;semantic views&lt;/strong&gt; as tool definitions. A semantic view is just a standard view with a &lt;code&gt;COMMENT&lt;/code&gt; that describes its purpose in natural language. The agent reads those comments at inference time to decide which tool (view) answers the user's question.&lt;/p&gt;

&lt;p&gt;No prompt engineering required on your end.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3 — Start the MCP Bridge
&lt;/h2&gt;

&lt;p&gt;The MCP (Model Context Protocol) server is a lightweight Python HTTP server that exposes one tool — &lt;code&gt;ask_admin&lt;/code&gt; — which Copilot Chat can call.&lt;/p&gt;

&lt;h3&gt;
  
  
  Install dependencies
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; mcp/requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Configure your environment
&lt;/h3&gt;

&lt;p&gt;Create a &lt;code&gt;.env&lt;/code&gt; file in the repo root (never commit this):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SNOWFLAKE_ACCOUNT=&amp;lt;your_account&amp;gt;
SNOWFLAKE_USER=&amp;lt;your_user&amp;gt;
SNOWFLAKE_AUTHENTICATOR=externalbrowser
SNOWFLAKE_ROLE=&amp;lt;DEVELOPER_ROLE&amp;gt;
SNOWFLAKE_WAREHOUSE=&amp;lt;EXEC_WAREHOUSE&amp;gt;
SNOWFLAKE_AGENT_FQN=&amp;lt;APP_DB&amp;gt;.&amp;lt;APP_SCHEMA&amp;gt;.&amp;lt;AGENT_NAME&amp;gt;
MCP_PORT=3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Supports &lt;code&gt;externalbrowser&lt;/code&gt; (SSO), &lt;code&gt;snowflake&lt;/code&gt; (password), and OAuth — set &lt;code&gt;SNOWFLAKE_AUTHENTICATOR&lt;/code&gt; and the matching credential variable.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Launch the server
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;/scripts/start-mcp.ps1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python mcp/server.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify it's up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Invoke-RestMethod&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Uri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http://127.0.0.1:3000/health&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Method&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Get&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# → {"status":"ok"}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 4 — Connect Copilot Chat
&lt;/h2&gt;

&lt;p&gt;The repo ships a pre-configured &lt;code&gt;.vscode/mcp.json&lt;/code&gt; that wires the local MCP server into VS Code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"snowflake-admin"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://127.0.0.1:3000"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;VS Code picks this up automatically when you open the workspace. You'll see the &lt;code&gt;ask_admin&lt;/code&gt; tool listed inside Copilot Chat's tool picker.&lt;/p&gt;

&lt;p&gt;There's also a GitHub Copilot instruction file at &lt;code&gt;.github/instructions/admin.instructions.md&lt;/code&gt; which gives Copilot context about the agent's scope so it routes questions there automatically.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5 — Ask It Questions
&lt;/h2&gt;

&lt;p&gt;Switch Copilot Chat to &lt;strong&gt;Agent mode&lt;/strong&gt; and try:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;What are my top 5 warehouses by compute credits in the last 30 days?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Which users have spent the most credits this month?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Show me failed logins by user in the last 7 days.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;What serverless services are costing me the most right now?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;What's my current storage usage broken down by database?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent selects the right semantic view, runs the query, and returns a plain-English answer with the numbers inline — no SQL, no context switching.&lt;/p&gt;




&lt;h2&gt;
  
  
  Optional: Budget Guardrails
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;04_create_budget.sql&lt;/code&gt; sets up a Snowflake Budget object so you can enforce credit limits at the agent level. Handy for keeping a shared admin agent from running runaway queries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;APP_DB&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;APP_SCHEMA&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;BUDGET_NAME&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;!&lt;/span&gt;&lt;span class="n"&gt;SET_SPENDING_LIMIT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;APP_DB&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;APP_SCHEMA&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;BUDGET_NAME&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;!&lt;/span&gt;&lt;span class="n"&gt;GET_SPENDING_HISTORY&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Architecture Recap
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────┐
│         GitHub Copilot Chat         │
│      (Agent mode + ask_admin tool)  │
└────────────────┬────────────────────┘
                 │ HTTP / MCP
┌────────────────▼────────────────────┐
│      MCP Bridge  (mcp/server.py)    │
│  Exposes: /mcp/tools/list           │
│           /mcp/tools/call           │
│           /health                   │
└────────────────┬────────────────────┘
                 │ snowflake-connector-python
┌────────────────▼────────────────────┐
│   SNOWFLAKE.CORTEX.DATA_AGENT_RUN   │
│   ← Cortex Agent (semantic router)  │
└────────────────┬────────────────────┘
                 │ picks the right tool
┌────────────────▼────────────────────┐
│     Semantic Views (SV_*)           │
│     Base Views    (V_*)             │
│     ↑ SNOWFLAKE.ACCOUNT_USAGE       │
└─────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Key Things to Know
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Account Usage latency&lt;/strong&gt;: &lt;code&gt;SNOWFLAKE.ACCOUNT_USAGE&lt;/code&gt; is typically delayed by up to a few hours — not real-time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate sheet&lt;/strong&gt;: &lt;code&gt;ORGANIZATION_USAGE.RATE_SHEET_DAILY&lt;/code&gt; is optional and depends on your edition/region.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auth flexibility&lt;/strong&gt;: The MCP server supports SSO (&lt;code&gt;externalbrowser&lt;/code&gt;), OAuth, and password auth — configure once in &lt;code&gt;.env&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Get the Code
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/LALITHASWAROOPK/agent_snowflake_admin" rel="noopener noreferrer"&gt;github.com/LALITHASWAROOPK/agent_snowflake_admin_assistant&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;PRs and issues welcome. If you build on top of this, tag me — I'd love to see what you add.&lt;/p&gt;




</description>
      <category>snowflake</category>
      <category>githubcopilot</category>
      <category>mcp</category>
      <category>ai</category>
    </item>
    <item>
      <title>How Snowflake’s RELY Constraint Supercharges Your Star‑Schema Queries</title>
      <dc:creator>Krishna Tangudu</dc:creator>
      <pubDate>Thu, 19 Mar 2026 18:49:36 +0000</pubDate>
      <link>https://dev.to/swaroop_krishna_e2f4b83b2/how-snowflakes-rely-constraint-supercharges-your-star-schema-queries-3n4f</link>
      <guid>https://dev.to/swaroop_krishna_e2f4b83b2/how-snowflakes-rely-constraint-supercharges-your-star-schema-queries-3n4f</guid>
      <description>&lt;p&gt;Snowflake’s RELY constraint is a powerful but under‑used hint that tells the optimizer it can trust your primary keys and foreign keys and use them to eliminate unnecessary work—especially redundant joins. In this post, I’ll explain what RELY does, how it helps with join elimination, and walk you through concrete examples you can adapt for your own schemas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters to a data engineer?
&lt;/h2&gt;

&lt;p&gt;If you’ve ever used wide, reusable models with hundreds of columns designed as a single source of truth, you know how easily queries can accumulate unnecessary joins. &lt;code&gt;RELY&lt;/code&gt; helps here by letting Snowflake eliminate redundant joins when they’re logically not needed, so queries only scan the tables that actually contribute to the result.&lt;/p&gt;

&lt;p&gt;In Snowflake, UNIQUE, PRIMARY KEY, and FOREIGN KEY constraints on standard tables are not enforced by default; they are mainly metadata for humans and tools. However, Snowflake lets you add a RELY property on those constraints to signal:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I guarantee this constraint is true in my data, and you can use it to rewrite my queries.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You set RELY like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;dim_customer&lt;/span&gt;
  &lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="n"&gt;RELY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;fact_sales&lt;/span&gt;
  &lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="n"&gt;fk_customer_id&lt;/span&gt; &lt;span class="n"&gt;RELY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  When does RELY kick in?
&lt;/h2&gt;

&lt;p&gt;Snowflake can use RELY‑tagged constraints to eliminate unnecessary joins when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A table has a PRIMARY KEY or UNIQUE constraint marked RELY.&lt;/li&gt;
&lt;li&gt;A related table has a FOREIGN KEY constraint also marked RELY.&lt;/li&gt;
&lt;li&gt;The query joins on those keys, but doesn’t actually need any columns from one of the joined tables beyond the key itself.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In those situations, Snowflake can realize that the join is “logically redundant” and rewrite the plan to drop the table scan entirely.&lt;/p&gt;

&lt;p&gt;This is a cost-saving measure as it rewrites the query to eliminate unnecessary tables, allowing for faster performance. Additionally, may allows us to use a smaller warehouse may help reduce overall costs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example 1: Simple join elimination with sample data&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;dim_customer&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;customer_name&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;fact_sales&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;sale_id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;sale_date&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="n"&gt;fk_customer_id&lt;/span&gt; &lt;span class="k"&gt;FOREIGN&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;dim_customer&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sample data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;dim_customer&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Alice'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Bob'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Charlie'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;fact_sales&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'2026-01-01'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;102&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'2026-01-05'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;103&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'2026-01-03'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;104&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'2026-01-04'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Initial query (no RELY):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total_amount&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;fact_sales&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;dim_customer&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Output&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr6n9d4azm0h36y04xh6y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr6n9d4azm0h36y04xh6y.png" alt=" " width="800" height="244"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Query Plan:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkcuhr14oh42dgth5mfg6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkcuhr14oh42dgth5mfg6.png" alt=" " width="591" height="679"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Now enable RELY:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;dim_customer&lt;/span&gt;
  &lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="n"&gt;RELY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;fact_sales&lt;/span&gt;
  &lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="n"&gt;fk_customer_id&lt;/span&gt; &lt;span class="n"&gt;RELY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Under the hood, Snowflake sees:&lt;/p&gt;

&lt;p&gt;dim_customer.customer_id is unique and trusted (PRIMARY KEY RELY).&lt;br&gt;
fact_sales.customer_id is a trusted foreign key (fk_customer_id RELY).&lt;br&gt;
The only column used from dim_customer is customer_id, which is already in fact_sales.&lt;/p&gt;

&lt;p&gt;So the optimizer eliminates the join to dim_customer and rewrites the query plan like below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frs66gh5nrpdakxqp9ohf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frs66gh5nrpdakxqp9ohf.png" alt=" " width="380" height="492"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The result is identical, but the execution plan no longer scans dim_customer at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example 2: Join elimination with filters (and when it doesn’t kick in)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total_amount&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;fact_sales&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;dim_customer&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Alice'&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, dim_customer.customer_name is used in the filter, so the join on dim_customer is not redundant even with RELY. The optimizer must still fetch dimension data to evaluate the WHERE clause, so join elimination does not occur.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fodpifee9z4xfa6tfx2y1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fodpifee9z4xfa6tfx2y1.png" alt=" " width="633" height="669"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example 3: RELY in a multi‑dimension star schema&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;dim_customer&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;customer_name&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt;  &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;dim_channel&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;channel_id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;channel_name&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;fact_sales&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;sale_id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;channel_id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;sale_date&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="n"&gt;fk_customer&lt;/span&gt; &lt;span class="k"&gt;FOREIGN&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;dim_customer&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="n"&gt;fk_channel&lt;/span&gt; &lt;span class="k"&gt;FOREIGN&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;channel_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;dim_channel&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;channel_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="o"&gt;##&lt;/span&gt; &lt;span class="n"&gt;Sample&lt;/span&gt; &lt;span class="k"&gt;Data&lt;/span&gt;

&lt;span class="c1"&gt;-- dimensions&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;dim_customer&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Alice'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Bob'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;dim_channel&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Online'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'In‑store'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- facts&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;fact_sales&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'2026-01-01'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;102&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'2026-01-02'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;103&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'2026-01-03'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="o"&gt;##&lt;/span&gt; &lt;span class="n"&gt;Now&lt;/span&gt; &lt;span class="n"&gt;Enable&lt;/span&gt; &lt;span class="n"&gt;Rely&lt;/span&gt;

&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;dim_customer&lt;/span&gt;
  &lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="n"&gt;RELY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;dim_channel&lt;/span&gt;
  &lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="n"&gt;RELY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;fact_sales&lt;/span&gt;
  &lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="n"&gt;fk_customer&lt;/span&gt; &lt;span class="n"&gt;RELY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;fact_sales&lt;/span&gt;
  &lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="n"&gt;fk_channel&lt;/span&gt; &lt;span class="n"&gt;RELY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you run a query that only aggregates by fact keys:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;channel_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total_amount&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;fact_sales&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;dim_customer&lt;/span&gt; &lt;span class="n"&gt;d_c&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;d_c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;dim_channel&lt;/span&gt; &lt;span class="n"&gt;d_ch&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;channel_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;d_ch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;channel_id&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;channel_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, dim_channel and dim_customer are only used for their keys, which are already in fact_sales. With RELY, Snowflake can understand that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;dim_customer.customer_id is unique and trusted.&lt;/li&gt;
&lt;li&gt;dim_channel.channel_id is unique and trusted.&lt;/li&gt;
&lt;li&gt;The join columns are already present in fact_sales.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So it can eliminate both joins, effectively turning the query into:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff59c1tknvr8qxk7wvrj9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff59c1tknvr8qxk7wvrj9.png" alt=" " width="404" height="510"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How RELY helps performance in practice&lt;/strong&gt;&lt;br&gt;
By enabling RELY on trusted PK/FK constraints, you give the optimizer permission to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Drop unnecessary dimensions from analytical queries that only reference key columns.&lt;/li&gt;
&lt;li&gt;Short‑circuit joins and reduce the number of operators in the plan, which often lowers merge‑join/hash‑join overhead.&lt;/li&gt;
&lt;li&gt;Reduce memory and compute usage, especially for large fact tables that are joined repeatedly in BI or ad‑hoc workloads.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Important caveats and best practices&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Because RELY is a trust‑me hint, a few caveats matter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You are responsible for enforcing UNIQUE, PRIMARY KEY, and FOREIGN KEY constraints. If those constraints are violated, query results with RELY can differ from those with NORELY.&lt;/li&gt;
&lt;li&gt;Always validate data via ETL (e.g., dbt tests) before setting RELY, as violations can lead to suboptimal plans&lt;/li&gt;
&lt;li&gt;Timestamp_tz PKs or hybrid tables limit &lt;a href="https://community.snowflake.com/s/article/No-join-elimination-with-RELY-constraint-on-timestamp-tz-primary-key" rel="noopener noreferrer"&gt;elimination&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dataengineering</category>
      <category>performance</category>
      <category>snowflake</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Part 2: Zero-Copy data federation Snowflake Customer 360 Data to Salesforce Sales Reps</title>
      <dc:creator>Krishna Tangudu</dc:creator>
      <pubDate>Wed, 18 Mar 2026 18:00:00 +0000</pubDate>
      <link>https://dev.to/swaroop_krishna_e2f4b83b2/part-2-reverse-zero-copy-activating-customer-360-in-salesforce-3c2b</link>
      <guid>https://dev.to/swaroop_krishna_e2f4b83b2/part-2-reverse-zero-copy-activating-customer-360-in-salesforce-3c2b</guid>
      <description>&lt;p&gt;In Part 1, we connected Salesforce Data Cloud to Snowflake for analytics. In this blog, you'll flip the direction: surface your Snowflake Customer 360 model (Orders, Service contracts, invoices, Repairs, etc.) directly into Salesforce so your sales and service teams can activate it in real time—without ETL or data duplication.&lt;/p&gt;

&lt;h2&gt;
  
  
  Snowflake ↔ Salesforce Data Cloud ↔ Salesforce CRM
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Snowflake&lt;/strong&gt;: Owns Customer 360 models, health scores, churn risk&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Zero-Copy&lt;/strong&gt;: Data Cloud queries Snowflake live (pushdown SQL)&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Salesforce&lt;/strong&gt;: Activates signals (flows, UI, campaigns) &lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture Pattern
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Your data team builds 'CUSTOMER_360_VIEW' with 'ACCOUNT_ID', 'HEALTH_SCORE', 'CHURN_RISK', 'NEXT_BEST_PRODUCT'
&lt;/li&gt;
&lt;li&gt;Modeled once, governed in Snowflake (roles, masking, row access)
&lt;/li&gt;
&lt;li&gt;Updated via your ELT pipelines (daily/hourly)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1: Building data model in Snowflake
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;
&lt;span class="c1"&gt;-- ============================================================&lt;/span&gt;
&lt;span class="c1"&gt;-- COMPLETE DEMO SCRIPT: Accounts + Customer 360 + Final Table&lt;/span&gt;
&lt;span class="c1"&gt;-- ============================================================&lt;/span&gt;

&lt;span class="c1"&gt;-- Optional: set your context&lt;/span&gt;
&lt;span class="c1"&gt;-- USE ROLE &amp;lt;your_role&amp;gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- USE WAREHOUSE &amp;lt;your_warehouse&amp;gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- USE DATABASE &amp;lt;your_database&amp;gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- USE SCHEMA &amp;lt;your_schema&amp;gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- ------------------------------------------------------------&lt;/span&gt;
&lt;span class="c1"&gt;-- 1) Clean up old objects (safe for reruns)&lt;/span&gt;
&lt;span class="c1"&gt;-- ------------------------------------------------------------&lt;/span&gt;
&lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;ACCOUNT_C360_SNAPSHOT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;CUSTOMER_360_METRICS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;ACCOUNTS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- ------------------------------------------------------------&lt;/span&gt;
&lt;span class="c1"&gt;-- 2) Create source tables&lt;/span&gt;
&lt;span class="c1"&gt;-- ------------------------------------------------------------&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;ACCOUNTS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ACCOUNT_ID&lt;/span&gt; &lt;span class="n"&gt;NUMBER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ACCOUNT_NAME&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;INDUSTRY&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ANNUAL_REVENUE&lt;/span&gt; &lt;span class="n"&gt;NUMBER&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;DELETED_AT&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMP_NTZ&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;CUSTOMER_360_METRICS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ACCOUNT_ID&lt;/span&gt; &lt;span class="n"&gt;NUMBER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;HEALTH_SCORE&lt;/span&gt; &lt;span class="nb"&gt;FLOAT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;CHURN_RISK&lt;/span&gt; &lt;span class="nb"&gt;FLOAT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;NPS_SCORE&lt;/span&gt; &lt;span class="n"&gt;NUMBER&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;ARR&lt;/span&gt; &lt;span class="n"&gt;NUMBER&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;NEXT_BEST_PRODUCT&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;LAST_ENGAGEMENT_DATE&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- ------------------------------------------------------------&lt;/span&gt;
&lt;span class="c1"&gt;-- 3) Insert sample data (toy companies)&lt;/span&gt;
&lt;span class="c1"&gt;-- ------------------------------------------------------------&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;ACCOUNTS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ACCOUNT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ACCOUNT_NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;INDUSTRY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ANNUAL_REVENUE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DELETED_AT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1001&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Bright Blocks Toys'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="s1"&gt;'Toys'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;25000000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1002&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Rocket Wheels Co'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="s1"&gt;'Toys'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="mi"&gt;8000000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1003&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Cuddle Critters Inc'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Toys'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12000000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1004&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Puzzle Planet Ltd'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="s1"&gt;'Toys'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30000000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1005&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Old Toy Factory'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="s1"&gt;'Toys'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="mi"&gt;5000000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'2025-01-15 10:00:00'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Bad Row'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;             &lt;span class="s1"&gt;'Toys'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="mi"&gt;100000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;CUSTOMER_360_METRICS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ACCOUNT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HEALTH_SCORE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CHURN_RISK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NPS_SCORE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ARR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NEXT_BEST_PRODUCT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LAST_ENGAGEMENT_DATE&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1001&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;91&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1800000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'STEM Robot Kit'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="s1"&gt;'2026-03-10'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1002&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;67&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="mi"&gt;650000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Turbo Track Racing Set'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="s1"&gt;'2026-03-05'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1003&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;43&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;81&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="mi"&gt;420000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Deluxe Plush Care Pack'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="s1"&gt;'2026-02-20'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1004&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;55&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2200000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Master Puzzle Collection'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'2026-03-14'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;9999&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;95&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="mi"&gt;900000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Collector Train Set'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="s1"&gt;'2026-03-01'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;-- no matching account&lt;/span&gt;

&lt;span class="c1"&gt;-- ------------------------------------------------------------&lt;/span&gt;
&lt;span class="c1"&gt;-- 4) Create final table using your view definition logic&lt;/span&gt;
&lt;span class="c1"&gt;-- ------------------------------------------------------------&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;ACCOUNT_C360_SNAPSHOT&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ACCOUNT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;UPPER&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ACCOUNT_NAME&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;ACCOUNT_NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INDUSTRY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ANNUAL_REVENUE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;C360&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HEALTH_SCORE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;C360&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CHURN_RISK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;C360&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NPS_SCORE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;C360&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ARR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;C360&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NEXT_BEST_PRODUCT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;C360&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LAST_ENGAGEMENT_DATE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;CASE&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;C360&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HEALTH_SCORE&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'Healthy'&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;C360&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HEALTH_SCORE&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'At Risk'&lt;/span&gt;
        &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="s1"&gt;'Critical'&lt;/span&gt;
    &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;HEALTH_STATUS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;CASE&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;C360&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CHURN_RISK&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'High'&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;C360&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CHURN_RISK&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'Medium'&lt;/span&gt;
        &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="s1"&gt;'Low'&lt;/span&gt;
    &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;CHURN_RISK_LEVEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;LAST_UPDATED&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;ACCOUNTS&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;
&lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;CUSTOMER_360_METRICS&lt;/span&gt; &lt;span class="n"&gt;C360&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ACCOUNT_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;C360&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ACCOUNT_ID&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ACCOUNT_ID&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DELETED_AT&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Data Cloud keeps a **virtual reference&lt;/strong&gt; to Snowflake views—no data duplication, always fresh**&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Connecting Salesforce Data Cloud to Snowflake using zero copy data federation
&lt;/h2&gt;

&lt;p&gt;First, review your network policies and ensure that Salesforce Data Cloud &lt;a href="https://help.salesforce.com/s/articleView?id=data.c360_a_data_cloud_ip_address_allowlist.htm&amp;amp;type=5" rel="noopener noreferrer"&gt;IPs&lt;/a&gt; are included.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Salesforce Data Cloud connection:&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpsojl5fcs491eyc284o7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpsojl5fcs491eyc284o7.png" alt=" " width="800" height="297"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuaiipzojy7rnarrtzr2d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuaiipzojy7rnarrtzr2d.png" alt=" " width="800" height="664"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Make sure the private key is unencrypted and eliminate comments, such as "BEGINNING OF PRIVATE KEY" and "END OF PRIVATE KEY".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create a Snowflake Data Stream in Data Cloud:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsjzkxym2fwram5z15u1n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsjzkxym2fwram5z15u1n.png" alt=" " width="800" height="345"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Select the required object from Snowflake:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzkn35ap33m0r7lu6q4oq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzkn35ap33m0r7lu6q4oq.png" alt=" " width="800" height="161"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Configure with below details:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Object Name&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Customer_360&lt;/td&gt;
&lt;td&gt;Simple, no special chars; this is the internal name Data Cloud uses&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Category&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Accounts&lt;/td&gt;
&lt;td&gt;Helps organize data streams by business domain&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Primary Key&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;ACCOUNT_ID&lt;/td&gt;
&lt;td&gt;The unique identifier that joins to Salesforce Account records&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Organization Unit Identifier&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;ACCOUNT_ID&lt;/td&gt;
&lt;td&gt;(Optional; keep same as Primary Key unless you have a multi-tenant model)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9dskzr0jp1xt3fnx4of6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9dskzr0jp1xt3fnx4of6.png" alt=" " width="800" height="335"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Choose Live/Schedule:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frfm6x9s12z9n4kavkbjp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frfm6x9s12z9n4kavkbjp.png" alt=" " width="800" height="339"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Enable Select Acceleration only when necessary.&lt;/strong&gt;&lt;br&gt;
This approach allows Salesforce to cache data on a scheduled basis instead of querying Snowflake for every request.&lt;br&gt;
When users attempt to load large volumes of data but only select a small subset of columns, querying Snowflake directly can become unnecessarily expensive. Leveraging Select Acceleration in these scenarios helps reduce compute costs and improves query performance by avoiding repeated full-table scans.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fstp1mklzrm11m44n0stz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fstp1mklzrm11m44n0stz.png" alt=" " width="800" height="513"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3. Salesforce as Activation Layer
&lt;/h2&gt;

&lt;p&gt;Sales reps see Snowflake signals in Account/Opportunity pages, flows create tasks automatically&lt;/p&gt;

&lt;p&gt;Maps &lt;code&gt;ACCOUNT_ID&lt;/code&gt; → Data Cloud profile, pulls &lt;code&gt;HEALTH_SCORE&lt;/code&gt;, &lt;code&gt;CHURN_RISK&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Live signals drive real-time actions without swivel-chair between BI and CRM.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitoring:
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Queries fired by Salesforce can be monitored in snowflake in Query History:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnhravdp6oy4ar102cch2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnhravdp6oy4ar102cch2.png" alt=" " width="800" height="247"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Governance &amp;amp; Ownership
&lt;/h2&gt;

&lt;p&gt;Snowflake: Data ownership, model logic, calculations&lt;br&gt;
Data Cloud: Unification, activation logic&lt;br&gt;
Salesforce: UX, workflows, business rules&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key patterns from production&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Snowflake auto-scaling handles Data Cloud query bursts&lt;/li&gt;
&lt;li&gt;Explicit 'GRANT SELECT' on views (not tables) for least privilege&lt;/li&gt;
&lt;li&gt;Data Cloud refresh schedule matches Snowflake pipeline cadence &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Production Impact
&lt;/h2&gt;

&lt;p&gt;Before: Sellers export CSV from Snowflake → manual Salesforce upload&lt;br&gt;
After: Zero-copy signals → automatic tasks → faster CSM response&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next?
&lt;/h2&gt;

&lt;p&gt;Salesforce Data Cloud also provides an option for &lt;a href="https://developer.salesforce.com/docs/data/data-cloud-int/guide/c360-a-set-up-snowflake-file-federation-connection.html" rel="noopener noreferrer"&gt;file‑based federation&lt;/a&gt; when using the Snowflake Polaris Catalog Account.&lt;/p&gt;

&lt;p&gt;This enables data access without continuously querying Snowflake, offering an alternative integration pattern for specific use cases.&lt;/p&gt;

&lt;p&gt;Whether this capability will be extended to the Horizon Catalog remains to be seen—something to watch as the platform evolves.&lt;/p&gt;

</description>
      <category>cloud</category>
      <category>dataengineering</category>
      <category>salesforce</category>
      <category>snowflake</category>
    </item>
    <item>
      <title>Part 1: Zero-Copy Sharing – Salesforce Snowflake for Analytics</title>
      <dc:creator>Krishna Tangudu</dc:creator>
      <pubDate>Thu, 12 Mar 2026 16:00:00 +0000</pubDate>
      <link>https://dev.to/swaroop_krishna_e2f4b83b2/part-1-zero-copy-sharing-salesforce-snowflake-for-analytics-4f0j</link>
      <guid>https://dev.to/swaroop_krishna_e2f4b83b2/part-1-zero-copy-sharing-salesforce-snowflake-for-analytics-4f0j</guid>
      <description>&lt;h2&gt;
  
  
  The ETL Nightmare Ends
&lt;/h2&gt;

&lt;p&gt;Salesforce holds your richest customer data—opportunities, accounts, contacts—but getting it into Snowflake for analytics usually means painful ETL pipelines, schema drift, and maintenance hell. Zero-copy data Federation sharing changes everything: Salesforce publishes secure shares that land in Snowflake as native objects, queryable instantly without duplication or latency.&lt;br&gt;
​&lt;/p&gt;

&lt;p&gt;We are currently in the process of implementing Salesforce Sales Cloud. In this first part of a 2-part series, I'll walk through the exact OAuth setup, common integration traps (network policies! URL underscores!), and best practices for analytics teams.&lt;/p&gt;
&lt;h2&gt;
  
  
  Architecture Overview
&lt;/h2&gt;

&lt;p&gt;Zero-copy sharing flips traditional pipelines: Salesforce Data Cloud becomes the secure publisher, Snowflake the consumer.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Owner&lt;/th&gt;
&lt;th&gt;Contains&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Source&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Salesforce Sales Cloud&lt;/td&gt;
&lt;td&gt;Raw CRM objects (OPPORTUNITY, ACCOUNT, CONTACT)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Share&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Salesforce Data Cloud&lt;/td&gt;
&lt;td&gt;Secure data share with selected objects&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Consumer DB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Snowflake&lt;/td&gt;
&lt;td&gt;Read-only shared objects like SFDC.OPPORTUNITY&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Analytics Layer&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Snowflake&lt;/td&gt;
&lt;td&gt;Views, models, dashboards built on shared data&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Salesforce Datacloud handles object sharing; Snowflake analysts query familiar objects immediately. No Fivetran, no Airflow, no storage costs for duplicates.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step-by-Step Setup
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Before you begin:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Make sure that your Snowflake admin adds the &lt;a href="https://help.salesforce.com/s/articleView?id=data.c360_a_data_cloud_ip_address_allowlist.htm&amp;amp;language=en_US&amp;amp;type=5" rel="noopener noreferrer"&gt;Data 360 IP Addresses&lt;/a&gt; to the allowlist.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://help.salesforce.com/s/articleView?id=data.c360_a_data_shares_aws_supportability_snowflake.htm&amp;amp;type=5" rel="noopener noreferrer"&gt;Supported Regions for Snowflake Integration&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In Snowflake&lt;/strong&gt;&lt;br&gt;
Step 1: Snowflake Security Integration&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;SECURITY&lt;/span&gt; &lt;span class="n"&gt;INTEGRATION&lt;/span&gt; &lt;span class="n"&gt;sf_integration&lt;/span&gt;
  &lt;span class="k"&gt;TYPE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OAUTH&lt;/span&gt;
  &lt;span class="n"&gt;OAUTH_CLIENT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CUSTOM&lt;/span&gt;
  &lt;span class="n"&gt;OAUTH_CLIENT_TYPE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'CONFIDENTIAL'&lt;/span&gt;
  &lt;span class="n"&gt;OAUTH_REDIRECT_URI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'https://test.salesforce.com/services/cdp/SnowflakeOAuthCallback'&lt;/span&gt;
  &lt;span class="n"&gt;ENABLED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;TRUE&lt;/span&gt;
  &lt;span class="n"&gt;OAUTH_ISSUE_REFRESH_TOKENS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;TRUE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Prod tip: Swap test.salesforce.com for login.salesforce.com in production.&lt;br&gt;
​&lt;br&gt;
Step 2: Snowflake Integration User (Not required if the user creating a data target on Salesforce already has a login using Azure or a native user in Snowflake.)&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;USER&lt;/span&gt; &lt;span class="n"&gt;sf_integration_user&lt;/span&gt;
  &lt;span class="n"&gt;LOGIN_NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'sf_integration_user'&lt;/span&gt;
  &lt;span class="n"&gt;PASSWORD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;secure-password&amp;gt;'&lt;/span&gt;
  &lt;span class="n"&gt;DEFAULT_ROLE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'sf_integration_role'&lt;/span&gt;
  &lt;span class="n"&gt;EMAIL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'your-email@company.com'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;Grant&lt;/span&gt; &lt;span class="n"&gt;minimal&lt;/span&gt; &lt;span class="k"&gt;privileges&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;USAGE&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;warehouse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;consumer&lt;/span&gt; &lt;span class="k"&gt;database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Step 3: Client Secrets &amp;amp; Salesforce Config&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;SYSTEM&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;SHOW_OAUTH_CLIENT_SECRETS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'sf_integration'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Copy both client ID and secret to Salesforce Data Cloud. &lt;br&gt;
​&lt;/p&gt;

&lt;p&gt;Step 4: Salesforce Data Target&lt;br&gt;
Salesforce team configures:&lt;/p&gt;

&lt;p&gt;Account URL: &lt;a href="https://my-account-us-east-1.snowflakecomputing.com" rel="noopener noreferrer"&gt;https://my-account-us-east-1.snowflakecomputing.com&lt;/a&gt;&lt;br&gt;
Critical: if your Snowflake URL has underscores (my_account_us-east-1), convert to hyphens (my-account-us-east-1) in the Account URL field.&lt;/p&gt;

&lt;p&gt;Authentication: OAuth (using secrets from step 3)&lt;/p&gt;

&lt;p&gt;Step 5: Publish to Share:&lt;br&gt;
The Salesforce team should create a data stream and a data lake object, then add this object to the created Data Share Target.&lt;/p&gt;

&lt;p&gt;Data Stream ( Address ):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwb7o5bivs1p5nla929ll.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwb7o5bivs1p5nla929ll.png" alt=" " width="565" height="647"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Data Lake Object: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fel12xb7tl6on5zy9ua9d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fel12xb7tl6on5zy9ua9d.png" alt=" " width="661" height="619"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Salesforce publishes share → Snowflake receives:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc1nq8ux16yzgc1a34o5h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc1nq8ux16yzgc1a34o5h.png" alt=" " width="224" height="278"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;sf_shared&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="k"&gt;SHARE&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ShareName&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Integration Issues &amp;amp; Fixes&lt;/strong&gt;&lt;br&gt;
80% of failures happen here. Lessons from this activity:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Network Policies Block Everything&lt;/strong&gt;&lt;br&gt;
Snowflake network policies can silently prevent Salesforce Data cloud IP ranges.&lt;br&gt;
Check: SHOW NETWORK POLICIES;&lt;br&gt;
Fix: Add Salesforce Data cloud IPs&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Account URL Underscores → Hyphens&lt;/strong&gt;&lt;br&gt;
Salesforce rejects Snowflake URLs with _. Always transform:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check Permissions&lt;/strong&gt;&lt;br&gt;
Check the permissions of the user who is trying to establish the connection&lt;/p&gt;

&lt;p&gt;Note: This setup is only required one time.&lt;/p&gt;
&lt;h2&gt;
  
  
  Best Practices for Analytics Teams
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Layered Architecture&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SF_SHARED (raw, read-only)
↓
SF_ANALYTICS (views, materialized)
↓
BI tools (Power BI, Tableau)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Governance&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enable only semantic view access :
grant USAGE on raw share to business users&lt;/li&gt;
&lt;li&gt;Create weekly Alert to monitor Schema drift breaks downstream views
Salesforce owns object changes (new fields, deletions) &lt;/li&gt;
&lt;li&gt;Materialize frequent aggregates like below
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;MATERIALIZED&lt;/span&gt; &lt;span class="k"&gt;VIEW&lt;/span&gt; &lt;span class="n"&gt;analytics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;opp_pipeline_summary&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;stage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;avg_amount&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;sf_shared&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sfdc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;opportunity&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;stage&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;Cost&lt;/span&gt; &lt;span class="n"&gt;Control&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Use multi-cluster warehouses for concurrent BI queries&lt;br&gt;
Monitor: QUERY_HISTORY() for expensive shared table scans&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Take Away:&lt;/strong&gt;&lt;br&gt;
0 TB duplicated storage, 30% faster pipeline deployment, 100% schema fidelity.&lt;/p&gt;

&lt;p&gt;Quick Wins Checklist&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ]  URL underscores → hyphens&lt;/li&gt;
&lt;li&gt;[ ]  Network policy allows Salesforce IPs&lt;/li&gt;
&lt;li&gt;[ ]  Test OAuth token refresh before prod&lt;/li&gt;
&lt;li&gt;[ ]  Raw share → semantic views (don't expose raw)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What's Next (Part 2 Teaser)&lt;br&gt;
This gets Salesforce data into Snowflake seamlessly. In Part 2, we reverse direction: Snowflake Customer 360 data flowing back to sales reps in Salesforce via data federation&lt;/p&gt;
&lt;h2&gt;
  
  
  Quick Start Your Integration
&lt;/h2&gt;

&lt;p&gt;✅ Fork: &lt;a href="https://github.com/LALITHASWAROOPK/salesforce-snowflake-zero-copy" rel="noopener noreferrer"&gt;https://github.com/LALITHASWAROOPK/salesforce-snowflake-zero-copy&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part 2 →&lt;/strong&gt; Customer 360 (Snowflake → Salesforce)&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/swaroop_krishna_e2f4b83b2/part-2-reverse-zero-copy-activating-customer-360-in-salesforce-3c2b" class="crayons-story__hidden-navigation-link"&gt;Part 2: Zero-Copy data federation Snowflake Customer 360 Data to Salesforce Sales Reps&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/swaroop_krishna_e2f4b83b2" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3814074%2F3d7f150a-da32-48c6-9fda-dd6f74b170e2.jpeg" alt="swaroop_krishna_e2f4b83b2 profile" class="crayons-avatar__image" width="800" height="859"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/swaroop_krishna_e2f4b83b2" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Krishna Tangudu
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Krishna Tangudu
                
              
              &lt;div id="story-author-preview-content-3365564" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/swaroop_krishna_e2f4b83b2" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3814074%2F3d7f150a-da32-48c6-9fda-dd6f74b170e2.jpeg" class="crayons-avatar__image" alt="" width="800" height="859"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Krishna Tangudu&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/swaroop_krishna_e2f4b83b2/part-2-reverse-zero-copy-activating-customer-360-in-salesforce-3c2b" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Mar 18&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/swaroop_krishna_e2f4b83b2/part-2-reverse-zero-copy-activating-customer-360-in-salesforce-3c2b" id="article-link-3365564"&gt;
          Part 2: Zero-Copy data federation Snowflake Customer 360 Data to Salesforce Sales Reps
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/cloud"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;cloud&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/dataengineering"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;dataengineering&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/salesforce"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;salesforce&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/snowflake"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;snowflake&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/swaroop_krishna_e2f4b83b2/part-2-reverse-zero-copy-activating-customer-360-in-salesforce-3c2b" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/raised-hands-74b2099fd66a39f2d7eed9305ee0f4553df0eb7b4f11b01b6b1b499973048fe5.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;3&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/swaroop_krishna_e2f4b83b2/part-2-reverse-zero-copy-activating-customer-360-in-salesforce-3c2b#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            4 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;Try this yourself: Share your biggest Salesforce-Snowflake integration pain point in the comments!&lt;/p&gt;

</description>
      <category>snowflake</category>
      <category>salesforce</category>
      <category>datacloud</category>
      <category>zerocopy</category>
    </item>
    <item>
      <title>Setting up Snowflake–Power BI Connectivity with Azure AD SSO and Auto-Provisioning</title>
      <dc:creator>Krishna Tangudu</dc:creator>
      <pubDate>Thu, 12 Mar 2026 14:18:02 +0000</pubDate>
      <link>https://dev.to/swaroop_krishna_e2f4b83b2/setting-up-snowflake-power-bi-connectivity-with-azure-ad-sso-and-auto-provisioning-1gda</link>
      <guid>https://dev.to/swaroop_krishna_e2f4b83b2/setting-up-snowflake-power-bi-connectivity-with-azure-ad-sso-and-auto-provisioning-1gda</guid>
      <description>&lt;h2&gt;
  
  
  Overview:
&lt;/h2&gt;

&lt;p&gt;This post walks through how to enable end-to-end SSO from Power BI to Snowflake using Azure AD, while automatically provisioning users and roles via SCIM. It’s designed for enterprise BI scenarios where analysts connect with DirectQuery or scheduled refresh and you want their Azure AD identity and group membership to control Snowflake access without manual user administration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1. Configure SSO to Snowflake with Azure AD
&lt;/h2&gt;

&lt;p&gt;Step 1.1:  Register Snowflake in Azure AD&lt;br&gt;
Ask your Azure team to register the Snowflake application in Azure AD using these values (replace with your account locator):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Identifier (Entity ID):
https://&amp;lt;account_locator&amp;gt;.snowflakecomputing.com/

Reply URL:
https://&amp;lt;account_locator&amp;gt;.snowflakecomputing.com/fed/login

Sign On URL:
https://&amp;lt;account_locator&amp;gt;.snowflakecomputing.com/

Sign Out URL:
https://&amp;lt;account_locator&amp;gt;.snowflakecomputing.com/fed/logout
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 1.2:  Collect SAML metadata from Azure AD&lt;/p&gt;

&lt;p&gt;&lt;code&gt;EntityID&lt;/code&gt; in the form: &lt;a href="https://sts.windows.net/" rel="noopener noreferrer"&gt;https://sts.windows.net/&lt;/a&gt;/ → use as SAML2_ISSUER&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Location&lt;/code&gt; in the form: &lt;a href="https://login.microsoftonline.com/" rel="noopener noreferrer"&gt;https://login.microsoftonline.com/&lt;/a&gt;/saml2 → use as SAML2_SSO_URL&lt;/p&gt;

&lt;p&gt;&lt;code&gt;X509Certificate&lt;/code&gt; → use as SAML2_X509_CERT&lt;/p&gt;

&lt;p&gt;&lt;code&gt;SAML2_PROVIDER&lt;/code&gt; → set to CUSTOM when Azure AD is the IdP&lt;/p&gt;

&lt;p&gt;Step 1.3: Create the SAML2 integration in Snowflake&lt;/p&gt;

&lt;p&gt;Run as &lt;code&gt;ACCOUNTADMIN&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;USE ROLE ACCOUNTADMIN;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;SECURITY&lt;/span&gt; &lt;span class="n"&gt;INTEGRATION&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AZURE_AD_SSO_NAME&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;TYPE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SAML2&lt;/span&gt;
  &lt;span class="n"&gt;ENABLED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;TRUE&lt;/span&gt;
  &lt;span class="n"&gt;SAML2_ISSUER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;EntityID from metadata&amp;gt;'&lt;/span&gt;
  &lt;span class="n"&gt;SAML2_SSO_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;Location from metadata&amp;gt;'&lt;/span&gt;
  &lt;span class="n"&gt;SAML2_PROVIDER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'CUSTOM'&lt;/span&gt;
  &lt;span class="n"&gt;SAML2_X509_CERT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;X509 certificate&amp;gt;'&lt;/span&gt;
  &lt;span class="n"&gt;SAML2_SP_INITIATED_LOGIN_PAGE_LABEL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'AzureADSSO'&lt;/span&gt;
  &lt;span class="n"&gt;SAML2_ENABLE_SP_INITIATED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;TRUE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This enables browser SSO into Snowflake via Azure AD&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Enable Automatic Provisioning with SCIM
&lt;/h2&gt;

&lt;p&gt;2.1 Create functional Azure groups&lt;br&gt;
Have the support/AD team create AD groups following a functional-role naming convention, for example:&lt;br&gt;
​&lt;br&gt;
DEVELOPER-SNOWFLAKE&lt;br&gt;
ADMIN-SNOWFLAKE&lt;br&gt;
SUPPORT-SNOWFLAKE&lt;br&gt;
​&lt;br&gt;
These group names will become Snowflake role names via SCIM and act as default roles.&lt;/p&gt;

&lt;p&gt;Step 2.2: Create SCIM integration in Snowflake&lt;/p&gt;

&lt;p&gt;Run as ACCOUNTADMIN:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;AAD_PROVISIONER&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;USER&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;ACCOUNT&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="n"&gt;AAD_PROVISIONER&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;ACCOUNT&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="n"&gt;AAD_PROVISIONER&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="n"&gt;AAD_PROVISIONER&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="n"&gt;ACCOUNTADMIN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="n"&gt;AAD_PROVISIONER&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="n"&gt;SYSADMIN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;SECURITY&lt;/span&gt; &lt;span class="n"&gt;INTEGRATION&lt;/span&gt; &lt;span class="n"&gt;AAD_PROVISIONING&lt;/span&gt;
  &lt;span class="k"&gt;TYPE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SCIM&lt;/span&gt;
  &lt;span class="n"&gt;SCIM_CLIENT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'azure'&lt;/span&gt;
  &lt;span class="n"&gt;RUN_AS_ROLE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'AAD_PROVISIONER'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then generate the SCIM access token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;SYSTEM&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;GENERATE_SCIM_ACCESS_TOKEN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'AAD_PROVISIONING'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Token validity is 6 months; you must regenerate it periodically.&lt;/li&gt;
&lt;li&gt;Add monitoring/alerting to renew before expiry&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Step 2.3 Share SCIM details with Azure team&lt;br&gt;
​&lt;br&gt;
Tenant URL:&lt;br&gt;
https://.snowflakecomputing.com/scim/v2/&lt;/p&gt;

&lt;p&gt;Secret Token: The SCIM access token generated above&lt;/p&gt;

&lt;p&gt;They will configure the Snowflake enterprise app in Microsoft Entra ID (Azure AD) for automatic provisioning, following Microsoft’s “&lt;a href="https://learn.microsoft.com/en-us/entra/identity/saas-apps/snowflake-provisioning-tutorial" rel="noopener noreferrer"&gt;Configure Snowflake for automatic user provisioning with Microsoft Entra ID&lt;/a&gt;” tutorial.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 3. Configure SSO from Power BI to Snowflake
&lt;/h2&gt;

&lt;p&gt;Step 3.1: Security Integration creation&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;SECURITY&lt;/span&gt; &lt;span class="n"&gt;INTEGRATION&lt;/span&gt; &lt;span class="n"&gt;CM_SC_&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ACCOUNT_LOCATOR&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;_SSO_POWERBI_SNFK&lt;/span&gt;
  &lt;span class="k"&gt;TYPE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;EXTERNAL_OAUTH&lt;/span&gt;
  &lt;span class="n"&gt;ENABLED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;TRUE&lt;/span&gt;
  &lt;span class="n"&gt;EXTERNAL_OAUTH_TYPE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AZURE&lt;/span&gt;
  &lt;span class="n"&gt;EXTERNAL_OAUTH_ISSUER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;EntityID from Step 2 (https://sts.windows.net/.../)&amp;gt;'&lt;/span&gt;
  &lt;span class="n"&gt;EXTERNAL_OAUTH_JWS_KEYS_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'https://login.windows.net/common/discovery/keys'&lt;/span&gt;
  &lt;span class="n"&gt;EXTERNAL_OAUTH_AUDIENCE_LIST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;'https://analysis.windows.net/powerbi/connector/Snowflake'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'https://analysis.windows.net/powerbi/connector/snowflake'&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;EXTERNAL_OAUTH_TOKEN_USER_MAPPING_CLAIM&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'upn'&lt;/span&gt;
  &lt;span class="n"&gt;EXTERNAL_OAUTH_SNOWFLAKE_USER_MAPPING_ATTRIBUTE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'login_name'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;--UPN in Azure AD matches the login_name or email&lt;/span&gt;

&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;SECURITY&lt;/span&gt; &lt;span class="n"&gt;INTEGRATION&lt;/span&gt; &lt;span class="n"&gt;CM_SC_&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ACCOUNT_LOCATOR&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;_SSO_POWERBI_SNFK&lt;/span&gt;
  &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;EXTERNAL_OAUTH_ANY_ROLE_MODE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'ENABLE'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;-- This allows additional roles&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 3.2 Grant Snowflake access for Power BI users&lt;br&gt;
Grant appropriate warehouse and database access to the Snowflake roles that are created.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;USAGE&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;WAREHOUSE&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;WAREHOUSENAME&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="nv"&gt;"DEVELOPER-SNOWFLAKE"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;USAGE&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DATABASENAME&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="nv"&gt;"DEVELOPER-SNOWFLAKE"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;USAGE&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DATABASENAME&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SCHEMANAME&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="nv"&gt;"DEVELOPER-SNOWFLAKE"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="n"&gt;TABLES&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DATABASENAME&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SCHEMANAME&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="nv"&gt;"DEVELOPER-SNOWFLAKE"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 3.3 Network policy and IP ranges&lt;/p&gt;

&lt;p&gt;If you use Snowflake network policies, ensure the policy allows:&lt;br&gt;
​&lt;br&gt;
Power BI service IP ranges&lt;br&gt;
Azure AD IP ranges&lt;br&gt;
Microsoft publishes updated IP ranges here:&lt;br&gt;
&lt;a href="https://www.microsoft.com/en-us/download/details.aspx?id=56519" rel="noopener noreferrer"&gt;https://www.microsoft.com/en-us/download/details.aspx?id=56519&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Using it from Power BI
&lt;/h2&gt;

&lt;p&gt;Once everything above is configured:&lt;br&gt;
​&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In Power BI (Desktop or Service), use the Snowflake connector.&lt;/li&gt;
&lt;li&gt;Sign in with your Azure AD (organizational) account.&lt;/li&gt;
&lt;li&gt;The connector obtains an Azure AD token, which Snowflake validates via the EXTERNAL_OAUTH integration, mapping the upn claim to the Snowflake login_name.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note: &lt;br&gt;
If connecting using PowerBI Service, please use Server Name as all lower case, otherwise MS PBI interface gives weird errors.&lt;/p&gt;

</description>
      <category>snowflake</category>
      <category>powerplatform</category>
      <category>fabric</category>
      <category>microsoft</category>
    </item>
  </channel>
</rss>
