Expedition. Free, virtual, Nov 3–6.

Technical tracks for practitioners, outcomes for leaders.

Snowflake for Developers/Guides/The Merger: Two Order Systems, One Revenue Number with CoCo
Quickstart

The Merger: Two Order Systems, One Revenue Number with CoCo

Snowflake CoCo
James Cha-Earley

Overview

Northbound Coffee just bought Daily Grind. The CEO wants one revenue dashboard by Monday. The problem: the two companies' order systems disagree on almost everything.

In this guide you play the data engineer who has to make the numbers agree. You'll use CoCo (Cortex Code), the AI coding agent built into Snowsight, to compare the two sources, build a pipeline with Dynamic Tables, survive a surprise schema change upstream, and build the CEO's dashboard as a Streamlit app from scratch.

Every step ends with a check query and the exact numbers you should see, so you always know whether you're on track.

Important: CoCo is powered by LLMs and is non-deterministic. The SQL and code it writes may differ from what you see here. That's fine. The check queries read your tables, not CoCo's chat, so they tell you whether the result is right.

What You'll Need

  • A Snowflake account with access to CoCo in Snowsight. A free trial works.
  • The ACCOUNTADMIN role for the one-time setup. Setup creates a lab role, MERGER_ROLE, that you use for everything after that.
  • The companion GitHub repo, which you'll open as a workspace: sfc-gh-JCHAEARLEY/sfguide-merge-two-order-systems-with-cortex-code
  • Nothing to install. Everything runs in your browser.

Using a trial account? AI features, including CoCo, are disabled by default on self-service trial accounts. To turn them on, go to Admin » Billing, open the Snowflake billing tab, and add a credit card. Adding a card doesn't upgrade the trial to a paid account or end the trial period.

What You'll Learn

  • How to use CoCo to compare two messy sources and write a mapping between them
  • How to build a raw → unified → daily-metrics pipeline with Dynamic Tables using CoCo's Plan mode
  • How to diagnose a failed pipeline with Dynamic Table refresh history, and fix it with CoCo
  • How to build a Streamlit app from scratch with CoCo and run it in Snowflake

What You'll Build

  • Two Dynamic Tables, ORDERS_UNIFIED and DAILY_REVENUE, that merge both companies' orders into one trustworthy revenue number
  • A Streamlit dashboard, running in Snowflake, that the CEO can open on Monday

Run sheet

StepWhat happensTime
1Set up: open the repo and run one script5 min
2Discover the data: CoCo compares the sources and writes a mapping10 min
3Build the pipeline: CoCo plans and builds the Dynamic Tables12 min
4The break: an upstream change fails the pipeline, and CoCo fixes it5 min
5Build the dashboard: CoCo writes the Streamlit app13 min

The Scenario

Two companies, two order systems, one board meeting.

Northbound Coffee (the acquirer)Daily Grind (just acquired)
ChannelIn-store registers (POS)Web and app pickup orders
FormatCSV export, loaded into typed columnsOne JSON document per order
TableMERGER_DB.RAW.STORE_SALESMERGER_DB.RAW.WEB_ORDERS
AmountsDollars (TOTAL_USD)Cents (amount_cents)
TimestampsStore-local wall-clock timeUTC (created_at ends in Z)
Store IDsRegister number, like 101Location code, like DG-SF-01
GotchaNone, yetThe order API retried requests, so some orders arrived twice

The one table both companies agree on is MERGER_DB.RAW.STORE_CROSSWALK. It maps each source's store key to a unified STORE_ID, along with each store's name, city, and time zone.

Each of those differences is a way to get the wrong revenue number:

  • Add cents to dollars and Daily Grind looks 100 times bigger than it is.
  • Count orders by UTC date and evening web orders on the West Coast land on the next day.
  • Keep the retried orders and you count revenue that never happened.

Your job is to make the number right, and keep it right.

Set Up Your Environment

Sign in as ACCOUNTADMIN

Sign in to Snowsight. In the user menu in the lower-left corner, switch your role to ACCOUNTADMIN. You need it only for setup.

Create a Git-backed workspace

The companion repo holds every file you'll run in this lab. Open it as a workspace so the files are already in Snowsight.

  1. In the navigation menu, select Projects » Workspaces.
  2. Select the + icon next to Workspaces/Databases, then Create new Git workspace.
  3. Fill out the dialog:
    • Repository URL: https://github.com/sfc-gh-JCHAEARLEY/sfguide-merge-two-order-systems-with-cortex-code
    • Workspace name: anything you like, for example the-merger
    • API integration: select + / Create new and enter:
      • Name: GITHUB_MERGER_LAB
      • Allowed prefixes: https://github.com/sfc-gh-JCHAEARLEY
    • Select Public repository.
  4. Select Create.

The workspace opens with the repo's files in the file explorer on the left:

