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.

Beginner100–125 minutesNamespaces + dialect comparisonLast reviewed: August 2026

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.

01

Distinguish a server or cluster, database, schema, and object name.

02

Explain why SQLite database files do not implement CREATE SCHEMA.

03

Create PostgreSQL databases and schemas with explicit ownership.

04

Use qualified names and search paths without creating ambiguity.

05

Model isolated namespaces locally with SQLite files and ATTACH DATABASE.

The namespace hierarchy

Database service or cluster
Database or catalog
Schema or logical namespace
Table, view, sequence, function

A fully qualified object name identifies a path through the product’s namespace hierarchy. Not every engine implements every level in the same way.

ConceptPurposeTypical examples
Service / clusterRunning database engine and shared administrative boundary.PostgreSQL cluster, SQL Server instance, MySQL server.
Database / catalogLarge isolation boundary for connections, ownership, backup, and configuration.academy, analytics, production.
SchemaNamespace for related objects inside a database.public, admissions, reporting.
ObjectThe table, view, index, sequence, routine, or type itself.reporting.monthly_sales.

Same words, different products

ProductDatabase levelSchema levelQualification example
PostgreSQLOne cluster contains multiple databases.Each database contains schemas.academy.reporting.course_summary
SQL ServerAn instance contains databases.Each database contains schemas.Academy.reporting.CourseSummary
MySQLDATABASE and SCHEMA are synonyms.No separate schema level beneath a database.academy.course
OracleA schema is closely associated with a database user.Objects belong to the owning schema.ACADEMY.COURSE
SQLiteA database is normally one file attached to a connection.No CREATE SCHEMA; attached database names act as qualifiers.analytics.course
Do not transfer syntax mechanically

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

postgresql · administrative connection
CREATE DATABASE academy    WITH OWNER = academy_owner         ENCODING = 'UTF8'         TEMPLATE = template0;-- Connect to the new database before creating its schemas.-- In psql: \connect academy

CREATE 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

postgresql · academy namespaces
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

postgresql · explicit resolution
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.

Explicit

Qualified names

Use schema-qualified names in migrations, security-sensitive SQL, and cross-schema interfaces.

Convenient

Search path

Use a controlled path for interactive work or applications with a single trusted namespace.

Owned

Ownership

Assign a stable owner role rather than a personal account.

Granted

Privileges

Grant USAGE on the schema and object privileges separately.

SQLite: files and attached namespaces

sqlite · main, temp, and attached databases
-- 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

DecisionPreferAvoid
Isolation boundarySeparate database when backup, connection, tenant, or administrative isolation must differ.Creating databases only to imitate folders.
Logical organizationSchemas for stable domains such as learning, billing, and reporting.A schema per transient feature or developer.
NamingLowercase, descriptive, durable names that survive team changes.Personal names, environment suffixes inside every object, and reserved words.
ReferencesExplicit qualification at boundaries and in migrations.Assuming a search path that is not controlled.
SecurityOwner 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.

sqlite · chapter11_setup.sql
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

sqlite · attached reporting namespace
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

  1. Why is a database not merely a folder of tables?
  2. How does PostgreSQL distinguish a database from a schema?
  3. What does an SQLite attached database name represent?
  4. When should a query use schema-qualified names?
  5. 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.

References

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only assets compatible with the Ethereum/ERC-20 network. Do not send TRC-20/TRON assets.