Creating SQL Tables and Schemas

1. Creating Tables

ClausePurpose
CREATE TABLEDefine new table
IF NOT EXISTSSkip if already present
AS SELECT (CTAS)Copy structure + data from query
LIKE other_tableClone structure only
INHERITS (Postgres)Table inheritance
PARTITION BYDeclarative partitioning

Example: Comprehensive CREATE TABLE

CREATE TABLE IF NOT EXISTS products (
  id          BIGSERIAL PRIMARY KEY,
  sku         VARCHAR(50) NOT NULL UNIQUE,
  name        TEXT NOT NULL,
  price       NUMERIC(10,2) NOT NULL CHECK (price >= 0),
  category_id BIGINT REFERENCES categories(id) ON DELETE SET NULL,
  attrs       JSONB DEFAULT '{}'::jsonb,
  created_at  TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  updated_at  TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

2. Defining Column Data Types

CategoryPostgresMySQL
IntegerSMALLINT, INT, BIGINT, SERIALTINYINT, INT, BIGINT, AUTO_INCREMENT
DecimalNUMERIC(p,s), REAL, DOUBLE PRECISIONDECIMAL(p,s), FLOAT, DOUBLE
StringVARCHAR(n), TEXT, CHAR(n)VARCHAR(n), TEXT, CHAR(n)
Date/TimeDATE, TIME, TIMESTAMPTZ, INTERVALDATE, TIME, DATETIME, TIMESTAMP
BooleanBOOLEANTINYINT(1) / BOOLEAN
JSONJSONB (binary), JSON (text)JSON
UUIDUUIDCHAR(36) / BINARY(16)
BinaryBYTEABLOB, VARBINARY
Arraytype[]JSON array

3. Setting Column Constraints

ConstraintSyntaxEffect
NOT NULLNOT NULLColumn must have a value
DEFAULTDEFAULT exprAuto-fill on insert
UNIQUEUNIQUENo duplicates (NULLs allowed)
PRIMARY KEYPRIMARY KEYUNIQUE + NOT NULL
CHECKCHECK (expr)Custom validation rule
FOREIGN KEYREFERENCES t(c)Referential integrity
GENERATEDGENERATED ALWAYS ASComputed column

4. Defining Primary Keys

ApproachProsCons
BIGSERIAL / AUTO_INCREMENTCompact, sequentialReveals row counts, hard across shards
UUID v4Globally unique, no central authorityRandom → index bloat
UUID v7 NEWTime-ordered, index-friendlyNewer standard, lib support varies
Snowflake IDSortable, distributedNeeds ID service
Composite KeyNatural for junction tablesLarger FK references

5. Creating Foreign Keys

ActionBehavior on Parent Delete/Update
NO ACTION (default)Error if child rows exist (deferred check)
RESTRICTError immediately
CASCADEDelete/update child rows
SET NULLSet FK to NULL (column must allow NULL)
SET DEFAULTSet FK to DEFAULT value

Example: FK with ON DELETE CASCADE

ALTER TABLE order_items
  ADD CONSTRAINT fk_order
  FOREIGN KEY (order_id) REFERENCES orders(id)
  ON DELETE CASCADE
  ON UPDATE RESTRICT;

6. Adding Check Constraints

PatternExample
RangeCHECK (age BETWEEN 0 AND 150)
Enum-likeCHECK (status IN ('open','closed','pending'))
Regex (Postgres)CHECK (email ~ '^[^@]+@[^@]+\.[^@]+$')
Cross-columnCHECK (end_date > start_date)
JSON validationCHECK (jsonb_typeof(meta->'tags') = 'array')

7. Creating Schemas and Namespaces

OperationSQL
Create schemaCREATE SCHEMA sales;
Drop schemaDROP SCHEMA sales CASCADE;
Qualify namesales.invoices
Set search pathSET search_path TO sales, public;
Grant usageGRANT USAGE ON SCHEMA sales TO role;

8. Altering Tables

OperationSQL
Add columnALTER TABLE t ADD COLUMN c TYPE [DEFAULT v];
Drop columnALTER TABLE t DROP COLUMN c;
Rename columnALTER TABLE t RENAME COLUMN a TO b;
Change typeALTER TABLE t ALTER COLUMN c TYPE NEWTYPE USING expr;
Add constraintALTER TABLE t ADD CONSTRAINT name CHECK (...);
Drop constraintALTER TABLE t DROP CONSTRAINT name;
Rename tableALTER TABLE t RENAME TO new_t;
Warning: In Postgres < 11, adding a NOT NULL column with default rewrites the whole table. PG 11+ avoids the rewrite for constant defaults.

9. Dropping Tables

FormEffect
DROP TABLE t;Error if dependencies exist
DROP TABLE IF EXISTS t;No error if missing
DROP TABLE t CASCADE;Also drops dependent FKs/views
TRUNCATE TABLE t;Empty rows but keep schema (faster than DELETE)

10. Using Temporary Tables

ScopeLifetime
SESSION (default)Until disconnect
TRANSACTIONUntil COMMIT/ROLLBACK (ON COMMIT DROP)
LOCALVisible only to current session
GLOBAL (SQL Server)Visible to all sessions, dropped when last session ends

Example: Transaction-scoped Temp Table

BEGIN;
CREATE TEMP TABLE staging_orders (LIKE orders INCLUDING ALL)
  ON COMMIT DROP;
COPY staging_orders FROM '/tmp/orders.csv' CSV HEADER;
INSERT INTO orders SELECT * FROM staging_orders
  ON CONFLICT (id) DO NOTHING;
COMMIT;