Skip to Content

FAQ

Which warehouses are supported?

Snowflake, BigQuery, Databricks and ClickHouse.

Redshift and Spark lakehouse sources are not supported yet, and Zeotap refuses to create a prepared table on one rather than creating a table it might not be able to maintain. The SQL for both exists and is tested, but this feature’s failure mode is a table in your warehouse, and a test that only compares generated text cannot tell valid SQL from SQL an engine rejects. They will be supported once they have been exercised against real warehouses end to end.

What does it cost to run?

Three things cost warehouse compute, and nothing else does:

  • A build. A full rebuild scans and rewrites the whole table; an incremental build reads and writes only the new window. A build with nothing to do costs one small query — the check for whether the input has moved — and stops there.
  • Profiling. Three queries for an ordinary table, plus up to ten small ones, taken from a sample.
  • Reconcile, when you run one. It rebuilds the table into a scratch copy in order to compare it, so it costs roughly what a full rebuild costs.

Everything else — the freshness indicator, the input-change sensor, input-drift detection, staleness alerting — is either free or reads your warehouse’s catalogue rather than its data.

The two things that most affect cost are the materialization (incremental instead of a full rebuild, where the recipe allows it) and the trigger (building when inputs actually change, instead of on a tight schedule). On BigQuery, the editor shows what a build would scan before you run one.

What happens if the feature is turned off for my workspace?

Scheduling, building and editing stop. Nothing is dropped.

Your prepared tables stay where they are with their data, and a model over one goes on serving audiences and syncs from the last build it got — the UI says the table is no longer refreshing rather than pretending it is. You can still dematerialize a model back onto its own query. Dropping the tables is a separate, explicit act.

Why not just use a view?

Because a view reintroduces exactly the cost this feature exists to remove: the cleaning would run again inside every query that reads it — every audience estimate, every sync, every orchestration tile — which is the situation a prepared table is there to fix.

A view also does not give you a build history, data tests that can refuse to publish, a freshness signal, or anything to reconcile.

Can reverse ETL read a change feed from a prepared table?

Yes, if the table is set up to keep one — and no by default.

By default a model over a prepared table is treated as needing a full comparison on each sync, because a full rebuild replaces the table, which resets its change history: a sync that believed it was reading a continuous feed across a rebuild would miss rows or apply them twice.

Turn on Preserve change feed on an incremental + merge table (Snowflake, BigQuery or Databricks) and a full refresh reconciles the new data into the existing table instead of replacing it, so the feed survives and syncs over it can read one. See Incremental Builds.

A column name came out in a different case than I typed

That is your warehouse’s own rule for a newly created column, and Zeotap records what the warehouse will really call it rather than what you typed.

On Snowflake, a prepared table’s columns are always upper case — CONTACT_ID — whether you aliased the column or passed it straight through from a table that holds contact_id. On BigQuery, Databricks and ClickHouse they keep exactly the names your recipe produced. That is the name you will see in the built table, in the model over it and in an audience filter, so it is the name Zeotap shows you.

Referring to an existing column is forgiving in the other direction: a column name you type into the incremental settings is matched against the real column list case-insensitively, so you can write it in whichever case you have. Only two columns differing only in case are refused — by name, rather than guessed between.

Snowflake: my landing’s columns are lower case. What will the prepared table’s be?

Upper case, and deliberately.

Tables written by a Zeotap loader are created with every column quoted, which on Snowflake makes those columns case-sensitive and lower case: you can only address them as "contact_id". A prepared table built over one publishes CONTACT_ID instead — addressable with no quotes at all from a model, an audience, a trait, a journey and a sync. That is the point of building it: a prepared table is meant to be the clean, conventional table everything else can read, and a column only reachable through exact quoting is not.

Three practical consequences:

  • delete_when is your own SQL over the published columns. Write is_deleted = true, not "is_deleted" = true.
  • A recipe producing two columns that differ only in case is refused — id and ID would become one column. Rename one with an as.
  • An existing table rebuilds in full once, the first time it is built after this behaviour shipped, to republish its columns. The build history says so.

