[{"data":1,"prerenderedAt":114},["ShallowReactive",2],{"qa-\u002Fsql\u002Fschema\u002Fdata-types":3},{"page":4,"siblings":98,"blog":111},{"id":5,"title":6,"body":7,"description":11,"difficulty":14,"extension":15,"framework":16,"frameworkSlug":17,"meta":18,"navigation":19,"order":20,"path":21,"questions":22,"questionsCount":89,"related":90,"seo":91,"seoDescription":92,"stem":93,"subtopic":6,"topic":94,"topicSlug":95,"updated":96,"__hash__":97},"qa\u002Fsql\u002Fschema\u002Fdata-types.md","Data Types",{"type":8,"value":9,"toc":10},"minimark",[],{"title":11,"searchDepth":12,"depth":12,"links":13},"",2,[],"medium","md","SQL","sql",{},true,1,"\u002Fsql\u002Fschema\u002Fdata-types",[23,28,32,36,40,44,48,52,56,60,64,68,72,76,80,84],{"id":24,"difficulty":25,"q":26,"a":27},"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":29,"difficulty":25,"q":30,"a":31},"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":33,"difficulty":14,"q":34,"a":35},"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":37,"difficulty":25,"q":38,"a":39},"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":41,"difficulty":14,"q":42,"a":43},"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":45,"difficulty":25,"q":46,"a":47},"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":49,"difficulty":14,"q":50,"a":51},"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":53,"difficulty":14,"q":54,"a":55},"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":57,"difficulty":14,"q":58,"a":59},"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":61,"difficulty":14,"q":62,"a":63},"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":65,"difficulty":14,"q":66,"a":67},"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":69,"difficulty":14,"q":70,"a":71},"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":73,"difficulty":14,"q":74,"a":75},"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":77,"difficulty":25,"q":78,"a":79},"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":81,"difficulty":14,"q":82,"a":83},"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":85,"difficulty":86,"q":87,"a":88},"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":11},"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","Schema & Data Types","schema","2026-06-20","JffWumXQ_4Kaqd6EvuOyCKiWfy7w6VhFSYtqDFgQngk",[99,100,103,107],{"subtopic":6,"path":21,"order":20},{"subtopic":101,"path":102,"order":12},"DDL — Creating & Altering Tables","\u002Fsql\u002Fschema\u002Fddl",{"subtopic":104,"path":105,"order":106},"Constraints & Integrity","\u002Fsql\u002Fschema\u002Fconstraints",3,{"subtopic":108,"path":109,"order":110},"Normalization","\u002Fsql\u002Fschema\u002Fnormalization",4,{"path":112,"title":113},"\u002Fblog\u002Fsql-data-types-choosing-right-type","SQL Data Types — Choosing the Right Type for Every Column",1782244107091]