Chapter 11 · Defining Databases and Tables
CREATE DATABASE, CREATE SCHEMA, and Namespaces
A database object lives inside a namespace hierarchy. The exact hierarchy differs by product, so portable design begins by separating the logical ideas—server, database, schema, and object—from each vendor’s syntax.
Learning outcomes
Namespaces answer two questions: where does an object live? and which object does an unqualified name mean? The answers vary across database products, but the design principles are stable.
Distinguish a server or cluster, database, schema, and object name.
Explain why SQLite database files do not implement CREATE SCHEMA.
Create PostgreSQL databases and schemas with explicit ownership.
Use qualified names and search paths without creating ambiguity.
Model isolated namespaces locally with SQLite files and ATTACH DATABASE.
The namespace hierarchy
A fully qualified object name identifies a path through the product’s namespace hierarchy. Not every engine implements every level in the same way.
| Concept | Purpose | Typical examples |
|---|---|---|
| Service / cluster | Running database engine and shared administrative boundary. | PostgreSQL cluster, SQL Server instance, MySQL server. |
| Database / catalog | Large isolation boundary for connections, ownership, backup, and configuration. | academy, analytics, production. |
| Schema | Namespace for related objects inside a database. | public, admissions, reporting. |
| Object | The table, view, index, sequence, routine, or type itself. | reporting.monthly_sales. |
Same words, different products
| Product | Database level | Schema level | Qualification example |
|---|---|---|---|
| PostgreSQL | One cluster contains multiple databases. | Each database contains schemas. | academy.reporting.course_summary |
| SQL Server | An instance contains databases. | Each database contains schemas. | Academy.reporting.CourseSummary |
| MySQL | DATABASE and SCHEMA are synonyms. | No separate schema level beneath a database. | academy.course |
| Oracle | A schema is closely associated with a database user. | Objects belong to the owning schema. | ACADEMY.COURSE |
| SQLite | A database is normally one file attached to a connection. | No CREATE SCHEMA; attached database names act as qualifiers. | analytics.course |
The phrase “create a schema” can mean a new namespace, a new database, or a new user-owned object collection depending on the engine. Always resolve the product’s hierarchy first.
PostgreSQL: create the database boundary
CREATE DATABASE academy WITH OWNER = academy_owner ENCODING = 'UTF8' TEMPLATE = template0;-- Connect to the new database before creating its schemas.-- In psql: \connect academyCREATE DATABASE is normally executed from another database and cannot run inside a transaction block. The database owner can then delegate schema-level privileges more narrowly.
PostgreSQL: create schemas and ownership
CREATE SCHEMA admissions AUTHORIZATION academy_owner;CREATE SCHEMA learning AUTHORIZATION academy_owner;CREATE SCHEMA reporting AUTHORIZATION reporting_owner;CREATE TABLE learning.course ( course_id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, course_code text NOT NULL UNIQUE, title text NOT NULL);CREATE VIEW reporting.course_catalog ASSELECT course_id, course_code, titleFROM learning.course;Schema ownership and object privileges are separate controls. A useful production pattern is to let a deployment role own objects while application roles receive only the privileges they require.
Qualified names and search paths
SELECT course_id, titleFROM learning.course;SET search_path = reporting, learning, pg_catalog;-- Now course_catalog resolves through reporting first.SELECT * FROM course_catalog;An unqualified name is resolved through a search path. That convenience can become a correctness or security risk when multiple schemas contain the same object name or untrusted users can create objects in a searched schema.
Qualified names
Use schema-qualified names in migrations, security-sensitive SQL, and cross-schema interfaces.
Search path
Use a controlled path for interactive work or applications with a single trusted namespace.
Ownership
Assign a stable owner role rather than a personal account.
Privileges
Grant USAGE on the schema and object privileges separately.
SQLite: files and attached namespaces
-- The opened file is qualified as main.CREATE TABLE main.course ( course_id INTEGER PRIMARY KEY, course_code TEXT NOT NULL UNIQUE, title TEXT NOT NULL) STRICT;-- TEMP objects live in the connection-local temp database.CREATE TEMP TABLE import_course ( course_code TEXT, title TEXT);-- Attach another file under a chosen namespace.ATTACH DATABASE 'analytics.db' AS analytics;CREATE TABLE analytics.course_snapshot ASSELECT course_id, course_code, titleFROM main.course;PRAGMA database_list;SQLite resolves unqualified objects among temp, main, and attached databases. Attachments are connection state, not persistent schema declarations inside the main file.
Namespace design rules
| Decision | Prefer | Avoid |
|---|---|---|
| Isolation boundary | Separate database when backup, connection, tenant, or administrative isolation must differ. | Creating databases only to imitate folders. |
| Logical organization | Schemas for stable domains such as learning, billing, and reporting. | A schema per transient feature or developer. |
| Naming | Lowercase, descriptive, durable names that survive team changes. | Personal names, environment suffixes inside every object, and reserved words. |
| References | Explicit qualification at boundaries and in migrations. | Assuming a search path that is not controlled. |
| Security | Owner roles plus least-privilege application roles. | Applications owning production schemas. |
Reusable Chapter 11 practice schema
Run this SQLite script in a disposable database before the hands-on exercises. It establishes a small academic domain with strict tables, generated data, composite uniqueness, foreign keys, a view, and representative rows.
PRAGMA foreign_keys = ON;DROP VIEW IF EXISTS active_course_catalog;DROP TABLE IF EXISTS enrollment;DROP TABLE IF EXISTS course;DROP TABLE IF EXISTS instructor;DROP TABLE IF EXISTS department;CREATE TABLE department ( department_id INTEGER PRIMARY KEY, code TEXT NOT NULL UNIQUE, name TEXT NOT NULL UNIQUE, budget_cents INTEGER NOT NULL DEFAULT 0 CHECK (budget_cents >= 0), created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP) STRICT;CREATE TABLE instructor ( instructor_id INTEGER PRIMARY KEY, department_id INTEGER NOT NULL REFERENCES department(department_id), email TEXT NOT NULL UNIQUE, full_name TEXT NOT NULL, hired_on TEXT NOT NULL CHECK (date(hired_on) IS NOT NULL), active INTEGER NOT NULL DEFAULT 1 CHECK (active IN (0, 1))) STRICT;CREATE TABLE course ( course_id INTEGER PRIMARY KEY, department_id INTEGER NOT NULL REFERENCES department(department_id), instructor_id INTEGER REFERENCES instructor(instructor_id) ON DELETE SET NULL, course_code TEXT NOT NULL, title TEXT NOT NULL, credits INTEGER NOT NULL DEFAULT 3 CHECK (credits BETWEEN 1 AND 6), capacity INTEGER NOT NULL DEFAULT 30 CHECK (capacity > 0), published INTEGER NOT NULL DEFAULT 0 CHECK (published IN (0, 1)), display_name TEXT GENERATED ALWAYS AS (course_code || ' · ' || title) VIRTUAL, created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE (department_id, course_code)) STRICT;CREATE TABLE enrollment ( course_id INTEGER NOT NULL REFERENCES course(course_id) ON DELETE CASCADE, student_id INTEGER NOT NULL, enrolled_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, status TEXT NOT NULL DEFAULT 'enrolled' CHECK (status IN ('enrolled', 'completed', 'withdrawn')), PRIMARY KEY (course_id, student_id)) STRICT, WITHOUT ROWID;CREATE VIEW active_course_catalog ASSELECT c.course_id, d.code AS department_code, c.course_code, c.title, c.credits, c.capacityFROM course AS cJOIN department AS d ON d.department_id = c.department_idWHERE c.published = 1;INSERT INTO department (department_id, code, name, budget_cents) VALUES (1, 'DATA', 'Data Engineering', 25000000), (2, 'CS', 'Computer Science', 30000000);INSERT INTO instructor (instructor_id, department_id, email, full_name, hired_on)VALUES (10, 1, 'nadia@example.edu', 'Nadia Rahimi', '2024-09-01'), (11, 2, 'omar@example.edu', 'Omar Haddad', '2023-02-15');INSERT INTO course (course_id, department_id, instructor_id, course_code, title, credits, capacity, published)VALUES (100, 1, 10, 'SQL-101', 'SQL Foundations', 3, 40, 1), (101, 1, 10, 'DE-201', 'Data Pipelines', 4, 30, 1), (102, 2, 11, 'DB-220', 'Database Systems',4, 35, 0);INSERT INTO enrollment (course_id, student_id, status) VALUES (100, 1001, 'enrolled'), (100, 1002, 'completed'), (101, 1001, 'enrolled');Hands-on: separate operational and reporting data
ATTACH DATABASE ':memory:' AS reporting;CREATE TABLE reporting.course_metrics ( course_id INTEGER PRIMARY KEY, enrolled_count INTEGER NOT NULL, completed_count INTEGER NOT NULL, refreshed_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP) STRICT;INSERT INTO reporting.course_metrics ( course_id, enrolled_count, completed_count)SELECT c.course_id, COUNT(e.student_id), SUM(CASE WHEN e.status = 'completed' THEN 1 ELSE 0 END)FROM main.course AS cLEFT JOIN main.enrollment AS e ON e.course_id = c.course_idGROUP BY c.course_id;SELECT * FROM reporting.course_metrics ORDER BY course_id;DETACH DATABASE reporting;Checkpoint
Resolve the namespace
- Why is a database not merely a folder of tables?
- How does PostgreSQL distinguish a database from a schema?
- What does an SQLite attached database name represent?
- When should a query use schema-qualified names?
- Why is object ownership different from application access?
Review the answers
A database is an administrative and connection boundary. PostgreSQL databases contain schemas; SQLite attachments expose additional files through connection-local qualifiers. Qualification removes ambiguity at important boundaries. Owners control object definitions, while application roles should receive narrowly scoped privileges.
Summary and references
- Namespace levels differ by database product.
- PostgreSQL separates clusters, databases, schemas, and objects.
- SQLite uses database files plus
main,temp, and attached names. - Qualification, ownership, and privilege design are part of schema correctness.