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

Write a Rerunnable Script

An idempotent script leaves the same end state whether you run it once or five times. Getting there is mostly picking the right guard.

A setup script that only works on an empty database is a script you will be afraid to run. An idempotent script is one you can run again without thinking: the second run leaves exactly the state the first one did, and does not stop halfway with "object already exists".

SQL gives you three guards for this, and they are not 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"

This is the guard you want almost everywhere. It redefines the object to whatever the script says, whether or not it already existed, so the script text is the single source of truth for the definition. Views, tables, sequences, and macros all take it.

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 does not 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, becauseCREATE 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 does not survive the object being the wrong type: if a view of that name exists, DuckDB still raisesExisting 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

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 ShopTable · customers9 columns · 20 rows
Table · customers

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

9 columns · 20 rows
customer_id intfirst_name textlast_name textemail textphone textcity textstate textsignup_date dateis_active bool
Your task

Write a script that can run twice. Create a reporting schema if it is not there, then a view reporting.active_customers withcustomer_id, customer_name, and statefor active customers only, then a tablereporting.customers_by_state counting them per state. Finish by selectingstate and customers, most customers first, then state.

SQL Workbench
query.sqlgarden_shop · SQL engine loading
⌘↵ to run
·
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 theDROP statement reference and theSQL schemas reference.