FileWhat it's for
setup.sqlCreates the lab role, warehouse, database, and source data
checks.sqlOne check block per step, with the expected answers
break_batch.sqlThe surprise schema change in step 4
solutions/Reference answers, if you fall behind

What just happened? The dialog created a Git API integration, a one-time step that tells Snowflake which GitHub accounts workspaces are allowed to clone from.

Run the setup script

Open setup.sql and select Run all. The script:

  • creates MERGER_ROLE and grants it to you, along with the Cortex access CoCo needs
  • creates MERGER_WH, an X-Small warehouse that suspends after 60 seconds
  • creates MERGER_DB and makes MERGER_ROLE its owner
  • switches to MERGER_ROLE and builds the RAW and ANALYTICS schemas and three source tables with 28 days of orders (Sept 1–28, 2026)

The data is generated deterministically, so everyone gets exactly the same rows.

Switch to the lab role

In the user menu in the lower-left corner, switch your role to MERGER_ROLE. CoCo works with whatever role is active in Snowsight, so from here on it can change only the objects the lab created.

CHECK 1

Open checks.sql. You'll run one block from it after each step. Select and run the CHECK 1 block. Every row should say PASS:

check_nameexpected
STORE_CROSSWALK rows10
STORE_SALES rows4,216
WEB_ORDERS documents3,106

Open CoCo

Select the CoCo icon in the lower-right corner of the workspace. You'll use one CoCo conversation for the discovery and pipeline steps, so CoCo can reuse what it learns.

Tip: Type @ in the CoCo message box to add a table as context, for example @MERGER_DB.RAW.WEB_ORDERS.

Discover the Data

Before writing any pipeline, have CoCo study both sources and tell you where they disagree.

Prompt

We just merged two coffee companies and I need one revenue number. Compare the
tables in MERGER_DB.RAW: STORE_SALES (Northbound Coffee in-store POS),
WEB_ORDERS (Daily Grind web and app orders, one JSON document per row in the
RAW column), and STORE_CROSSWALK.

For each source, tell me how it records the order ID, the store, the timestamp
and its time zone, and the amount and its unit. Find anything that would make a
combined revenue number wrong, including duplicate orders. Then write a
column-by-column mapping from both sources into one unified order shape.
Don't create any objects yet.

What CoCo should find

CoCo runs a few exploratory queries, such as sampling rows, flattening the JSON, and counting distinct order IDs, then summarizes. Check that its answer covers all five differences:

  1. Format: STORE_SALES has typed columns, while WEB_ORDERS stores each order as JSON in a VARIANT column.
  2. Units: TOTAL_USD is in dollars, while amount_cents is in cents.
  3. Time zones: SALE_TIMESTAMP is store-local time, while created_at is a UTC string. Each store's time zone is in STORE_CROSSWALK.TIMEZONE.
  4. Store IDs: POS uses register numbers (101–105), web uses location codes (DG-SF-01 and so on), and both map to a STORE_ID through STORE_CROSSWALK.
  5. Duplicates: WEB_ORDERS has 3,106 documents but only 3,040 distinct order_id values. The retried copies share an order_id and have a later LOADED_AT.

If CoCo misses one, ask about it directly, for example: "Are there duplicate order IDs in WEB_ORDERS? What unit is amount_cents in?"

CHECK 2

Run the CHECK 2 block in checks.sql. These numbers should match what CoCo reported:

check_nameexpected
Unique web orders3,040
Duplicate web documents66
Web store codes missing from crosswalk0

Build the Pipeline in Plan Mode

Now turn the mapping into a pipeline. You'll build two Dynamic Tables:

RAW.STORE_SALES ──┐
RAW.WEB_ORDERS  ──┼──► ANALYTICS.ORDERS_UNIFIED ──► ANALYTICS.DAILY_REVENUE ──► dashboard
RAW.STORE_CROSSWALK ┘     (one row per order)        (one row per store per day)

A Dynamic Table is defined by a SELECT. Snowflake keeps it up to date as the source tables change, within the TARGET_LAG you set, and works out the refresh order from the dependency graph. TARGET_LAG = DOWNSTREAM means "refresh only when a table downstream needs fresh data."

Switch to Plan mode

Creating core tables is a good time to review before anything runs. Press Shift+Tab in the CoCo message box to switch to Plan mode. CoCo lays out what it intends to do and waits for your approval before it creates anything.

No Plan mode toggle? Add this line to the start of the prompt below: "Make a plan first and wait for my approval before you create anything."

Prompt

In the same CoCo conversation, send:

Using that mapping, build a Dynamic Table pipeline in MERGER_DB.ANALYTICS that
uses warehouse MERGER_WH. Read from the RAW tables directly.