Nothing changes on BigQuery, Databricks or ClickHouse, and the table a recipe READS is untouched: your landing keeps exactly the columns your loader wrote.

Zeotap refused a character in my table name

A database, schema or table name used in a recipe — the primary input, {{source()}}, a typed step’s join or union reference — or named as a profile target may not contain a backtick, a double quote, a single quote, a backslash, a NUL, a newline, a carriage return or a semicolon.

Every one of those is either a quote character or a statement separator on at least one supported warehouse, so an unescaped one would change what the generated query reads rather than just naming a table. No real warehouse identifier needs any of them. The refusal names the character and which part of the reference to edit.

Snowflake says my table does not exist, and I can see that it does

A table or schema name in {{source('schema','table')}} is used exactly as you type it, because it has to mean the same object a model over that table means.

Anything created in Snowflake without quotes is stored in upper case, so {{source('cdp_raw','contacts')}} names a different, non-existent object. Write {{source('CDP_RAW','CONTACTS')}}, or pick the table from a browser — the Input control’s, or a SQL step’s Insert table or column — which always reports the warehouse’s own spelling. The editor raises a warning with the suggested spelling when it sees this, and the same suggestion is appended to your warehouse’s own error.

The mirror image applies inside a SQL step over a table a Zeotap loader wrote: those tables are created with quoted names, so their columns are case-sensitive and lower case, and a SQL step has to write "id" rather than id. A typed step handles it for you.

Why do my loader’s tables have two extra columns?

_ss_loaded_at and _ss_run_id. Zeotap stamps every row a loader lands with when it became visible in your warehouse and which run put it there.

They exist because an incremental build needs to know when a row became visible, and nothing in a landing answered that: a source’s own updated_at is written by the source, arrives out of order, and is sometimes absent or a string.

Rows that landed before this shipped are NULL, which is correct — a first build is a full build and keeps them, and every later window ignores them. A model that selects * over a landing table will see the two new columns.

My incremental table rebuilt in full. Why?

It happens, it is normal, and the build history always says which reason it was: no watermark yet (a first build is always full), the recipe changed, the output columns changed, the table was missing, an upstream table in the same build rebuilt in full, someone asked for a full refresh, the full-refresh schedule fired — or, once per table on Snowflake, the table was built before prepared tables published upper-case column names and had to republish them.

Should I turn off the weekly full refresh?

Only deliberately. It is the one defence against the two kinds of drift incrementality cannot see: a lookup row that changed after the primary row was built, and hard deletes upstream.

If you want to know whether you actually have that drift rather than paper over it, run a reconcile first — it measures it, and a full refresh makes the two sides agree by construction and destroys the evidence.

A build says “Tests failed”. Is that the same as a failure?

No, and the difference is the point.

Failed means your warehouse refused something; a retry may be in order. Tests failed means every statement succeeded, the data was measured against the tests you declared, it was wrong, and the build chose not to publish it. The table still holds exactly what it held before. Retrying will not help — the fix is upstream of Zeotap, in whatever produced the data.

Can I move recipes between workspaces?

Yes. Export produces a portable bundle, and it is closed over its dependencies: exporting a table brings the tables it reads with it, in the right order, because a bundle naming one without the other could not be imported.

Import creates them in the target workspace, each checked against that workspace’s own warehouse, with a dry run first and a choice of what to do about names that already exist. Importing into a different warehouse engine is allowed and warned about, because hand-written SQL steps are specific to one engine — a recipe of typed steps ports cleanly.

Can a prepared table read another prepared table?

Yes, and that is the normal way to build a staging layer and a mart layer. A chain builds in dependency order inside one build, a cycle is refused, and deleting a table something else reads is refused with the names of what reads it.

You can also author a whole chain before any of it has been built.

Next steps

Last updated on