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.
create or replace view active_customers as
select customer_id,
first_name || ' ' || last_name as customer_name
from customers
where is_activeIF 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.
create view if not exists active_customers as
select customer_id,
first_name || ' ' || last_name as customer_name,
state
from customers
where is_activeUse 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.
-- 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_customersSchema · Garden ShopTable · customers9 columns · 20 rows
One row per customer. Some customers have no phone on file.
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.
- 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.
Related
Count rows, nulls, distinct values, and the date range in one query.
Write checks that return nothing when the data is clean.
Remove tables, views, schemas, and more without breakage.
NOT NULL, DEFAULT, CHECK, UNIQUE, and keys.