[{"data":1,"prerenderedAt":111},["ShallowReactive",2],{"qa-\u002Fsql\u002Fschema\u002Fddl":3},{"page":4,"siblings":94,"blog":108},{"id":5,"title":6,"body":7,"description":11,"difficulty":14,"extension":15,"framework":16,"frameworkSlug":17,"meta":18,"navigation":19,"order":12,"path":20,"questions":21,"questionsCount":84,"related":85,"seo":86,"seoDescription":87,"stem":88,"subtopic":89,"topic":90,"topicSlug":91,"updated":92,"__hash__":93},"qa\u002Fsql\u002Fschema\u002Fddl.md","Ddl",{"type":8,"value":9,"toc":10},"minimark",[],{"title":11,"searchDepth":12,"depth":12,"links":13},"",2,[],"medium","md","SQL","sql",{},true,"\u002Fsql\u002Fschema\u002Fddl",[22,27,31,35,39,43,47,51,55,60,64,68,72,76,80],{"id":23,"difficulty":24,"q":25,"a":26},"what-is-ddl","easy","What is DDL and how does it differ from DML?","**DDL** (Data Definition Language) defines and modifies the *structure* of\ndatabase objects — tables, indexes, views, sequences, schemas. The core\nstatements are `CREATE`, `ALTER`, `DROP`, and `TRUNCATE`.\n\n**DML** (Data Manipulation Language) operates on the *data inside* those\nobjects — `SELECT`, `INSERT`, `UPDATE`, `DELETE`.\n\n```sql\n-- DDL: define structure\nCREATE TABLE products (id SERIAL PRIMARY KEY, name TEXT NOT NULL);\nALTER TABLE products ADD COLUMN price NUMERIC(10,2);\nDROP TABLE products;\n\n-- DML: manipulate data\nINSERT INTO products (name, price) VALUES ('Widget', 9.99);\nUPDATE products SET price = 8.99 WHERE id = 1;\nDELETE FROM products WHERE id = 1;\n```\n\n**Rule of thumb:** DDL changes persist after a transaction commits (and in\nmost databases auto-commit immediately). DML changes can be rolled back\nwithin a transaction.\n",{"id":28,"difficulty":24,"q":29,"a":30},"create-table-basics","What are the essential parts of a CREATE TABLE statement?","A `CREATE TABLE` statement names the table and declares each **column** with\nits type and optional constraints. Common additions: a primary key, `NOT NULL`\nmarkers, defaults, and foreign keys.\n\n```sql\nCREATE TABLE orders (\n  id          INT  GENERATED ALWAYS AS IDENTITY PRIMARY KEY,\n  customer_id INT  NOT NULL REFERENCES customers(id),\n  status      TEXT NOT NULL DEFAULT 'pending',\n  total       NUMERIC(10, 2) NOT NULL CHECK (total >= 0),\n  created_at  TIMESTAMPTZ NOT NULL DEFAULT now()\n);\n```\n\nKey parts:\n- **Column name + type** — required for every column.\n- **`NOT NULL`** — rejects `NULL` inserts; always add it unless `NULL` is\n  semantically meaningful.\n- **`DEFAULT`** — value used when the column is omitted in `INSERT`.\n- **`PRIMARY KEY`** — unique non-null identifier; creates an index\n  automatically.\n- **`REFERENCES`** — foreign-key constraint linking to a parent table.\n\n**Rule of thumb:** be explicit about `NOT NULL` and `DEFAULT` on every\ncolumn — relying on implicit NULLability makes the schema ambiguous.\n",{"id":32,"difficulty":14,"q":33,"a":34},"alter-table","What can you do with ALTER TABLE and what are the risks?","`ALTER TABLE` modifies an existing table: add\u002Fdrop\u002Frename columns, change\ntypes, add\u002Fdrop constraints, rename the table itself.\n\n```sql\n-- Add a new column with a default (safe — no full rewrite in Postgres 11+)\nALTER TABLE orders ADD COLUMN shipped_at TIMESTAMPTZ;\n\n-- Drop a column (removes data permanently)\nALTER TABLE orders DROP COLUMN legacy_field;\n\n-- Rename a column\nALTER TABLE orders RENAME COLUMN status TO order_status;\n\n-- Change a type (may rewrite the whole table and lock it)\nALTER TABLE orders ALTER COLUMN total TYPE NUMERIC(14, 4);\n\n-- Add a NOT NULL constraint (safe only if the column has no NULLs)\nALTER TABLE orders ALTER COLUMN shipped_at SET NOT NULL;\n```\n\n**Risks:**\n- Type changes and adding `NOT NULL` to an existing column may **lock the\n  table** and run a full rewrite on large tables.\n- Dropping a column is irreversible without a backup.\n\n**Rule of thumb:** for large production tables, test `ALTER TABLE` on a copy\nfirst; use `CONCURRENTLY` options or online schema change tools (pt-online-schema-change,\ngh-ost) where supported.\n",{"id":36,"difficulty":24,"q":37,"a":38},"drop-vs-truncate","What is the difference between DROP TABLE and TRUNCATE?","- **`DROP TABLE`** removes the table *definition and all its data* permanently.\n  The table no longer exists.\n- **`TRUNCATE`** removes **all rows** from a table but leaves the table\n  structure intact. It resets identity columns\u002Fsequences (in most databases)\n  and is much faster than `DELETE FROM table` because it deallocates data pages\n  rather than deleting row by row.\n\n```sql\n-- Remove the table entirely\nDROP TABLE staging_data;\n\n-- Empty the table but keep its structure\nTRUNCATE TABLE staging_data;\n\n-- TRUNCATE is faster than DELETE for clearing a whole table\n-- DELETE FROM staging_data;  -- slow: generates undo\u002Fredo for every row\n```\n\nIn Postgres, `TRUNCATE` can be inside a transaction and rolled back; in\nMySQL, `TRUNCATE` is DDL and implicitly commits.\n\n**Rule of thumb:** use `TRUNCATE` to reset a staging\u002Ftemp table between\nruns; use `DROP TABLE` only when you no longer need the schema.\n",{"id":40,"difficulty":14,"q":41,"a":42},"schemas-namespacing","What is a SQL schema (namespace) and why use one?","A **schema** is a named namespace inside a database that groups related\ntables, views, functions, and other objects. In Postgres the default schema\nis `public`; SQL Server uses `dbo`.\n\n```sql\n-- Create a schema\nCREATE SCHEMA reporting;\n\n-- Create a table inside it\nCREATE TABLE reporting.monthly_revenue (\n  month  DATE PRIMARY KEY,\n  total  NUMERIC(14, 2) NOT NULL\n);\n\n-- Search path (Postgres): sets which schemas to look in without qualifying\nSET search_path TO reporting, public;\nSELECT * FROM monthly_revenue;  -- resolves to reporting.monthly_revenue\n```\n\nBenefits:\n- Logical grouping (`app`, `reporting`, `audit`).\n- Separate permissions per schema.\n- Avoids name collisions between different subsystems.\n\n**Rule of thumb:** use schemas to separate concerns within a single\ndatabase (e.g., `app` for application tables, `etl` for staging tables,\n`audit` for history).\n",{"id":44,"difficulty":14,"q":45,"a":46},"sequences","What is a sequence and how does it relate to auto-increment columns?","A **sequence** is a database object that generates a monotonically\nincreasing series of integers. Auto-increment columns (`SERIAL`, `IDENTITY`)\nare backed by a sequence internally.\n\n```sql\n-- Explicit sequence (Postgres)\nCREATE SEQUENCE order_id_seq START 1000 INCREMENT 1;\n\n-- Use it as a default\nCREATE TABLE orders (\n  id INT DEFAULT nextval('order_id_seq') PRIMARY KEY\n);\n\n-- Advance and read the current value\nSELECT nextval('order_id_seq');   -- 1000 (first call)\nSELECT currval('order_id_seq');   -- 1000 (same session, same call)\nSELECT lastval();                 -- most recent nextval in this session\n```\n\nSequences are **non-transactional by design** — a rolled-back transaction\nstill consumes a number, so gaps in IDs are normal and expected.\n\n**Rule of thumb:** never rely on sequence values being gap-free; use them\nonly as unique opaque identifiers, not as row-count proxies.\n",{"id":48,"difficulty":14,"q":49,"a":50},"create-index-ddl","What is the DDL to create and drop an index, and when does CREATE INDEX CONCURRENTLY matter?","```sql\n-- Standard index (locks the table for writes during build)\nCREATE INDEX idx_orders_customer ON orders (customer_id);\n\n-- Unique index\nCREATE UNIQUE INDEX idx_users_email ON users (email);\n\n-- Composite index\nCREATE INDEX idx_orders_status_date ON orders (status, created_at DESC);\n\n-- Postgres: build without blocking writes (slower, but safe in production)\nCREATE INDEX CONCURRENTLY idx_orders_total ON orders (total);\n\n-- Drop\nDROP INDEX idx_orders_customer;\nDROP INDEX CONCURRENTLY idx_orders_total;  -- Postgres\n```\n\n`CREATE INDEX CONCURRENTLY` builds the index in multiple passes while the\ntable stays writable, avoiding the write lock. The trade-off: it takes longer\nand cannot be run inside a transaction block.\n\n**Rule of thumb:** always use `CONCURRENTLY` when adding indexes to large\nlive production tables to avoid blocking reads and writes.\n",{"id":52,"difficulty":14,"q":53,"a":54},"temp-tables","What are temporary tables and when should you use them?","A **temporary table** exists only for the duration of a session (or\ntransaction, depending on the database and declaration). It is invisible to\nother sessions and is automatically dropped when the session ends.\n\n```sql\n-- Postgres \u002F SQL Server\nCREATE TEMP TABLE staging_orders AS\n  SELECT * FROM orders WHERE status = 'pending';\n\n-- Manipulate without touching the real table\nUPDATE staging_orders SET status = 'processed';\n\n-- MySQL uses a slightly different syntax\nCREATE TEMPORARY TABLE staging_orders SELECT * FROM orders WHERE status = 'pending';\n```\n\nUse cases:\n- Breaking a complex multi-step ETL into readable steps.\n- Storing an intermediate result that is referenced multiple times (avoids\n  rerunning a slow subquery).\n- Isolating work from other sessions in long-running scripts.\n\n**Rule of thumb:** prefer CTEs for single-query decomposition; use temp\ntables when the intermediate result must be indexed, updated, or reused\nacross multiple queries.\n",{"id":56,"difficulty":57,"q":58,"a":59},"ddl-in-transactions","hard","Can DDL statements be run inside a transaction and rolled back?","It depends on the database:\n\n- **Postgres**: DDL is fully transactional. `CREATE TABLE`, `ALTER TABLE`,\n  `DROP TABLE` inside a `BEGIN` block can be rolled back if the transaction\n  aborts.\n- **MySQL**: DDL auto-commits. Any active transaction is committed before\n  the DDL executes; there is no way to roll back a `CREATE TABLE` in MySQL.\n- **SQL Server**: DDL is transactional (like Postgres).\n\n```sql\n-- Postgres: safe rollback of schema change\nBEGIN;\n  ALTER TABLE orders ADD COLUMN notes TEXT;\n  -- something fails...\nROLLBACK;\n-- The column was never added\n```\n\n**Rule of thumb:** in Postgres and SQL Server, wrap schema migrations in\ntransactions to get atomic, all-or-nothing deploys. In MySQL, deploy each\nDDL statement separately and use idempotent migration scripts (`IF NOT EXISTS`).\n",{"id":61,"difficulty":24,"q":62,"a":63},"if-not-exists","What does CREATE TABLE IF NOT EXISTS do and why is it useful in migrations?","`CREATE TABLE IF NOT EXISTS` creates the table only if no table with that\nname already exists in the current schema. If the table already exists, the\nstatement succeeds silently (no error, no data changed).\n\n```sql\n-- Safe to run multiple times — won't fail on re-run\nCREATE TABLE IF NOT EXISTS audit_log (\n  id         BIGSERIAL PRIMARY KEY,\n  event      TEXT NOT NULL,\n  created_at TIMESTAMPTZ NOT NULL DEFAULT now()\n);\n```\n\nSimilarly: `DROP TABLE IF EXISTS`, `CREATE INDEX IF NOT EXISTS` (Postgres 9.5+).\n\n**Rule of thumb:** use `IF NOT EXISTS` \u002F `IF EXISTS` in every migration\nscript to make migrations **idempotent** — safe to re-run after a partial\nfailure without manual cleanup.\n",{"id":65,"difficulty":24,"q":66,"a":67},"rename-table","How do you rename a table in SQL?","```sql\n-- Postgres \u002F MySQL\nALTER TABLE old_name RENAME TO new_name;\n\n-- SQL Server (stored procedure)\nEXEC sp_rename 'old_name', 'new_name';\n```\n\nRenaming a table does **not** automatically update views, stored procedures,\nor application code that reference the old name. Views in Postgres\nbecome invalid; in SQL Server they may silently continue working via the\ninternal object ID until they are recompiled.\n\n**Rule of thumb:** after renaming a table, search for all references to the\nold name in views, functions, application code, and ORM models and update\nthem in the same migration.\n",{"id":69,"difficulty":57,"q":70,"a":71},"generated-columns","What are generated (computed) columns?","A **generated column** is a column whose value is automatically computed\nfrom other columns. The expression is evaluated by the database, not the\napplication. There are two variants:\n- **`STORED`** — the computed value is physically stored and updated on\n  each write (can be indexed).\n- **`VIRTUAL`** — computed on read, not stored (MySQL and SQL Server support this).\n\n```sql\n-- Postgres (STORED only)\nCREATE TABLE rectangles (\n  width  NUMERIC NOT NULL,\n  height NUMERIC NOT NULL,\n  area   NUMERIC GENERATED ALWAYS AS (width * height) STORED\n);\n\n-- MySQL (VIRTUAL — no storage cost)\nCREATE TABLE rectangles (\n  width  DECIMAL(10,2) NOT NULL,\n  height DECIMAL(10,2) NOT NULL,\n  area   DECIMAL(10,2) AS (width * height) VIRTUAL\n);\n```\n\n**Rule of thumb:** use generated columns for values always derivable from\nother columns (area, full name, tax amount) to keep the value consistent and\navoid application-level bugs from forgetting to update the derived field.\n",{"id":73,"difficulty":57,"q":74,"a":75},"partitioning-ddl","How do you create a partitioned table in Postgres?","**Table partitioning** splits a logically single table into multiple\nphysical storage chunks (partitions) based on a column value. The database\nroutes rows transparently and can prune irrelevant partitions from queries.\n\n```sql\n-- Declarative partitioning (Postgres 10+)\nCREATE TABLE events (\n  id         BIGINT NOT NULL,\n  created_at DATE   NOT NULL,\n  payload    JSONB\n) PARTITION BY RANGE (created_at);\n\n-- Create child partitions (one per quarter)\nCREATE TABLE events_2026_q1\n  PARTITION OF events\n  FOR VALUES FROM ('2026-01-01') TO ('2026-04-01');\n\nCREATE TABLE events_2026_q2\n  PARTITION OF events\n  FOR VALUES FROM ('2026-04-01') TO ('2026-07-01');\n\n-- Queries automatically exclude irrelevant partitions\nSELECT * FROM events WHERE created_at >= '2026-04-01';\n-- Only scans events_2026_q2\n```\n\n**Rule of thumb:** partition very large tables (100 M+ rows) on the column\nmost commonly used in range filters (usually a timestamp). Add indexes on\neach partition individually.\n",{"id":77,"difficulty":14,"q":78,"a":79},"view-ddl","How do you create and replace a view?","A **view** is a named, stored `SELECT` statement. Querying a view executes\nthe underlying query at runtime. Views simplify complex joins, restrict\ncolumn access, or present a stable interface over a changing schema.\n\n```sql\n-- Create a view\nCREATE VIEW active_customers AS\n  SELECT id, name, email\n  FROM customers\n  WHERE deleted_at IS NULL;\n\n-- Query it like a table\nSELECT * FROM active_customers WHERE name ILIKE 'smith%';\n\n-- Replace (redefine) without dropping\nCREATE OR REPLACE VIEW active_customers AS\n  SELECT id, name, email, tier\n  FROM customers\n  WHERE deleted_at IS NULL;\n\n-- Remove\nDROP VIEW active_customers;\n```\n\n**Rule of thumb:** use `CREATE OR REPLACE VIEW` in migrations so\ndependent objects (other views, grants) are preserved. Only `DROP VIEW` when\nyou are removing the view entirely.\n",{"id":81,"difficulty":57,"q":82,"a":83},"materialized-view","What is a materialized view and how does it differ from a regular view?","A **materialized view** (Postgres, Oracle, SQL Server as \"indexed view\") is\na view whose results are **stored on disk** like a table. This makes reads\nfast but the data is stale until explicitly refreshed.\n\n```sql\n-- Create\nCREATE MATERIALIZED VIEW monthly_sales AS\n  SELECT date_trunc('month', created_at) AS month,\n         SUM(total)                      AS revenue\n  FROM orders\n  GROUP BY 1;\n\n-- Refresh (blocks reads in Postgres by default)\nREFRESH MATERIALIZED VIEW monthly_sales;\n\n-- Non-blocking refresh (requires a unique index)\nCREATE UNIQUE INDEX ON monthly_sales (month);\nREFRESH MATERIALIZED VIEW CONCURRENTLY monthly_sales;\n```\n\n**Rule of thumb:** use a materialized view for expensive aggregations or\nreports that can tolerate slightly stale data. Schedule `REFRESH` in a\nbackground job; use `CONCURRENTLY` on large views to avoid read downtime.\n",15,null,{"description":11},"SQL DDL interview questions — CREATE TABLE, ALTER TABLE, DROP, TRUNCATE, sequences, schemas, and safe migration patterns across Postgres, MySQL, and SQL Server.","sql\u002Fschema\u002Fddl","DDL — Creating & Altering Tables","Schema & Data Types","schema","2026-06-20","oV0M-AEXqYY_Ji5SQb-iVOo4R4fkWqua1zr1W0KhBnk",[95,99,100,104],{"subtopic":96,"path":97,"order":98},"Data Types","\u002Fsql\u002Fschema\u002Fdata-types",1,{"subtopic":89,"path":20,"order":12},{"subtopic":101,"path":102,"order":103},"Constraints & Integrity","\u002Fsql\u002Fschema\u002Fconstraints",3,{"subtopic":105,"path":106,"order":107},"Normalization","\u002Fsql\u002Fschema\u002Fnormalization",4,{"path":109,"title":110},"\u002Fblog\u002Fsql-ddl-create-alter-drop","SQL DDL — CREATE, ALTER, and DROP Tables Safely",1782244107122]