1. ORDERS_UNIFIED, TARGET_LAG = DOWNSTREAM. One row per order from both sources,
   with exactly these columns: ORDER_ID, STORE_ID, BRAND, CHANNEL ('In-store' or
   'Web'), STORE_NAME, CITY, ORDER_TS_LOCAL, BUSINESS_DATE, ITEM_NAME, QTY,
   AMOUNT_USD.
2. DAILY_REVENUE, TARGET_LAG = '1 hour'. One row per BUSINESS_DATE, STORE_ID,
   BRAND, CHANNEL, STORE_NAME, CITY, with ORDER_COUNT, REVENUE_USD and
   AVG_ORDER_USD.

Business rules:
- All amounts in dollars.
- BUSINESS_DATE is the store's LOCAL date. Convert web UTC timestamps using the
  store's TIMEZONE from STORE_CROSSWALK.
- Count each web order once: keep the copy with the earliest LOADED_AT.
- Get STORE_ID, STORE_NAME and CITY from STORE_CROSSWALK for both sources.

Review the plan, then approve

Before you approve, check that the plan:

  • divides amount_cents by 100
  • uses CONVERT_TIMEZONE with the crosswalk's TIMEZONE to compute the web BUSINESS_DATE
  • deduplicates web orders on order_id, for example with QUALIFY ROW_NUMBER() ... = 1
  • creates ORDERS_UNIFIED before DAILY_REVENUE

If something is off, tell CoCo in plain language before approving. When the plan looks right, approve it and let CoCo create the tables.

Note: Snowflake may set the refresh mode to FULL for ORDERS_UNIFIED because the query unions two sources and deduplicates with a window function. At a few thousand rows, a full refresh takes seconds, so that's expected and fine.

CHECK 3

Run the CHECK 3 block in checks.sql:

check_nameexpected
ORDERS_UNIFIED rows7,256
Orders with no STORE_ID0
DAILY_REVENUE rows (10 stores x 28 days)280
Total revenue (USD)72,702.55
Revenue on Sat Sept 19 (store-local day)3,372.30

That's 4,216 in-store orders plus 3,040 web orders. Northbound brings in $41,789.95 and Daily Grind $30,912.60.

If a number doesn't match, the wrong value usually tells you which rule was missed:

What you seeWhat went wrongAsk CoCo
ORDERS_UNIFIED = 7,322 or total = 73,390.65Retried web orders were counted twice"Deduplicate WEB_ORDERS on order_id, keeping the earliest LOADED_AT."
Total in the millionsCents were added to dollars"amount_cents is in cents. Convert it to dollars."
Sept 19 = 3,115.70Web orders were dated by UTC instead of store-local time"BUSINESS_DATE must be the store's local date. Convert created_at from UTC with STORE_CROSSWALK.TIMEZONE."
Orders with no STORE_ID above 0A source wasn't joined to the crosswalk correctly"Which orders in ORDERS_UNIFIED have no STORE_ID? Fix the crosswalk join."

Ask CoCo to fix the Dynamic Table rather than editing the SQL yourself, then run CHECK 3 again.

Short on time? Open solutions/solution.sql and select Run all to create the reference version of both tables, then continue.

The Monday-Morning Break

It's Monday. Over the weekend, Northbound's POS vendor shipped a "minor" update: the export now calls the dollar column SALE_AMOUNT_USD instead of TOTAL_USD. The first batch in the new format, Tuesday Sept 29, just landed. Then the CEO opens the dashboard.

Run the break

Open break_batch.sql and select Run all. The script:

  1. renames TOTAL_USD to SALE_AMOUNT_USD on RAW.STORE_SALES
  2. inserts 150 sales for Sept 29 using the new column name
  3. asks DAILY_REVENUE for a refresh

The last statement fails. That's the point. You'll see an error like:

Failed to refresh dynamic table ... Some inputs failed to refresh:
[MERGER_DB.ANALYTICS.ORDERS_UNIFIED]. To see the full list of failed DTs,
please query refresh history.

Prompt

Don't go looking for the cause yourself. In CoCo, switch back to normal mode (Shift+Tab) and send:

MERGER_DB.ANALYTICS.DAILY_REVENUE failed to refresh after this morning's POS
batch landed. Use the dynamic table refresh history and the pipeline's upstream
dependencies to find which table failed and why. Then fix the pipeline so it
refreshes again and includes the new batch. Don't change the raw data.

What CoCo should do

  1. Query INFORMATION_SCHEMA.DYNAMIC_TABLE_REFRESH_HISTORY for MERGER_DB.ANALYTICS and find two problems:
    • ORDERS_UNIFIED is FAILED with invalid identifier 'S.TOTAL_USD'.
    • DAILY_REVENUE is UPSTREAM_FAILED: it was skipped because its input failed. The root cause is upstream.
  2. Inspect RAW.STORE_SALES and see that TOTAL_USD is gone and SALE_AMOUNT_USD has replaced it.
  3. Update ORDERS_UNIFIED to read SALE_AMOUNT_USD, then refresh DAILY_REVENUE.

