Skip to content
/ Chapter 18 · Modeling and Data Quality
Lesson 18.5 · garden_shop
Lesson 18.5

Write a Rerunnable Script

An idempotent script is designed so repeated runs against the same inputs converge on the same intended state instead of duplicating work or failing on existing objects.

A setup script that only works on an empty database is difficult to operate safely. An idempotent script is designed so that, given the same source inputs, rerunning it converges on the same intended state instead of duplicating work or stopping on "object already exists". Rerunnable doesn't mean risk-free: replacement statements can still overwrite data or definitions.

SQL gives you three guards for this, and they aren't interchangeable. Picking the wrong one is how a script comes to look like it applied a change that it quietly skipped.

CREATE OR REPLACE means "make it match this"

Use this form when replacing the existing object is actually the operation you intend. It redefines the object to match the script whether or not it already existed. In DuckDB, views, tables, sequences, and macros support OR REPLACE, but the consequences differ: replacing a table overwrites the existing table, and replacing an object with dependencies may be blocked or have wider effects.

Redefine every run
create or replace view active_customers as
select customer_id,
       first_name || ' ' || last_name as customer_name
from customers
where is_active

IF NOT EXISTS means "leave whatever is there"

This one doesn't fail on a second run either, but for a different reason: if the object exists, the statement does nothing at all and reports no error. The state column added below never appears, and nothing tells you so.

A silent no-op, not an update
create view if not exists active_customers as
select customer_id,
       first_name || ' ' || last_name as customer_name,
       state
from customers
where is_active

Use it for the containers rather than the contents. A schema is the clear case: it holds no definition of its own, so leaving the existing one alone is exactly right. In DuckDB it is also the only workable choice, because CREATE OR REPLACE SCHEMA refuses to run once the schema holds anything: Cannot drop entry "reporting" because there are entries that depend on it.

DROP IF EXISTS only guards against absence

A cleanup line like drop table if exists active_customers survives the object not being there. It doesn't survive the object being the wrong type: if a view of that name exists, DuckDB still raises Existing object active_customers is of type View, trying to drop type Table, and every statement after it in the script is skipped. Drop the type you actually created.

The shape of a rerunnable script

A useful setup pattern is containers first with IF NOT EXISTS, then the objects with OR REPLACE, then a verification query so the script finishes by showing you what it built rather than leaving you to guess.

Setup, objects, verification
-- 1. containers, guarded by IF NOT EXISTS
create schema if not exists reporting;

-- 2. objects, redefined every run
create or replace view reporting.active_customers as
select customer_id,
       state
from customers
where is_active;

-- 3. verification, so the script ends by proving it worked
select count(*) as active_customers
from reporting.active_customers
Schema · Garden Shop Table · customers 9 columns · 20 rows
Table · customers

One row per customer. Some customers have no phone on file.

9 columns · 20 rows
customer_id int first_name text last_name text email text phone text city text state text signup_date date is_active bool
Your task

Write a script that can run twice. Create a reporting schema if it isn't there, then a view reporting.active_customers with customer_id, customer_name, and state for active customers only, then a table reporting.customers_by_state counting them per state. Finish by selecting state and customers, most customers first, then state.

·

Run your query to inspect the result.

Expected answer
  • Columns: state, customers.
  • Rows: 7, one per state with an active customer.
  • PA leads with 5. Running the whole script a second time returns exactly the same 7 rows.

Run it again. Same seven rows, no errors, nothing duplicated, which is the whole point. For the object-by-object detail, see the DROP statement reference and the SQL schemas reference.

← Previous · 18.4 Stage, clean, and model
End of Chapter 18

That's the last lesson in “Modeling and Data Quality.”

The next chapter builds on what this one covered.

Start Chapter 19: Window Functions in Depth → Begins with 19.1 Ranking and ties