[{"data":1,"prerenderedAt":340},["ShallowReactive",2],{"topic-sql-schema":3},{"framework":4,"topic":16,"subtopics":24},{"id":5,"description":6,"extension":7,"icon":8,"meta":9,"name":10,"order":11,"slug":12,"stem":13,"tier":14,"__hash__":15},"frameworks\u002Fframeworks\u002Fsql.yml","SQL interview questions on queries, joins and aggregation — essential for every backend, data and analytics interview.","yml","database",{},"SQL",4,"sql","frameworks\u002Fsql",1,"lpzsOj2p9p9W0Tctwc61nP-ulZAA80R5gJiyaZS6ZeI",{"id":17,"description":18,"extension":7,"frameworkSlug":12,"meta":19,"name":20,"order":11,"slug":21,"stem":22,"__hash__":23},"topics\u002Ftopics\u002Fsql-schema.yml","Data types, CREATE\u002FALTER\u002FDROP, constraints and normalization — designing the tables your queries run against.",{},"Schema & Data Types","schema","topics\u002Fsql-schema","1Ub7A8Ja3JDWV7ZB4nWkJo6X-pd9IG0Dx0dLMwnRSvE",[25,114,190,266],{"id":26,"title":27,"body":28,"description":32,"difficulty":35,"extension":36,"framework":10,"frameworkSlug":12,"meta":37,"navigation":38,"order":14,"path":39,"questions":40,"questionsCount":107,"related":108,"seo":109,"seoDescription":110,"stem":111,"subtopic":27,"topic":20,"topicSlug":21,"updated":112,"__hash__":113},"qa\u002Fsql\u002Fschema\u002Fdata-types.md","Data Types",{"type":29,"value":30,"toc":31},"minimark",[],{"title":32,"searchDepth":33,"depth":33,"links":34},"",2,[],"medium","md",{},true,"\u002Fsql\u002Fschema\u002Fdata-types",[41,46,50,54,58,62,66,70,74,78,82,86,90,94,98,102],{"id":42,"difficulty":43,"q":44,"a":45},"why-types-matter","easy","Why does picking the right data type matter?","Choosing the right type affects **storage size**, **query performance**, and\n**data integrity**. A correct type rejects bad data at insert time (the\ndatabase enforces the constraint for free) and lets the engine use internal\noptimizations (integer comparisons are faster than string comparisons; a\n`DATE` column can use date arithmetic natively).\n\n```sql\n-- BAD: storing a price as VARCHAR lets \"abc\" in and breaks SUM()\nprice VARCHAR(20)\n\n-- GOOD: exact numeric, 2 decimal places, always positive\nprice NUMERIC(10, 2) NOT NULL CHECK (price >= 0)\n```\n\n**Rule of thumb:** choose the *narrowest* type that correctly represents\nevery valid value — it saves space, speeds up indexes, and keeps invalid\ndata out automatically.\n",{"id":47,"difficulty":43,"q":48,"a":49},"integer-types","What are the main integer types and when do you choose each?","All major databases offer a family of fixed-size integers:\n\n| Type | Bytes | Range (~) | Use when… |\n|---|---|---|---|\n| `SMALLINT` | 2 | ±32 k | small lookup codes, status flags |\n| `INT` \u002F `INTEGER` | 4 | ±2.1 B | most surrogate keys, counters |\n| `BIGINT` | 8 | ±9.2 × 10¹⁸ | high-volume tables, distributed IDs |\n\n```sql\n-- Postgres auto-increment shorthand\nid SERIAL PRIMARY KEY          -- alias for INT + sequence\nid BIGSERIAL PRIMARY KEY       -- alias for BIGINT + sequence\n\n-- Standard SQL (Postgres 10+, MySQL 8, SQL Server)\nid INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY\n```\n\n**Rule of thumb:** default to `INT` for PKs; switch to `BIGINT` if you\nexpect more than ~1 billion rows or use globally distributed IDs (snowflakes,\nUUIDs stored as numbers).\n",{"id":51,"difficulty":35,"q":52,"a":53},"numeric-vs-float","What is the difference between NUMERIC\u002FDECIMAL and FLOAT\u002FREAL?","**`NUMERIC(p, s)` \u002F `DECIMAL(p, s)`** store exact values using binary-coded\ndecimal arithmetic. They never introduce rounding errors and are required for\nmoney, tax rates, or any value where \"0.10 + 0.20 = 0.30\" must hold exactly.\n\n**`FLOAT` \u002F `REAL` \u002F `DOUBLE PRECISION`** are IEEE-754 floating-point types.\nThey are faster and more compact but introduce tiny rounding errors, making\nthem unsuitable for financial calculations.\n\n```sql\n-- exact: total will always equal the sum of its parts\nprice NUMERIC(12, 4)\n\n-- approximate: fine for sensor readings, ML feature vectors\nlatitude DOUBLE PRECISION\n```\n\n**Rule of thumb:** use `NUMERIC` for money and anything that will be summed\nor compared for equality; use `FLOAT`\u002F`DOUBLE` for scientific measurements\nwhere small rounding is acceptable.\n",{"id":55,"difficulty":43,"q":56,"a":57},"char-vs-varchar-vs-text","What is the difference between CHAR, VARCHAR, and TEXT?","- **`CHAR(n)`** — fixed-length, always `n` characters, right-padded with\n  spaces. Trailing spaces are ignored in comparisons in most databases.\n  Useful only for truly fixed-width codes (country codes, ISO currency codes).\n- **`VARCHAR(n)`** — variable-length up to `n` characters. The limit is a\n  *declaration*; values shorter than `n` use less storage.\n- **`TEXT`** — unlimited-length character string (no declared max). Postgres\n  treats `TEXT` and `VARCHAR` identically at the storage level. MySQL and\n  SQL Server have different performance trade-offs for very large TEXT values.\n\n```sql\ncountry_code CHAR(2)       -- 'US', 'GB' — always exactly 2\nemail        VARCHAR(255)  -- typical cap; protects against runaway input\nbody         TEXT          -- blog post, unlimited\n```\n\n**Rule of thumb:** use `CHAR` only for fixed-width codes, `VARCHAR(n)` for\nfields with a meaningful business-length cap (email, username), and `TEXT`\nfor free-form content.\n",{"id":59,"difficulty":35,"q":60,"a":61},"date-time-types","What date and time types does SQL offer and how do they differ?","| Type | Stores | Timezone-aware? |\n|---|---|---|\n| `DATE` | year-month-day | No |\n| `TIME` | hour-min-sec | No (`TIMETZ` in Postgres) |\n| `TIMESTAMP` | date + time | No |\n| `TIMESTAMPTZ` (Postgres) \u002F `DATETIMEOFFSET` (SQL Server) | date + time | Yes — stored as UTC, displayed in session tz |\n\n```sql\n-- Postgres\ncreated_at  TIMESTAMPTZ NOT NULL DEFAULT now()\nbirth_date  DATE\n\n-- MySQL (no TIMESTAMPTZ; DATETIME is naive, TIMESTAMP is UTC-stored)\ncreated_at  DATETIME(6)  -- 6-digit microsecond precision\n```\n\n**Rule of thumb:** always store timestamps with time-zone awareness\n(`TIMESTAMPTZ` in Postgres, `DATETIMEOFFSET` in SQL Server). Store `DATE`\nalone only when the time component is meaningless (birthdays, holidays).\n",{"id":63,"difficulty":43,"q":64,"a":65},"boolean-type","How do databases handle boolean values?","**Postgres** has a native `BOOLEAN` type that accepts `TRUE`\u002F`FALSE` (and\naliases like `'yes'`\u002F`'no'`, `1`\u002F`0`).\n\n**MySQL** lacks a native boolean — `BOOL`\u002F`BOOLEAN` is an alias for\n`TINYINT(1)`, where `0` = false and any non-zero = true.\n\n**SQL Server** uses `BIT` (0 or 1, no native `TRUE`\u002F`FALSE` literal).\n\n```sql\n-- Postgres\nis_active BOOLEAN NOT NULL DEFAULT TRUE\n\n-- MySQL\nis_active TINYINT(1) NOT NULL DEFAULT 1\n\n-- SQL Server\nis_active BIT NOT NULL DEFAULT 1\n```\n\n**Rule of thumb:** in Postgres use `BOOLEAN`; in MySQL use `TINYINT(1)`;\nin SQL Server use `BIT`. In all cases, enforce `NOT NULL DEFAULT` to keep\nthe flag unambiguous.\n",{"id":67,"difficulty":35,"q":68,"a":69},"null-semantics","What is NULL and how does three-valued logic work in SQL?","`NULL` means **unknown \u002F missing \u002F not applicable** — it is not zero, not\nempty string, not `FALSE`. SQL uses **three-valued logic**: a comparison\ninvolving `NULL` evaluates to `UNKNOWN`, which acts like `FALSE` in `WHERE`\nclauses (the row is excluded).\n\n```sql\n-- These all return UNKNOWN, not TRUE or FALSE:\nNULL = NULL       -- UNKNOWN\nNULL \u003C> 1         -- UNKNOWN\nNULL IS NULL      -- TRUE  ← the correct test\nNULL IS NOT NULL  -- FALSE\n\n-- Practical pitfall: rows where discount IS NULL are excluded\nSELECT * FROM orders WHERE discount \u003C> 0;\n-- Fix:\nSELECT * FROM orders WHERE discount \u003C> 0 OR discount IS NULL;\n```\n\n**Rule of thumb:** never compare with `= NULL`; always use `IS NULL` \u002F\n`IS NOT NULL`. Use `COALESCE(col, default)` to substitute a default before\ncomparison.\n",{"id":71,"difficulty":35,"q":72,"a":73},"uuid-type","What are UUIDs and when should you use them as primary keys?","A **UUID** (Universally Unique Identifier) is a 128-bit value, usually\nwritten as `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`. It is collision-resistant\nwithout a central coordinator, making it ideal for **distributed inserts**\nand **exposing IDs in APIs** (not predictable like an integer sequence).\n\n```sql\n-- Postgres: native UUID type (stored as 16 bytes)\nid UUID PRIMARY KEY DEFAULT gen_random_uuid()\n\n-- MySQL: no native UUID; store as CHAR(36) or BINARY(16)\nid CHAR(36) PRIMARY KEY DEFAULT (UUID())\n```\n\nDownsides: random UUIDs (v4) cause **index fragmentation** because inserts\nscatter across the B-tree. **UUIDv7** (time-ordered) mitigates this.\n\n**Rule of thumb:** prefer integer PKs for internal tables; use UUIDs when\nrows are created across multiple nodes or when IDs are exposed externally and\nmust not be guessable.\n",{"id":75,"difficulty":35,"q":76,"a":77},"json-type","When should you store data as JSON in a relational column?","JSON columns let you persist **semi-structured, schema-flexible** data\n(event payloads, third-party API responses) alongside relational data.\nPostgres's `JSONB` stores a parsed binary representation — indexable with\nGIN, fast to query. MySQL 8+ and SQL Server 2016+ also support JSON but\nstore it as text with helper functions.\n\n```sql\n-- Postgres JSONB with a GIN index for fast containment queries\nCREATE TABLE events (\n  id      BIGSERIAL PRIMARY KEY,\n  payload JSONB NOT NULL\n);\nCREATE INDEX idx_events_payload ON events USING GIN (payload);\n\n-- Query a nested key\nSELECT * FROM events WHERE payload @> '{\"type\": \"click\"}';\n```\n\n**Rule of thumb:** use JSON columns for truly variable structures that would\notherwise require dozens of nullable columns or a separate EAV table. If\nyou find yourself querying the same JSON key in every `WHERE` clause,\nextract it into a proper column.\n",{"id":79,"difficulty":35,"q":80,"a":81},"enum-type","What are ENUM types and what are their trade-offs?","An **`ENUM`** restricts a column to a predefined list of string labels,\nenforcing a domain constraint at the type level. Postgres stores `ENUM` as a\nuser-defined type; MySQL stores it internally as an integer but displays the\nlabel.\n\n```sql\n-- Postgres\nCREATE TYPE order_status AS ENUM ('pending', 'shipped', 'delivered', 'cancelled');\nALTER TABLE orders ADD COLUMN status order_status NOT NULL DEFAULT 'pending';\n\n-- MySQL\nstatus ENUM('pending', 'shipped', 'delivered', 'cancelled') NOT NULL DEFAULT 'pending'\n```\n\nTrade-offs:\n- ✅ Compact storage, enforced domain, readable values.\n- ❌ Adding a new label requires `ALTER TYPE` (Postgres) or `ALTER TABLE` (MySQL), which can lock the table.\n- ❌ Harder to manage via migrations; lookup tables are more flexible.\n\n**Rule of thumb:** use `ENUM` for short, stable lists (\u003C 10 values, rarely\nchanging); use a lookup\u002Freference table when the list is large or frequently\nupdated.\n",{"id":83,"difficulty":35,"q":84,"a":85},"choosing-numeric-precision","How do you choose precision and scale for NUMERIC(p, s)?","`p` = **total significant digits**, `s` = **digits to the right of the\ndecimal point**.\n\n| Value | Type | p | s |\n|---|---|---|---|\n| `12345.67` | price | 7 | 2 |\n| `0.000001` | rate | 7 | 6 |\n| `-9999999.9999` | balance | 11 | 4 |\n\n```sql\n-- Store prices up to $9,999,999.99 with cent precision\nprice NUMERIC(9, 2)\n\n-- Interest rate: 0.0000 – 1.0000 with 4 decimal places\nrate  NUMERIC(5, 4)\n```\n\nPostgres and SQL Server will raise an error if a value exceeds the declared\nprecision. MySQL silently rounds or truncates.\n\n**Rule of thumb:** set `s` to the number of decimal places your business\nlogic requires; set `p` to `s` plus the number of digits you expect to the\nleft of the decimal, then add a few digits of headroom.\n",{"id":87,"difficulty":35,"q":88,"a":89},"serial-vs-identity","What is the difference between SERIAL and GENERATED AS IDENTITY?","Both auto-generate ascending integer PKs, but they differ in SQL standard\ncompliance and control:\n\n- **`SERIAL`** (Postgres-specific) creates a sequence and sets a `DEFAULT\n  nextval(...)` on the column. It is an alias, not a type — the column's real\n  type is `INTEGER`. Users can still `INSERT` an explicit value, bypassing\n  the sequence.\n- **`GENERATED ALWAYS AS IDENTITY`** (SQL:2003 standard, Postgres 10+,\n  SQL Server, MySQL 8+) formally declares the column as identity-generated.\n  `GENERATED ALWAYS` prevents manual inserts; `GENERATED BY DEFAULT` allows\n  them.\n\n```sql\n-- Old Postgres style\nid SERIAL PRIMARY KEY\n\n-- Standard SQL (preferred)\nid INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY\n```\n\n**Rule of thumb:** prefer `GENERATED ALWAYS AS IDENTITY` for new schemas —\nit is portable and prevents accidental sequence skips from manual inserts.\n",{"id":91,"difficulty":35,"q":92,"a":93},"storing-money","What is the best way to store monetary values in SQL?","Use **`NUMERIC(p, 2)`** (or a higher scale for currencies with sub-cent\nprecision). Never use `FLOAT` — floating-point arithmetic makes `0.10 + 0.20`\nequal `0.30000000000000004`, which causes reconciliation errors.\n\nSome teams store money as a `BIGINT` of the smallest unit (cents, pence) and\nconvert to a decimal only in the application layer — this avoids any numeric\ntype ambiguity.\n\n```sql\n-- Option 1: NUMERIC column in dollars\namount NUMERIC(12, 2) NOT NULL\n\n-- Option 2: BIGINT in cents (no decimal at all)\namount_cents BIGINT NOT NULL  -- 1099 = $10.99\n```\n\n**Rule of thumb:** use `NUMERIC` in SQL; if you also need speed at very\nhigh throughput, use `BIGINT` cents and divide by 100 in the application.\nDocument which approach you use in the column comment.\n",{"id":95,"difficulty":43,"q":96,"a":97},"binary-types","When would you store binary data in a SQL column?","Binary columns (`BYTEA` in Postgres, `VARBINARY`\u002F`BLOB` in MySQL\u002FSQL Server)\nstore raw byte sequences — images, PDFs, encrypted values, hashes.\n\nIn practice, storing **large blobs directly in the database** bloats the\ntable, slows backups, and is rarely optimal compared to object storage (S3,\nGCS) with only a URL or key in the DB.\n\n```sql\n-- Appropriate: cryptographic hash (fixed 32 bytes)\npassword_hash BYTEA NOT NULL   -- bcrypt\u002Fargon2 output\n\n-- Appropriate: small thumbnail thumbnail (\u003C 64 KB)\navatar_thumb  BYTEA\n\n-- Avoid: storing full-resolution images in the DB\n-- Store the S3 key instead:\navatar_s3_key VARCHAR(500)\n```\n\n**Rule of thumb:** store binary data in the database only when it is small\n(\u003C 1 MB), must be transactionally consistent with other columns, or access\npatterns demand it. Otherwise, use object storage and keep a reference key.\n",{"id":99,"difficulty":35,"q":100,"a":101},"implicit-vs-explicit-casting","What is implicit type casting and why can it be dangerous?","**Implicit casting** (coercion) happens when the database silently converts\na value from one type to another to satisfy a comparison or expression. This\ncan cause **index scans to degrade into full-table scans** if the cast\nprevents the engine from using the index on the original column.\n\n```sql\n-- Table: users(id INT, phone VARCHAR(20))\n-- phone has an index.\n\n-- BAD: implicit cast of the integer literal to VARCHAR\n-- Some databases may cast the column instead, killing the index\nSELECT * FROM users WHERE phone = 12345;\n\n-- GOOD: explicit cast or string literal\nSELECT * FROM users WHERE phone = '12345';\n-- or\nSELECT * FROM users WHERE phone = CAST(12345 AS VARCHAR);\n```\n\n**Rule of thumb:** always compare like types. Mismatched types in `WHERE`\npredicates are a common source of unexpected full-table scans — check with\n`EXPLAIN` when in doubt.\n",{"id":103,"difficulty":104,"q":105,"a":106},"array-types","hard","When should you use array columns (Postgres) instead of a child table?","Postgres supports **`ARRAY` columns** that hold a list of any base type\n(`INTEGER[]`, `TEXT[]`, `UUID[]`). They can save a join for read-heavy\ndenormalized patterns but lose referential integrity and are harder to\nindex and update partially.\n\n```sql\n-- Array column: fast read, no join, but no FK enforcement\nCREATE TABLE articles (\n  id   SERIAL PRIMARY KEY,\n  tags TEXT[] NOT NULL DEFAULT '{}'\n);\nCREATE INDEX idx_articles_tags ON articles USING GIN (tags);\n\nSELECT * FROM articles WHERE tags @> ARRAY['sql', 'indexing'];\n\n-- Child table: full relational integrity, flexible queries\nCREATE TABLE article_tags (\n  article_id INT REFERENCES articles(id),\n  tag        TEXT NOT NULL,\n  PRIMARY KEY (article_id, tag)\n);\n```\n\n**Rule of thumb:** use arrays when the list is small, ordered, read far more\nthan written, and does not need referential integrity or per-element queries.\nUse a child table when you need FK constraints, ordering, or per-row metadata.\n",16,null,{"description":32},"SQL data types interview questions — numeric, character, date\u002Ftime, boolean, JSON, NULL semantics, and how to choose the right type across Postgres, MySQL, and SQL Server.","sql\u002Fschema\u002Fdata-types","2026-06-20","JffWumXQ_4Kaqd6EvuOyCKiWfy7w6VhFSYtqDFgQngk",{"id":115,"title":116,"body":117,"description":32,"difficulty":35,"extension":36,"framework":10,"frameworkSlug":12,"meta":121,"navigation":38,"order":33,"path":122,"questions":123,"questionsCount":184,"related":108,"seo":185,"seoDescription":186,"stem":187,"subtopic":188,"topic":20,"topicSlug":21,"updated":112,"__hash__":189},"qa\u002Fsql\u002Fschema\u002Fddl.md","Ddl",{"type":29,"value":118,"toc":119},[],{"title":32,"searchDepth":33,"depth":33,"links":120},[],{},"\u002Fsql\u002Fschema\u002Fddl",[124,128,132,136,140,144,148,152,156,160,164,168,172,176,180],{"id":125,"difficulty":43,"q":126,"a":127},"what-is-ddl","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":129,"difficulty":43,"q":130,"a":131},"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":133,"difficulty":35,"q":134,"a":135},"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":137,"difficulty":43,"q":138,"a":139},"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":141,"difficulty":35,"q":142,"a":143},"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":145,"difficulty":35,"q":146,"a":147},"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":149,"difficulty":35,"q":150,"a":151},"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":153,"difficulty":35,"q":154,"a":155},"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":157,"difficulty":104,"q":158,"a":159},"ddl-in-transactions","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":161,"difficulty":43,"q":162,"a":163},"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":165,"difficulty":43,"q":166,"a":167},"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":169,"difficulty":104,"q":170,"a":171},"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":173,"difficulty":104,"q":174,"a":175},"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":177,"difficulty":35,"q":178,"a":179},"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":181,"difficulty":104,"q":182,"a":183},"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,{"description":32},"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","oV0M-AEXqYY_Ji5SQb-iVOo4R4fkWqua1zr1W0KhBnk",{"id":191,"title":192,"body":193,"description":32,"difficulty":35,"extension":36,"framework":10,"frameworkSlug":12,"meta":197,"navigation":38,"order":198,"path":199,"questions":200,"questionsCount":184,"related":108,"seo":261,"seoDescription":262,"stem":263,"subtopic":264,"topic":20,"topicSlug":21,"updated":112,"__hash__":265},"qa\u002Fsql\u002Fschema\u002Fconstraints.md","Constraints",{"type":29,"value":194,"toc":195},[],{"title":32,"searchDepth":33,"depth":33,"links":196},[],{},3,"\u002Fsql\u002Fschema\u002Fconstraints",[201,205,209,213,217,221,225,229,233,237,241,245,249,253,257],{"id":202,"difficulty":43,"q":203,"a":204},"what-is-constraint","What is a constraint and why use one?","A **constraint** is a rule enforced by the database engine that limits\nwhat values can be stored in a column or set of columns. Constraints catch\nbad data at write time — before it ever enters the database — so application\ncode never has to defensively re-validate what the schema already guarantees.\n\n```sql\nCREATE TABLE employees (\n  id         INT  GENERATED ALWAYS AS IDENTITY PRIMARY KEY, -- unique, not null\n  email      TEXT NOT NULL UNIQUE,                          -- no nulls, no dupes\n  salary     NUMERIC(10,2) CHECK (salary > 0),             -- must be positive\n  dept_id    INT  REFERENCES departments(id)               -- must exist in parent\n);\n```\n\n**Rule of thumb:** encode every invariant you can in the schema. A constraint\nthat runs in 0 ms at insert time is cheaper than debugging corrupt data in\nproduction.\n",{"id":206,"difficulty":43,"q":207,"a":208},"primary-key","What is a PRIMARY KEY constraint?","A **PRIMARY KEY** uniquely identifies each row in a table. It is a\ncombination of two implied constraints: `UNIQUE` (no two rows share the same\nvalue) and `NOT NULL` (the key column(s) can never be `NULL`). Each table\ncan have **at most one** primary key.\n\n```sql\n-- Single-column PK\nCREATE TABLE users (\n  id   INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,\n  name TEXT NOT NULL\n);\n\n-- Composite PK (natural key)\nCREATE TABLE order_items (\n  order_id   INT NOT NULL,\n  product_id INT NOT NULL,\n  quantity   INT NOT NULL,\n  PRIMARY KEY (order_id, product_id)\n);\n```\n\nThe database automatically creates a **unique index** on the PK column(s),\nwhich makes lookups by PK fast.\n\n**Rule of thumb:** every table should have a primary key. Prefer a single\nsurrogate integer\u002FUUID PK; use composite PKs for pure join tables\n(many-to-many mappings).\n",{"id":210,"difficulty":43,"q":211,"a":212},"foreign-key","What is a FOREIGN KEY constraint and what does it enforce?","A **FOREIGN KEY** (FK) constraint ensures that every non-NULL value in the\nreferencing column exists in the referenced column of the parent table.\nIt enforces **referential integrity** — you cannot have an `order` that\npoints to a `customer_id` that does not exist.\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  total       NUMERIC(10,2) NOT NULL\n);\n\n-- Explicit form with named constraint\nALTER TABLE orders\n  ADD CONSTRAINT fk_orders_customer\n  FOREIGN KEY (customer_id) REFERENCES customers(id);\n```\n\nOn insert, the DB checks that `customer_id` exists in `customers.id`. On\ndelete of a customer row, the FK's **referential action** decides what\nhappens (see CASCADE \u002F RESTRICT question).\n\n**Rule of thumb:** always add FK constraints on foreign-key columns — they\nare the database's guarantee that your data is consistent, not just a\ndocumentation comment.\n",{"id":214,"difficulty":35,"q":215,"a":216},"referential-actions","What are ON DELETE \u002F ON UPDATE referential actions on a foreign key?","Referential actions define what happens to child rows when the parent row\nis deleted or its PK updated:\n\n| Action | Effect on child rows |\n|---|---|\n| `RESTRICT` | Raises an error — cannot delete\u002Fupdate if children exist |\n| `NO ACTION` | Like RESTRICT but checked at end of statement (default) |\n| `CASCADE` | Deletes (or updates) all matching child rows automatically |\n| `SET NULL` | Sets the FK column(s) to `NULL` in child rows |\n| `SET DEFAULT` | Sets the FK column(s) to their default value |\n\n```sql\nCREATE TABLE order_items (\n  id         INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,\n  order_id   INT NOT NULL\n    REFERENCES orders(id) ON DELETE CASCADE,  -- items gone when order deleted\n  product_id INT NOT NULL\n    REFERENCES products(id) ON DELETE RESTRICT -- block if items still reference product\n);\n```\n\n**Rule of thumb:** use `ON DELETE CASCADE` for owned child records (items,\nline-items, comments). Use `RESTRICT` for shared reference data you never\nwant to accidentally wipe. Avoid `SET NULL` unless `NULL` is semantically\nmeaningful in the child.\n",{"id":218,"difficulty":43,"q":219,"a":220},"unique-constraint","What is a UNIQUE constraint and how does it differ from a PRIMARY KEY?","A **UNIQUE** constraint ensures that no two rows have the same value(s) in\nthe constrained column(s). Unlike `PRIMARY KEY`, a table can have **multiple**\nunique constraints, and unique columns **can** contain `NULL` (Postgres and\nSQL Server treat each `NULL` as distinct; MySQL treats `NULL = NULL`).\n\n```sql\nCREATE TABLE users (\n  id       INT  GENERATED ALWAYS AS IDENTITY PRIMARY KEY,\n  email    TEXT NOT NULL UNIQUE,\n  username TEXT NOT NULL UNIQUE,\n  phone    TEXT UNIQUE          -- nullable; two users can have NULL phone\n);\n\n-- Multi-column unique constraint\nALTER TABLE team_members\n  ADD CONSTRAINT uq_team_user UNIQUE (team_id, user_id);\n```\n\nA `UNIQUE` constraint creates a **unique index** automatically, so it also\nspeeds up equality lookups on those columns.\n\n**Rule of thumb:** add `UNIQUE` to every natural business key (email,\nusername, SSN) in addition to the surrogate PK — it is the database's\nguarantee that your deduplication logic is not bypassed.\n",{"id":222,"difficulty":35,"q":223,"a":224},"check-constraint","What is a CHECK constraint and what can it express?","A **CHECK** constraint specifies a boolean expression that every row must\nsatisfy. The insert or update is rejected if the expression evaluates to\n`FALSE`. (`NULL` evaluates to `UNKNOWN` and is allowed through by default.)\n\n```sql\nCREATE TABLE products (\n  id       INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,\n  price    NUMERIC(10,2) NOT NULL CHECK (price >= 0),\n  discount NUMERIC(5,2)  CHECK (discount BETWEEN 0 AND 100),\n  status   TEXT NOT NULL CHECK (status IN ('draft', 'active', 'archived'))\n);\n\n-- Cross-column check\nALTER TABLE bookings\n  ADD CONSTRAINT chk_dates CHECK (check_out > check_in);\n```\n\nLimitations: `CHECK` expressions cannot reference other tables (use triggers\nor application logic for cross-table validation). Some databases (MySQL pre-8.0)\nparsed but silently ignored `CHECK` constraints.\n\n**Rule of thumb:** use `CHECK` to enforce domain rules on a single row\n(non-negative price, valid status enum, date ordering). Prefer an `ENUM` type\nor a FK to a lookup table when the valid set of values is managed by\nthe business and may change.\n",{"id":226,"difficulty":43,"q":227,"a":228},"not-null-constraint","What does NOT NULL enforce and when should you omit it?","`NOT NULL` prevents the column from storing a `NULL`. An attempt to insert\nor update a row with `NULL` in that column raises an error.\n\n```sql\nCREATE TABLE events (\n  id          BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,\n  name        TEXT    NOT NULL,            -- must always be present\n  description TEXT,                        -- optional (nullable)\n  occurred_at TIMESTAMPTZ NOT NULL DEFAULT now()\n);\n```\n\nAdd `NOT NULL` when:\n- The value is required for every row to be meaningful.\n- You need aggregate functions (like `SUM`, `AVG`) to behave predictably\n  (`NULL` is skipped by aggregates).\n\nOmit `NOT NULL` (allow `NULL`) when:\n- The value is genuinely optional (\"middle name\", \"description\").\n- You want to distinguish \"not yet provided\" from a default value.\n\n**Rule of thumb:** default to `NOT NULL`; make a column nullable only when\nthe absence of a value has a distinct meaning you intend to query for.\n",{"id":230,"difficulty":104,"q":231,"a":232},"deferrable-constraints","What are deferrable constraints and when do you need them?","By default, constraints are checked **immediately** after each statement.\nA **deferrable constraint** can be postponed until `COMMIT`, which is\nnecessary when two statements in the same transaction temporarily violate\nthe constraint before reaching a consistent state.\n\n```sql\n-- Postgres: declare the FK as deferrable\nALTER TABLE nodes\n  ADD CONSTRAINT fk_parent\n  FOREIGN KEY (parent_id) REFERENCES nodes(id)\n  DEFERRABLE INITIALLY DEFERRED;\n\n-- Now you can insert in any order within a transaction\nBEGIN;\n  INSERT INTO nodes (id, parent_id) VALUES (2, 1);  -- parent 1 doesn't exist yet\n  INSERT INTO nodes (id, parent_id) VALUES (1, NULL);\nCOMMIT;  -- FK checked here — both rows now exist, so it passes\n```\n\nCommon use case: self-referential trees, circular FK graphs, or bulk\nimports where you cannot guarantee insert order.\n\n**Rule of thumb:** use `DEFERRABLE INITIALLY DEFERRED` for FK constraints\nin self-referential or circular relationship tables. Leave constraints\n`NOT DEFERRABLE` by default — immediate checking catches bugs faster.\n",{"id":234,"difficulty":104,"q":235,"a":236},"exclusion-constraint","What is an exclusion constraint in Postgres?","An **exclusion constraint** (Postgres-specific) generalizes `UNIQUE` to\narbitrary operators, not just equality. It ensures that for any two rows,\nat least one of the specified conditions is false. The classic use case is\npreventing **overlapping time ranges**.\n\n```sql\n-- Requires btree_gist extension for mixed operator support\nCREATE EXTENSION IF NOT EXISTS btree_gist;\n\nCREATE TABLE room_bookings (\n  room_id    INT  NOT NULL,\n  reserved   TSTZRANGE NOT NULL,  -- time range type\n  EXCLUDE USING GIST (\n    room_id  WITH =,              -- same room\n    reserved WITH &&              -- overlapping periods\n  )\n);\n\n-- This pair of inserts will succeed\nINSERT INTO room_bookings VALUES (1, '[2026-06-01, 2026-06-03)');\nINSERT INTO room_bookings VALUES (1, '[2026-06-05, 2026-06-07)');\n\n-- This overlaps and will fail the exclusion constraint\nINSERT INTO room_bookings VALUES (1, '[2026-06-02, 2026-06-06)');\n```\n\n**Rule of thumb:** use exclusion constraints for scheduling, resource\nallocation, or any domain where overlapping intervals must be prevented.\nThey are more expressive than triggers for this pattern.\n",{"id":238,"difficulty":35,"q":239,"a":240},"naming-constraints","Why should you name your constraints explicitly?","When you omit a name, the database generates one automatically\n(`orders_customer_id_fkey` in Postgres, a UUID-based name in SQL Server).\nUnnamed constraints are hard to reference in migrations, error messages, and\napplication code.\n\n```sql\n-- Anonymous (avoid)\nALTER TABLE orders ADD FOREIGN KEY (customer_id) REFERENCES customers(id);\n\n-- Named (preferred)\nALTER TABLE orders\n  ADD CONSTRAINT fk_orders_customer\n  FOREIGN KEY (customer_id) REFERENCES customers(id);\n\n-- Now you can drop it cleanly\nALTER TABLE orders DROP CONSTRAINT fk_orders_customer;\n```\n\nA consistent naming convention (`fk_\u003Ctable>_\u003Ccol>`, `uq_\u003Ctable>_\u003Ccol>`,\n`chk_\u003Ctable>_\u003Crule>`) makes the schema self-documenting and makes migration\nscripts reliable across environments.\n\n**Rule of thumb:** always name constraints explicitly with a predictable\nconvention. Anonymous constraints force you to query the system catalog every\ntime you need to alter or drop one.\n",{"id":242,"difficulty":104,"q":243,"a":244},"partial-unique-index","What is a partial unique index and when does it replace a constraint?","A **partial unique index** applies the uniqueness guarantee only to rows\nthat satisfy a `WHERE` condition. This is useful when uniqueness should apply\nonly to a subset of rows — for example, only *active* records.\n\n```sql\n-- Only one active record per user (soft-delete pattern)\nCREATE UNIQUE INDEX uq_subscriptions_active_user\n  ON subscriptions (user_id)\n  WHERE cancelled_at IS NULL;\n\n-- Two rows for user 1, one cancelled and one active — both allowed\nINSERT INTO subscriptions (user_id, cancelled_at) VALUES (1, '2025-01-01');\nINSERT INTO subscriptions (user_id, cancelled_at) VALUES (1, NULL);  -- OK\n\n-- A second active row for user 1 — fails\nINSERT INTO subscriptions (user_id, cancelled_at) VALUES (1, NULL);  -- ERROR\n```\n\n**Rule of thumb:** use a partial unique index when the uniqueness rule\napplies to a subset of rows (e.g., non-deleted, active-status). Full `UNIQUE`\nconstraints cannot express this; partial indexes can.\n",{"id":246,"difficulty":35,"q":247,"a":248},"disable-enable-constraint","Can you disable a constraint temporarily and should you?","In **SQL Server** you can `DISABLE` a foreign key or check constraint and\nlater `ENABLE` it, optionally with `WITH NOCHECK` (skip validation of\nexisting data) or `WITH CHECK` (validate existing data on re-enable).\n\nIn **Postgres**, you can `SET CONSTRAINTS ALL DEFERRED` for the current\ntransaction, or `ALTER TABLE … DISABLE TRIGGER ALL` to disable trigger-based\nconstraints. You can also drop and recreate constraints for bulk loads.\n\nIn **MySQL**, `SET FOREIGN_KEY_CHECKS = 0` disables FK checks session-wide.\n\n```sql\n-- SQL Server bulk load workaround\nALTER TABLE orders NOCHECK CONSTRAINT fk_orders_customer;\nBULK INSERT orders FROM 'orders.csv';\nALTER TABLE orders WITH CHECK CHECK CONSTRAINT fk_orders_customer;\n\n-- MySQL bulk load\nSET FOREIGN_KEY_CHECKS = 0;\nLOAD DATA INFILE 'orders.csv' INTO TABLE orders;\nSET FOREIGN_KEY_CHECKS = 1;\n```\n\n**Rule of thumb:** disabling constraints is acceptable for controlled bulk\nloads, but always re-enable and validate immediately. Never disable\nconstraints permanently — you will eventually have corrupt data.\n",{"id":250,"difficulty":35,"q":251,"a":252},"index-vs-constraint","How does a UNIQUE constraint relate to a unique index?","A `UNIQUE` constraint is enforced by a **unique index** under the hood.\nIn Postgres and SQL Server, a `UNIQUE` constraint and a `CREATE UNIQUE INDEX`\non the same column are nearly equivalent — the constraint simply gives the\nindex a constraint-associated name.\n\n```sql\n-- These two are functionally equivalent in Postgres:\nALTER TABLE users ADD CONSTRAINT uq_users_email UNIQUE (email);\n\nCREATE UNIQUE INDEX uq_users_email ON users (email);\n```\n\nDifferences in Postgres:\n- Constraints can be `DEFERRABLE`; indexes cannot.\n- You can `DROP CONSTRAINT` but must `DROP INDEX` for a standalone index.\n- Partial unique indexes cannot be declared as a `UNIQUE` constraint\n  (you need the `CREATE UNIQUE INDEX … WHERE` form).\n\n**Rule of thumb:** use the `CONSTRAINT … UNIQUE` form when you need\ndeferred checking or want the DDL to be clearly declarative. Use\n`CREATE UNIQUE INDEX` when you need a partial unique condition.\n",{"id":254,"difficulty":43,"q":255,"a":256},"constraint-violations-errors","What error do you get when a constraint is violated, and how do you handle it?","Each constraint violation raises a specific error that application code can\ncatch and present meaningfully:\n\n| Constraint | Postgres SQLSTATE | Typical message |\n|---|---|---|\n| NOT NULL | 23502 | null value in column \"x\" violates not-null constraint |\n| UNIQUE | 23505 | duplicate key value violates unique constraint \"uq_…\" |\n| FOREIGN KEY | 23503 | insert or update on table \"x\" violates foreign key constraint |\n| CHECK | 23514 | new row for relation \"x\" violates check constraint \"chk_…\" |\n\n```python\n# Python + psycopg2 example\nfrom psycopg2 import errors\ntry:\n    cur.execute(\"INSERT INTO users (email) VALUES (%s)\", (email,))\nexcept errors.UniqueViolation:\n    return {\"error\": \"That email is already registered\"}\n```\n\n**Rule of thumb:** catch constraint violations by **SQLSTATE code**, not by\nparsing the error message string — message text can change between database\nversions. Map each violation to a user-friendly message in the application layer.\n",{"id":258,"difficulty":43,"q":259,"a":260},"table-vs-column-constraint","What is the difference between a column-level and a table-level constraint?","A **column-level constraint** is declared inline with the column definition\nand can only reference that single column. A **table-level constraint** is\ndeclared separately (after all column definitions) and can reference multiple\ncolumns.\n\n```sql\nCREATE TABLE order_items (\n  order_id   INT NOT NULL,               -- column-level NOT NULL\n  product_id INT NOT NULL,               -- column-level NOT NULL\n  quantity   INT NOT NULL CHECK (quantity > 0),  -- column-level CHECK\n\n  -- Table-level: composite PK (must reference both columns — impossible column-level)\n  PRIMARY KEY (order_id, product_id),\n\n  -- Table-level: cross-column check\n  CONSTRAINT chk_valid_quantity CHECK (quantity \u003C= 1000 OR order_id \u003C 1000)\n);\n```\n\n**Rule of thumb:** use column-level constraints for single-column rules\n(NOT NULL, UNIQUE, CHECK on one column). Use table-level constraints\nfor composite keys, composite unique indexes, and cross-column CHECK\nexpressions.\n",{"description":32},"SQL constraints interview questions — PRIMARY KEY, FOREIGN KEY, UNIQUE, CHECK, NOT NULL, referential actions, deferrable constraints, and constraint best practices across Postgres, MySQL, and SQL Server.","sql\u002Fschema\u002Fconstraints","Constraints & Integrity","rq8jkLiLHCb4HPydx8hdz0jqAzhIQYLbWY0jcAKimbE",{"id":267,"title":268,"body":269,"description":32,"difficulty":35,"extension":36,"framework":10,"frameworkSlug":12,"meta":273,"navigation":38,"order":11,"path":274,"questions":275,"questionsCount":184,"related":108,"seo":336,"seoDescription":337,"stem":338,"subtopic":268,"topic":20,"topicSlug":21,"updated":112,"__hash__":339},"qa\u002Fsql\u002Fschema\u002Fnormalization.md","Normalization",{"type":29,"value":270,"toc":271},[],{"title":32,"searchDepth":33,"depth":33,"links":272},[],{},"\u002Fsql\u002Fschema\u002Fnormalization",[276,280,284,288,292,296,300,304,308,312,316,320,324,328,332],{"id":277,"difficulty":43,"q":278,"a":279},"what-is-normalization","What is database normalization and why does it matter?","**Normalization** is the process of organizing a relational schema to\n**reduce data redundancy** and **prevent update anomalies**. The theory\nwas introduced by E.F. Codd and is expressed as a series of **normal forms**\n(1NF, 2NF, 3NF, BCNF …) — each one stricter than the last.\n\n```sql\n-- Unnormalized: storing multiple values in one cell (violates 1NF)\n-- orders: | id | customer | items              |\n-- data:   | 1  | Alice    | 'Pen, Pencil, Pad' |\n\n-- After normalization: separate tables, one fact per cell\n-- orders: | id | customer_id |\n-- order_items: | order_id | product_id | qty |\n```\n\nBenefits: no redundant copies to keep in sync, constraints are enforceable,\nqueries are composable.\n\n**Rule of thumb:** normalize to at least **3NF** for transactional\n(OLTP) schemas; selectively denormalize for read-heavy reporting (OLAP) only\nwhen profiling proves it necessary.\n",{"id":281,"difficulty":35,"q":282,"a":283},"update-anomalies","What are the three update anomalies normalization prevents?","Un-normalized schemas suffer from three anomalies that make data unreliable:\n\n1. **Insertion anomaly** — you cannot store a fact without also storing\n   another unrelated fact. E.g., you cannot record a new department unless\n   you also have an employee for it.\n2. **Update anomaly** — the same fact appears in multiple rows. Changing a\n   manager's name requires updating every row for every employee in that\n   department. Miss one row → inconsistent data.\n3. **Deletion anomaly** — deleting a row removes more facts than intended.\n   Delete the last employee in a department and you lose the department's\n   name\u002Flocation too.\n\n```sql\n-- Bad: employee table stores department info in every row\n-- | emp_id | emp_name | dept_id | dept_name  | dept_location |\n-- If you rename the dept, you must update EVERY employee row.\n\n-- Fixed (normalized): dept facts live in one place\nCREATE TABLE departments (id INT PRIMARY KEY, name TEXT, location TEXT);\nCREATE TABLE employees (id INT PRIMARY KEY, name TEXT, dept_id INT REFERENCES departments(id));\n```\n\n**Rule of thumb:** if the same value must be updated in more than one row\nto keep the data consistent, you have a normalization problem.\n",{"id":285,"difficulty":43,"q":286,"a":287},"first-normal-form","What is First Normal Form (1NF)?","A table is in **1NF** when:\n1. Every column contains **atomic** (indivisible) values — no sets, lists,\n   or repeating groups inside a single cell.\n2. Every column contains values of a **single type**.\n3. Each row is **uniquely identifiable** (there is a primary key).\n\n```sql\n-- Violates 1NF: multiple phone numbers in one column\n-- | id | name  | phones               |\n-- | 1  | Alice | '555-1234, 555-5678' |\n\n-- 1NF compliant: separate table for multi-valued attribute\nCREATE TABLE contacts (id INT PRIMARY KEY, name TEXT);\nCREATE TABLE contact_phones (\n  contact_id INT REFERENCES contacts(id),\n  phone      TEXT NOT NULL,\n  PRIMARY KEY (contact_id, phone)\n);\n```\n\n**Rule of thumb:** if a cell contains comma-separated values or you find\nyourself doing `LIKE '%value%'` to search within a column, the table\nviolates 1NF and needs to be split.\n",{"id":289,"difficulty":35,"q":290,"a":291},"functional-dependency","What is a functional dependency?","A **functional dependency** (FD) `A → B` means that knowing the value of\n`A` uniquely determines the value of `B`. In a table, a functional\ndependency is a constraint on which combinations of values are valid.\n\n```\n-- In an orders table:\norder_id → customer_id      (each order has exactly one customer)\norder_id → order_date\n(order_id, product_id) → quantity   (composite key determines quantity)\n\n-- Problematic FD in an unnormalized table:\ndept_id → dept_name         (department name depends only on dept_id,\n                             not on the full PK of the employee row)\n```\n\nUnderstanding FDs is the foundation of 2NF and 3NF: each normal form\nremoves a class of problematic FDs from the schema.\n\n**Rule of thumb:** draw out the FDs before designing a schema. Every\nnon-key column should depend on **the whole key and nothing but the key**.\n(This is essentially the definition of 3NF in plain English.)\n",{"id":293,"difficulty":35,"q":294,"a":295},"second-normal-form","What is Second Normal Form (2NF) and what does it fix?","A table is in **2NF** when it is in 1NF and every non-key column is\n**fully functionally dependent on the whole primary key** — not just part\nof it. 2NF only matters when the PK is composite.\n\n```sql\n-- Violates 2NF: PK is (order_id, product_id) but product_name\n-- depends only on product_id (partial dependency)\n-- | order_id | product_id | product_name | quantity |\n\n-- Fix: split product facts into their own table\nCREATE TABLE products (\n  id   INT PRIMARY KEY,\n  name TEXT NOT NULL\n);\nCREATE TABLE order_items (\n  order_id   INT REFERENCES orders(id),\n  product_id INT REFERENCES products(id),\n  quantity   INT NOT NULL,\n  PRIMARY KEY (order_id, product_id)\n);\n```\n\n**Rule of thumb:** if any non-key column depends on *part* of a composite\nprimary key, move those columns to a table where that partial key *is* the\nfull primary key.\n",{"id":297,"difficulty":35,"q":298,"a":299},"third-normal-form","What is Third Normal Form (3NF) and what does it eliminate?","A table is in **3NF** when it is in 2NF and **no non-key column determines\nanother non-key column** (no transitive dependencies).\n\n```sql\n-- Violates 3NF: zip_code → city (transitive: emp_id → zip_code → city)\n-- | emp_id | name  | zip_code | city     |\n\n-- Fix: move the transitive dependency to its own table\nCREATE TABLE zip_codes (zip TEXT PRIMARY KEY, city TEXT NOT NULL);\nCREATE TABLE employees (\n  id       INT PRIMARY KEY,\n  name     TEXT NOT NULL,\n  zip_code TEXT REFERENCES zip_codes(zip)\n);\n```\n\nThe classic mnemonic: **\"Every non-key attribute must depend on the key,\nthe whole key, and nothing but the key — so help me Codd.\"**\n\n**Rule of thumb:** if changing one non-key value (like a zip code) should\nautomatically update another non-key value (the city), those values belong\nin a separate table joined by a foreign key.\n",{"id":301,"difficulty":104,"q":302,"a":303},"bcnf","What is Boyce-Codd Normal Form (BCNF) and how does it differ from 3NF?","**BCNF** (sometimes called 3.5NF) is a stricter version of 3NF. A table\nis in BCNF if, for every non-trivial functional dependency `A → B`, `A`\nis a **superkey** (a set of columns that uniquely identifies a row).\n\nBCNF and 3NF differ only when a table has **multiple overlapping candidate\nkeys**. 3NF allows a non-key column to determine part of another candidate\nkey; BCNF does not.\n\n```\n-- Classic BCNF violation: Tutors(student, subject, tutor)\n-- Candidate keys: (student, subject) and (student, tutor)\n-- FD: tutor → subject (a tutor teaches exactly one subject)\n-- This violates BCNF because 'tutor' is not a superkey.\n\n-- Fix (decompose):\n-- TutorSubject(tutor PK, subject)\n-- StudentTutor(student, tutor, FK tutor → TutorSubject)\n```\n\n**Rule of thumb:** BCNF matters in schemas with multiple candidate keys.\nIn practice, 3NF is the target for most applications; BCNF is pursued when\nredundancy in multi-key tables causes real anomalies.\n",{"id":305,"difficulty":35,"q":306,"a":307},"denormalization","What is denormalization and when is it justified?","**Denormalization** intentionally introduces redundancy into a schema to\nimprove **read performance** — typically by precomputing joins or\naggregations and caching their results in additional columns or tables.\n\n```sql\n-- Normalized: count must be computed with a JOIN every time\nSELECT u.id, COUNT(o.id) AS order_count\nFROM users u LEFT JOIN orders o ON o.user_id = u.id\nGROUP BY u.id;\n\n-- Denormalized: cache the count in the users table\nALTER TABLE users ADD COLUMN order_count INT NOT NULL DEFAULT 0;\n\n-- Maintain via trigger or application logic on each insert\u002Fdelete\nUPDATE users SET order_count = order_count + 1 WHERE id = NEW.user_id;\n```\n\nTrade-offs:\n- ✅ Faster reads, simpler queries, reduced join cost.\n- ❌ Write complexity — must keep redundant copies in sync.\n- ❌ Risk of inconsistency if update logic is missed.\n\n**Rule of thumb:** normalize first; denormalize only after profiling shows\nthat a specific query is a bottleneck *and* the added write complexity is\nworth the read gain. Document every denormalized column with a comment\nexplaining what it caches and how it is maintained.\n",{"id":309,"difficulty":35,"q":310,"a":311},"star-schema","What is a star schema and how does it differ from a normalized OLTP schema?","A **star schema** is a denormalized dimensional model used in **data\nwarehouses (OLAP)**. It centers on a large **fact table** (events\u002Ftransactions)\nsurrounded by smaller **dimension tables** (descriptive attributes). Dimensions\nare intentionally denormalized for fast, simple queries.\n\n```sql\n-- Fact table: one row per sale event\nCREATE TABLE fact_sales (\n  sale_id     BIGINT PRIMARY KEY,\n  date_key    INT REFERENCES dim_date(date_key),\n  product_key INT REFERENCES dim_product(product_key),\n  store_key   INT REFERENCES dim_store(store_key),\n  quantity    INT NOT NULL,\n  revenue     NUMERIC(12,2) NOT NULL\n);\n\n-- Dimension: denormalized (city + country in same row, no 3NF)\nCREATE TABLE dim_store (\n  store_key INT PRIMARY KEY,\n  name      TEXT,\n  city      TEXT,\n  country   TEXT\n);\n```\n\nOLTP schemas normalize to avoid write anomalies. OLAP star schemas\ndenormalize to minimize joins and maximize scan throughput for analytics.\n\n**Rule of thumb:** use a normalized 3NF schema for transactional\napplications; use a star or snowflake schema for analytical\u002FBI workloads.\nDon't mix them — ETL pipelines transform data between the two.\n",{"id":313,"difficulty":35,"q":314,"a":315},"normalization-vs-performance","How do you balance normalization and query performance in practice?","Fully normalized schemas can require many joins, which hurts read\nperformance on large datasets. Common pragmatic trade-offs:\n\n1. **Add indexes before denormalizing** — a join on indexed FKs is fast.\n   Denormalization should only be considered after indexes fail to help.\n2. **Materialized views \u002F summary tables** — precompute expensive aggregates\n   without changing the base schema.\n3. **Selective redundancy** — add a `cached_count` column or a denormalized\n   `status` flag where read frequency vastly exceeds write frequency.\n4. **Separate OLAP schema** — replicate data nightly into a star schema for\n   reporting; keep OLTP tables normalized.\n\n```sql\n-- Before denormalizing: try an index on the join column\nCREATE INDEX idx_orders_customer_id ON orders (customer_id);\n-- EXPLAIN ANALYZE to verify it is used before adding redundant columns\nEXPLAIN ANALYZE\n  SELECT u.name, COUNT(o.id)\n  FROM users u JOIN orders o ON o.user_id = u.id\n  GROUP BY u.id;\n```\n\n**Rule of thumb:** profile with real data before denormalizing. A missing\nindex is the most common \"normalization performance problem\" — and it is\na 30-second fix compared to the ongoing cost of maintaining denormalized data.\n",{"id":317,"difficulty":35,"q":318,"a":319},"surrogate-vs-natural-key","What is the difference between a surrogate key and a natural key?","- **Natural key**: a column (or set of columns) from the real-world domain\n  that uniquely identifies an entity — e.g., email address, ISBN, social\n  security number. Natural keys carry meaning but can change over time.\n- **Surrogate key**: a system-generated identifier with no business meaning —\n  e.g., an auto-increment `id` or UUID. It never changes and has no\n  domain semantics.\n\n```sql\n-- Natural key PK (email can change → cascading updates on all FKs)\nCREATE TABLE users (email TEXT PRIMARY KEY, name TEXT);\n\n-- Surrogate PK (id never changes; email change is isolated to one row)\nCREATE TABLE users (\n  id    INT  GENERATED ALWAYS AS IDENTITY PRIMARY KEY,\n  email TEXT NOT NULL UNIQUE,\n  name  TEXT NOT NULL\n);\n```\n\n**Rule of thumb:** use a surrogate PK for every table; declare natural keys\nas `UNIQUE` constraints alongside the surrogate. This gives you the integrity\nguarantee of natural keys without the cascade pain of changing a PK.\n",{"id":321,"difficulty":104,"q":322,"a":323},"fourth-normal-form","What is Fourth Normal Form (4NF) and what does it address?","**4NF** eliminates **multi-valued dependencies (MVDs)** — independent\nmany-to-many relationships stored in a single table, which causes\nmultiplicative row explosion.\n\n```\n-- Violates 4NF: Employee can have many Skills AND many Projects, independently.\n-- | emp_id | skill      | project   |\n-- | 1      | SQL        | Alpha     |\n-- | 1      | SQL        | Beta      |  ← duplicating the SQL row for Beta\n-- | 1      | Python     | Alpha     |  ← duplicating Alpha row for Python\n-- | 1      | Python     | Beta      |\n\n-- 4NF: split into two separate tables\n-- EmployeeSkills(emp_id, skill)\n-- EmployeeProjects(emp_id, project)\n```\n\nAdding a new skill in the original table requires adding rows for every\nproject combination, creating redundancy and anomalies.\n\n**Rule of thumb:** when two many-to-many relationships are independent of\neach other but share the same entity, split them into two separate\njoin tables rather than combining them into one.\n",{"id":325,"difficulty":43,"q":326,"a":327},"normalization-checklist","How do you quickly check if a table is in 3NF?","Run through this checklist:\n\n1. **1NF check**: every cell contains one atomic value; there is a primary key.\n2. **2NF check**: if the PK is composite, every non-key column depends on the\n   *entire* PK, not just part of it.\n3. **3NF check**: no non-key column determines another non-key column\n   (no transitive dependencies — e.g., `zip → city` when `zip` is not the PK).\n\n```sql\n-- Red flags that indicate a 3NF violation:\n-- 1. A column stores concatenated values ('red,blue,green')\n-- 2. Repeated column groups (phone1, phone2, phone3)\n-- 3. A non-PK column appears in WHERE of a JOIN as if it were a PK\n-- 4. Updating one row's value requires updating dozens of other rows\n-- 5. You cannot add a fact without inserting an unrelated row\n```\n\n**Rule of thumb:** if you can answer \"what does each column tell you about?\"\nand the answer is always \"it tells you something about the primary key (and\nonly the primary key)\", the table is in 3NF.\n",{"id":329,"difficulty":43,"q":330,"a":331},"junction-table","What is a junction (bridge) table and when do you use one?","A **junction table** (also called a bridge or associative table) resolves a\n**many-to-many relationship** between two entities into two one-to-many\nrelationships. It stores the *association* as rows rather than as repeated\ncolumns.\n\n```sql\n-- Many students can enroll in many courses\nCREATE TABLE students (id INT PRIMARY KEY, name TEXT NOT NULL);\nCREATE TABLE courses  (id INT PRIMARY KEY, title TEXT NOT NULL);\n\n-- Junction table: one row per (student, course) pair\nCREATE TABLE enrollments (\n  student_id  INT NOT NULL REFERENCES students(id) ON DELETE CASCADE,\n  course_id   INT NOT NULL REFERENCES courses(id)  ON DELETE CASCADE,\n  enrolled_at DATE NOT NULL DEFAULT CURRENT_DATE,\n  PRIMARY KEY (student_id, course_id)\n);\n```\n\nThe junction table can carry **payload columns** (enrolled_at, grade) that\ndescribe the relationship itself — something impossible to store on either\nside alone.\n\n**Rule of thumb:** whenever two entities have a many-to-many relationship,\nalways model it with a junction table. Never store comma-separated IDs in a\nsingle column as an alternative.\n",{"id":333,"difficulty":35,"q":334,"a":335},"when-not-to-normalize","When should you deliberately stop normalizing?","Normalization is not always the right answer. Practical reasons to stop\nbefore reaching 3NF or BCNF:\n\n1. **Read performance is the primary concern** — heavily queried analytical\n   tables benefit from fewer joins, even at the cost of redundancy.\n2. **The relationship is stable** — if a denormalized value (e.g., a country\n   name embedded in every row) will almost never change, the update anomaly\n   risk is negligible.\n3. **External schema constraints** — integrating with a vendor schema or\n   legacy system you cannot change.\n4. **Simplicity for small, short-lived data** — a temporary staging table or\n   a one-off report table does not need 3NF rigor.\n\n```sql\n-- Acceptable denormalization: reporting snapshot\n-- Copies customer_name at the time of the order; intentionally redundant\n-- so historical reports are stable even if the customer renames.\nCREATE TABLE order_snapshots (\n  order_id      BIGINT PRIMARY KEY,\n  customer_id   INT    NOT NULL,\n  customer_name TEXT   NOT NULL,   -- denormalized snapshot\n  total         NUMERIC(12,2) NOT NULL,\n  snapped_at    TIMESTAMPTZ NOT NULL DEFAULT now()\n);\n```\n\n**Rule of thumb:** normalize transactional data to 3NF by default. Deviate\ndeliberately, document the reason, and ensure the update path (trigger, ETL,\napplication code) is clearly owned and tested.\n",{"description":32},"SQL normalization interview questions — 1NF through 3NF\u002FBCNF, functional dependencies, update anomalies, denormalization trade-offs, and practical schema design patterns.","sql\u002Fschema\u002Fnormalization","yogAW6WtW466-6r7gxd1LZhJEPh_v0Vd470WBErGp4Y",1782244097960]