CoCo may use CREATE OR ALTER DYNAMIC TABLE or CREATE OR REPLACE DYNAMIC TABLE for the fix. Both work here. CREATE OR ALTER updates the existing table's definition, while CREATE OR REPLACE rebuilds the table, which makes downstream incremental Dynamic Tables reinitialize. That difference matters more in large pipelines than in this lab.

Tip: You can see the same story in the UI. Go to Transformation » Dynamic tables, open ORDERS_UNIFIED, and look at the Refresh History and Graph tabs.

CHECK 4

Run the CHECK 4 block in checks.sql. Before the fix, every row fails, and the last one shows UPSTREAM_FAILED. After the fix, every row should say PASS:

check_nameexpected
ORDERS_UNIFIED rows7,406
Orders on Tue Sept 29 (new batch)150
Revenue on Tue Sept 29 (USD)1,667.75
Total revenue (USD)74,370.30
Latest DAILY_REVENUE refresh stateSUCCEEDED

Short on time? Open solutions/fix_after_break.sql and select Run all, then continue.

Build the Dashboard

The numbers agree, and the pipeline survives upstream changes. Now give the CEO something to look at.

Create a Streamlit app in your workspace

  1. In the workspace file explorer, select + Add new » Streamlit app. Name it merger_dashboard.
  2. Snowflake creates a folder with starter files, including streamlit_app.py. Open streamlit_app.py.
  3. Open the CoCo panel. Start a new conversation so CoCo focuses on the app.

Prompt

Replace this starter app with a revenue dashboard for our CEO. Read
MERGER_DB.ANALYTICS.DAILY_REVENUE with st.connection("snowflake") and cache the
query result. Include:
- a business-day date range filter and a brand filter
- KPI cards for total revenue, orders, average order value, and number of stores
- a line chart of daily revenue by brand
- a store leaderboard table sorted by revenue
- revenue by channel (In-store vs Web)
Use only streamlit, pandas, and altair, and don't add new packages.

CoCo rewrites streamlit_app.py. Review the changes and accept them.

Run it

Select Run in the action bar at the top of the file. This starts a private preview only you can see. With the full date range and both brands selected, the KPIs should read:

KPIexpected
Revenue$74,370.30
Orders7,406
Average order$10.04
Stores10

Change the filters to explore. Saturdays are busier than weekdays, and Daily Grind's web orders skew toward the evening.

If the app shows an error, paste the error into CoCo and ask it to fix the app. If you're stuck, copy the contents of solutions/streamlit_app.py into your streamlit_app.py.

Optional: deploy it

The preview is private. To share the dashboard, deploy it as a Streamlit object other users can open.

  1. In the project pane toolbar, select Deploy.
  2. Review the settings:
    • Location: MERGER_DB.ANALYTICS
    • Execution: keep the default compute pool, and choose MERGER_WH as the query warehouse. Unless your account has changed it, the default pool is the Snowflake-created SYSTEM_COMPUTE_POOL_CPU.
    • Sharing: optionally add the roles or users who should see the app.
  3. Select Deploy.

Open the deployed app. That's the dashboard the CEO gets on Monday: one revenue number, from two systems that didn't agree on anything.

Clean Up

To stop all credit consumption from this guide, run:

USE ROLE ACCOUNTADMIN;

-- Drops the raw tables, both Dynamic Tables, and the deployed Streamlit app
DROP DATABASE IF EXISTS MERGER_DB;

DROP WAREHOUSE IF EXISTS MERGER_WH;
DROP ROLE IF EXISTS MERGER_ROLE;

-- Only if you created this integration for this lab and don't use it elsewhere
DROP API INTEGRATION IF EXISTS GITHUB_MERGER_LAB;

You can also delete the the-merger workspace from Projects » Workspaces.

Conclusion and Resources

You merged two order systems that disagreed on format, units, time zones, store IDs, and duplicates, and turned them into one revenue number that holds up. Then you kept it right when an upstream change broke the pipeline, and built a dashboard on top of it, without writing the code by hand.

What You Learned

  • Using CoCo to compare messy sources and produce a mapping before you build
  • Building a Dynamic Table pipeline with CoCo's Plan mode, with business rules stated up front
  • Diagnosing FAILED and UPSTREAM_FAILED refreshes from Dynamic Table refresh history, and fixing the root cause with CoCo
  • Building a Streamlit app from scratch with CoCo and running it in a workspace
  • Using check queries with known answers to verify AI-generated work

Related Resources

Code:

Guides:

Documentation:

Updated Nov 6, 2026

This content is provided as is, and is not maintained on an ongoing basis. It may be out of date with current Snowflake instances