SQL Schemas
A schema is a folder for database objects. It keeps raw, staging, and reporting tables from colliding, and makes it obvious which is which.
A schema is a named group of objects inside a database. Tables, views, sequences, and macros all live in one. Two schemas can each hold a table called orders without conflict, because the full name of an object includes the schema it sits in.
create schema reporting;
create table reporting.category_revenue as
select p.category_id,
sum(oi.quantity * oi.unit_price) as revenue
from order_items oi
join products p on p.product_id = oi.product_id
group by p.category_id;
select * from reporting.category_revenue
order by revenue desc, category_id;Objects you create without naming a schema go into main, which is where everything on the rest of this site lives.
Creating a schema that already exists is an error, so useCREATE SCHEMA IF NOT EXISTS in any script you expect to run more than once.
Qualified names
A fully qualified name has three parts:database.schema.object. You will mostly write two, and often one.
| Written as | Means |
|---|---|
category_revenue | Look in the current schema. |
reporting.category_revenue | That schema, in the current database. |
memory.reporting.category_revenue | Leaves nothing to interpretation. |
Query an object in another schema without qualifying it and DuckDB reportsTable with name category_revenue does not exist! — usually with aDid you mean "reporting.category_revenue"? hint, which is the fastest way to spot that you forgot the prefix.
Switching schemas
USE changes which schema unqualified names resolve to, so you can stop typing the prefix while working inside one area.
use reporting;
-- no prefix needed now
select * from category_revenue;
-- and back
use memory.main;Tables in main stay reachable while you are in another schema, soUSE adds a place to look rather than cutting you off from the rest of the database. select current_schema() tells you where you are.
USE is a per-connection setting. In the SQLShed playground each Run is a new connection, so a USE from one Run does not carry into the next — put it at the top of the same script that depends on it.
What to name them
The convention most teams land on describes how finished the data is, not who owns it:
| Schema | Holds |
|---|---|
raw | Loaded exactly as it arrived. Never edited by hand. |
staging | Cleaned and typed, not yet joined into anything. |
mart / reporting | What people query. Business names, joins already done. |
sandbox | Whatever someone is trying today. Droppable without asking. |
The value is that the schema name answers "can I trust this table?" before anyone opens it.
Schemas vs databases
A database contains schemas; a schema contains objects. One query can join across schemas in the same database without ceremony. Crossing databases means attaching the other one first, and in DuckDB that is a separateATTACH step. If you are deciding between the two, schemas are almost always what you want — they are lighter, and joins stay simple.
Listing what exists
select schema_name
from duckdb_schemas()
where not internal
order by schema_name;
select table_schema, table_name
from information_schema.tables
where table_schema not in ('information_schema')
order by table_schema, table_name;Cleaning up
Dropping a schema that still contains objects fails with aDependency Error. CASCADE removes the schema and everything inside it, which makes a named schema the tidiest possible sandbox: do the work in it, drop it in one statement, forget nothing.
drop schema sandbox cascadeSee DROP statements for whatCASCADE does and does not reach. You cannot dropmain — DuckDB reports it as an internal system entry.
Schemas are standard, but the layer above them is not. PostgreSQL matches DuckDB closely: databases contain schemas, and cross-database queries need extra machinery. MySQL treats "schema" and "database" as the same thing, so there is no nesting at all. SQL Server puts schemas inside databases inside a server. BigQuery calls the same idea a dataset. The two-partschema.table name is the portable part.
Related
Declare columns, types, defaults, and constraints.
Save a query under a name and reuse it like a table.
Remove tables, views, schemas, and more without breakage.
Scratch tables and views that clean up after themselves.