[{"data":1,"prerenderedAt":989},["ShallowReactive",2],{"blog-\u002Fblog\u002Fsql-data-types-choosing-right-type":3},{"id":4,"title":5,"body":6,"description":975,"difficulty":976,"extension":977,"framework":978,"frameworkSlug":105,"meta":979,"navigation":764,"order":113,"path":980,"qaPath":981,"seo":982,"stem":983,"subtopic":984,"topic":985,"topicSlug":986,"updated":987,"__hash__":988},"blog\u002Fblog\u002Fsql-data-types-choosing-right-type.md","SQL Data Types — Choosing the Right Type for Every Column",{"type":7,"value":8,"toc":965},"minimark",[9,14,27,31,34,100,209,225,229,256,385,394,398,430,540,559,563,671,687,691,777,781,784,921,935,939,961],[10,11,13],"h2",{"id":12},"why-data-types-matter","Why data types matter",[15,16,17,18,22,23,26],"p",{},"The data type of a column controls what values it stores, how much disk space\nit uses, how it is indexed, and what arithmetic you can do on it. Choosing the\nwrong type — storing a price as ",[19,20,21],"code",{},"VARCHAR",", a user ID as ",[19,24,25],{},"FLOAT",", or a timestamp\nas a string — leads to silent bugs, poor query performance, and joins that never\nmatch.",[10,28,30],{"id":29},"integer-types","Integer types",[15,32,33],{},"Use the smallest integer type that fits your expected maximum value. Storage\ndifferences matter at scale (millions of rows).",[35,36,37,53],"table",{},[38,39,40],"thead",{},[41,42,43,47,50],"tr",{},[44,45,46],"th",{},"Type",[44,48,49],{},"Storage",[44,51,52],{},"Range",[54,55,56,70,87],"tbody",{},[41,57,58,64,67],{},[59,60,61],"td",{},[19,62,63],{},"SMALLINT",[59,65,66],{},"2 bytes",[59,68,69],{},"−32 768 to 32 767",[41,71,72,81,84],{},[59,73,74,77,78],{},[19,75,76],{},"INTEGER"," \u002F ",[19,79,80],{},"INT",[59,82,83],{},"4 bytes",[59,85,86],{},"−2.1 B to 2.1 B",[41,88,89,94,97],{},[59,90,91],{},[19,92,93],{},"BIGINT",[59,95,96],{},"8 bytes",[59,98,99],{},"−9.2 × 10¹⁸ to 9.2 × 10¹⁸",[101,102,107],"pre",{"className":103,"code":104,"language":105,"meta":106,"style":106},"language-sql shiki shiki-themes github-light github-dark","CREATE TABLE orders (\n    id           BIGINT       PRIMARY KEY GENERATED ALWAYS AS IDENTITY,\n    customer_id  INT          NOT NULL,\n    item_count   SMALLINT     NOT NULL DEFAULT 0,\n    status_code  SMALLINT     NOT NULL DEFAULT 1\n);\n","sql","",[19,108,109,129,155,168,188,203],{"__ignoreMap":106},[110,111,114,118,121,125],"span",{"class":112,"line":113},"line",1,[110,115,117],{"class":116},"szBVR","CREATE",[110,119,120],{"class":116}," TABLE",[110,122,124],{"class":123},"sScJk"," orders",[110,126,128],{"class":127},"sVt8B"," (\n",[110,130,132,135,137,140,143,146,149,152],{"class":112,"line":131},2,[110,133,134],{"class":127},"    id           ",[110,136,93],{"class":116},[110,138,139],{"class":116},"       PRIMARY KEY",[110,141,142],{"class":116}," GENERATED",[110,144,145],{"class":116}," ALWAYS",[110,147,148],{"class":116}," AS",[110,150,151],{"class":116}," IDENTITY",[110,153,154],{"class":127},",\n",[110,156,158,161,163,166],{"class":112,"line":157},3,[110,159,160],{"class":127},"    customer_id  ",[110,162,80],{"class":116},[110,164,165],{"class":116},"          NOT NULL",[110,167,154],{"class":127},[110,169,171,174,176,179,182,186],{"class":112,"line":170},4,[110,172,173],{"class":127},"    item_count   ",[110,175,63],{"class":116},[110,177,178],{"class":116},"     NOT NULL",[110,180,181],{"class":116}," DEFAULT",[110,183,185],{"class":184},"sj4cs"," 0",[110,187,154],{"class":127},[110,189,191,194,196,198,200],{"class":112,"line":190},5,[110,192,193],{"class":127},"    status_code  ",[110,195,63],{"class":116},[110,197,178],{"class":116},[110,199,181],{"class":116},[110,201,202],{"class":184}," 1\n",[110,204,206],{"class":112,"line":205},6,[110,207,208],{"class":127},");\n",[15,210,211,212,214,215,217,218,220,221,224],{},"Use ",[19,213,93],{}," for primary keys on high-volume tables — ",[19,216,80],{}," runs out at 2.1\nbillion rows and a busy table can reach that. Never use ",[19,219,25],{}," or ",[19,222,223],{},"DOUBLE"," for\nIDs — floating-point arithmetic introduces rounding errors that can cause missed\njoins.",[10,226,228],{"id":227},"numeric-decimal-for-money","NUMERIC \u002F DECIMAL — for money",[15,230,231,232,234,235,234,237,240,241,244,245,248,249,220,252,255],{},"Floating-point types (",[19,233,25],{},", ",[19,236,223],{},[19,238,239],{},"REAL",") store approximate values. They\nare wrong for money. ",[19,242,243],{},"0.1 + 0.2"," in floating-point is ",[19,246,247],{},"0.30000000000000004",".\nUse ",[19,250,251],{},"NUMERIC(precision, scale)",[19,253,254],{},"DECIMAL"," for any monetary column.",[101,257,259],{"className":103,"code":258,"language":105,"meta":106,"style":106},"CREATE TABLE products (\n    id          INT          PRIMARY KEY,\n    product_name VARCHAR(200) NOT NULL,\n    unit_price  NUMERIC(10, 2) NOT NULL,  -- up to 99,999,999.99\n    cost_price  NUMERIC(10, 2) NOT NULL,\n    tax_rate    NUMERIC(5, 4)  NOT NULL   -- e.g., 0.2000 for 20%\n);\n",[19,260,261,272,284,305,334,355,380],{"__ignoreMap":106},[110,262,263,265,267,270],{"class":112,"line":113},[110,264,117],{"class":116},[110,266,120],{"class":116},[110,268,269],{"class":123}," products",[110,271,128],{"class":127},[110,273,274,277,279,282],{"class":112,"line":131},[110,275,276],{"class":127},"    id          ",[110,278,80],{"class":116},[110,280,281],{"class":116},"          PRIMARY KEY",[110,283,154],{"class":127},[110,285,286,289,291,294,297,300,303],{"class":112,"line":157},[110,287,288],{"class":127},"    product_name ",[110,290,21],{"class":116},[110,292,293],{"class":127},"(",[110,295,296],{"class":184},"200",[110,298,299],{"class":127},") ",[110,301,302],{"class":116},"NOT NULL",[110,304,154],{"class":127},[110,306,307,310,313,315,318,320,323,325,327,330],{"class":112,"line":170},[110,308,309],{"class":127},"    unit_price  ",[110,311,312],{"class":116},"NUMERIC",[110,314,293],{"class":127},[110,316,317],{"class":184},"10",[110,319,234],{"class":127},[110,321,322],{"class":184},"2",[110,324,299],{"class":127},[110,326,302],{"class":116},[110,328,329],{"class":127},",  ",[110,331,333],{"class":332},"sJ8bj","-- up to 99,999,999.99\n",[110,335,336,339,341,343,345,347,349,351,353],{"class":112,"line":190},[110,337,338],{"class":127},"    cost_price  ",[110,340,312],{"class":116},[110,342,293],{"class":127},[110,344,317],{"class":184},[110,346,234],{"class":127},[110,348,322],{"class":184},[110,350,299],{"class":127},[110,352,302],{"class":116},[110,354,154],{"class":127},[110,356,357,360,362,364,367,369,372,375,377],{"class":112,"line":205},[110,358,359],{"class":127},"    tax_rate    ",[110,361,312],{"class":116},[110,363,293],{"class":127},[110,365,366],{"class":184},"5",[110,368,234],{"class":127},[110,370,371],{"class":184},"4",[110,373,374],{"class":127},")  ",[110,376,302],{"class":116},[110,378,379],{"class":332},"   -- e.g., 0.2000 for 20%\n",[110,381,383],{"class":112,"line":382},7,[110,384,208],{"class":127},[15,386,387,390,391,393],{},[19,388,389],{},"NUMERIC(10, 2)"," stores 10 significant digits with 2 after the decimal point.\nArithmetic on ",[19,392,312],{}," is exact — no rounding surprises.",[10,395,397],{"id":396},"text-vs-varchar","TEXT vs VARCHAR",[399,400,401,412,424],"ul",{},[402,403,404,407,408,411],"li",{},[19,405,406],{},"VARCHAR(n)"," — variable-length string capped at ",[19,409,410],{},"n"," characters. Exceeding\nthe cap raises an error (Postgres) or silently truncates (MySQL).",[402,413,414,417,418,420,421,423],{},[19,415,416],{},"TEXT"," — unlimited variable-length string (Postgres, MySQL). In Postgres,\n",[19,419,416],{}," and ",[19,422,21],{}," have identical storage and performance.",[402,425,426,429],{},[19,427,428],{},"CHAR(n)"," — fixed-length, padded with spaces. Rarely useful.",[101,431,433],{"className":103,"code":432,"language":105,"meta":106,"style":106},"CREATE TABLE users (\n    id           BIGINT        PRIMARY KEY GENERATED ALWAYS AS IDENTITY,\n    email        VARCHAR(254)  NOT NULL UNIQUE,   -- RFC 5321 max\n    username     VARCHAR(50)   NOT NULL UNIQUE,\n    display_name VARCHAR(100),\n    bio          TEXT                             -- no sensible length cap\n);\n",[19,434,435,446,465,490,511,526,536],{"__ignoreMap":106},[110,436,437,439,441,444],{"class":112,"line":113},[110,438,117],{"class":116},[110,440,120],{"class":116},[110,442,443],{"class":123}," users",[110,445,128],{"class":127},[110,447,448,450,452,455,457,459,461,463],{"class":112,"line":131},[110,449,134],{"class":127},[110,451,93],{"class":116},[110,453,454],{"class":116},"        PRIMARY KEY",[110,456,142],{"class":116},[110,458,145],{"class":116},[110,460,148],{"class":116},[110,462,151],{"class":116},[110,464,154],{"class":127},[110,466,467,470,472,474,477,479,481,484,487],{"class":112,"line":157},[110,468,469],{"class":127},"    email        ",[110,471,21],{"class":116},[110,473,293],{"class":127},[110,475,476],{"class":184},"254",[110,478,374],{"class":127},[110,480,302],{"class":116},[110,482,483],{"class":116}," UNIQUE",[110,485,486],{"class":127},",   ",[110,488,489],{"class":332},"-- RFC 5321 max\n",[110,491,492,495,497,499,502,505,507,509],{"class":112,"line":170},[110,493,494],{"class":127},"    username     ",[110,496,21],{"class":116},[110,498,293],{"class":127},[110,500,501],{"class":184},"50",[110,503,504],{"class":127},")   ",[110,506,302],{"class":116},[110,508,483],{"class":116},[110,510,154],{"class":127},[110,512,513,516,518,520,523],{"class":112,"line":190},[110,514,515],{"class":127},"    display_name ",[110,517,21],{"class":116},[110,519,293],{"class":127},[110,521,522],{"class":184},"100",[110,524,525],{"class":127},"),\n",[110,527,528,531,533],{"class":112,"line":205},[110,529,530],{"class":127},"    bio          ",[110,532,416],{"class":116},[110,534,535],{"class":332},"                             -- no sensible length cap\n",[110,537,538],{"class":112,"line":382},[110,539,208],{"class":127},[15,541,542,543,545,546,549,550,552,553,555,556,558],{},"In Postgres, prefer ",[19,544,416],{}," with a ",[19,547,548],{},"CHECK"," constraint over ",[19,551,406],{}," — you\ncan change the constraint without rewriting the column. In MySQL, ",[19,554,406],{},"\nis required for columns you intend to index (large ",[19,557,416],{}," columns need a prefix\nindex).",[10,560,562],{"id":561},"date-time-and-timestamp","DATE, TIME, and TIMESTAMP",[101,564,566],{"className":103,"code":565,"language":105,"meta":106,"style":106},"CREATE TABLE orders (\n    id           BIGINT      PRIMARY KEY GENERATED ALWAYS AS IDENTITY,\n    order_date   DATE        NOT NULL,            -- '2026-06-20', no time component\n    scheduled_at TIME        NOT NULL,            -- '14:30:00', no date\n    created_at   TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- date + time + timezone\n    updated_at   TIMESTAMPTZ NOT NULL DEFAULT NOW()\n);\n",[19,567,568,578,597,614,629,651,667],{"__ignoreMap":106},[110,569,570,572,574,576],{"class":112,"line":113},[110,571,117],{"class":116},[110,573,120],{"class":116},[110,575,124],{"class":123},[110,577,128],{"class":127},[110,579,580,582,584,587,589,591,593,595],{"class":112,"line":131},[110,581,134],{"class":127},[110,583,93],{"class":116},[110,585,586],{"class":116},"      PRIMARY KEY",[110,588,142],{"class":116},[110,590,145],{"class":116},[110,592,148],{"class":116},[110,594,151],{"class":116},[110,596,154],{"class":127},[110,598,599,602,605,608,611],{"class":112,"line":157},[110,600,601],{"class":127},"    order_date   ",[110,603,604],{"class":116},"DATE",[110,606,607],{"class":116},"        NOT NULL",[110,609,610],{"class":127},",            ",[110,612,613],{"class":332},"-- '2026-06-20', no time component\n",[110,615,616,619,622,624,626],{"class":112,"line":170},[110,617,618],{"class":127},"    scheduled_at ",[110,620,621],{"class":116},"TIME",[110,623,607],{"class":116},[110,625,610],{"class":127},[110,627,628],{"class":332},"-- '14:30:00', no date\n",[110,630,631,634,637,640,642,645,648],{"class":112,"line":190},[110,632,633],{"class":127},"    created_at   ",[110,635,636],{"class":116},"TIMESTAMPTZ",[110,638,639],{"class":116}," NOT NULL",[110,641,181],{"class":116},[110,643,644],{"class":116}," NOW",[110,646,647],{"class":127},"(), ",[110,649,650],{"class":332},"-- date + time + timezone\n",[110,652,653,656,658,660,662,664],{"class":112,"line":205},[110,654,655],{"class":127},"    updated_at   ",[110,657,636],{"class":116},[110,659,639],{"class":116},[110,661,181],{"class":116},[110,663,644],{"class":116},[110,665,666],{"class":127},"()\n",[110,668,669],{"class":112,"line":382},[110,670,208],{"class":127},[15,672,673,674,676,677,680,681,683,684,686],{},"Always use ",[19,675,636],{}," (timestamp with time zone) over ",[19,678,679],{},"TIMESTAMP"," (without).\n",[19,682,636],{}," stores UTC internally and converts to the session's time zone on\ndisplay — so two users in different time zones see the correct local time.\n",[19,685,679],{}," without time zone stores the value literally, with no zone\ninformation — it becomes ambiguous as soon as users or servers span time zones.",[10,688,690],{"id":689},"boolean","BOOLEAN",[101,692,694],{"className":103,"code":693,"language":105,"meta":106,"style":106},"CREATE TABLE feature_flags (\n    flag_name  VARCHAR(100) PRIMARY KEY,\n    is_enabled BOOLEAN      NOT NULL DEFAULT FALSE,\n    updated_at TIMESTAMPTZ  NOT NULL DEFAULT NOW()\n);\n\n-- MySQL: TINYINT(1) is the conventional boolean\n-- (MySQL's BOOLEAN type is an alias for TINYINT(1))\n",[19,695,696,707,725,740,756,760,766,771],{"__ignoreMap":106},[110,697,698,700,702,705],{"class":112,"line":113},[110,699,117],{"class":116},[110,701,120],{"class":116},[110,703,704],{"class":123}," feature_flags",[110,706,128],{"class":127},[110,708,709,712,714,716,718,720,723],{"class":112,"line":131},[110,710,711],{"class":127},"    flag_name  ",[110,713,21],{"class":116},[110,715,293],{"class":127},[110,717,522],{"class":184},[110,719,299],{"class":127},[110,721,722],{"class":116},"PRIMARY KEY",[110,724,154],{"class":127},[110,726,727,730,732,735,737],{"class":112,"line":157},[110,728,729],{"class":127},"    is_enabled ",[110,731,690],{"class":116},[110,733,734],{"class":116},"      NOT NULL",[110,736,181],{"class":116},[110,738,739],{"class":127}," FALSE,\n",[110,741,742,745,747,750,752,754],{"class":112,"line":170},[110,743,744],{"class":127},"    updated_at ",[110,746,636],{"class":116},[110,748,749],{"class":116},"  NOT NULL",[110,751,181],{"class":116},[110,753,644],{"class":116},[110,755,666],{"class":127},[110,757,758],{"class":112,"line":190},[110,759,208],{"class":127},[110,761,762],{"class":112,"line":205},[110,763,765],{"emptyLinePlaceholder":764},true,"\n",[110,767,768],{"class":112,"line":382},[110,769,770],{"class":332},"-- MySQL: TINYINT(1) is the conventional boolean\n",[110,772,774],{"class":112,"line":773},8,[110,775,776],{"class":332},"-- (MySQL's BOOLEAN type is an alias for TINYINT(1))\n",[10,778,780],{"id":779},"json-jsonb","JSON \u002F JSONB",[15,782,783],{},"Use JSON columns for truly variable or schema-less data — configuration blobs,\nthird-party API payloads, or sparse attributes. Do not use JSON as a workaround\nfor poor relational design.",[101,785,787],{"className":103,"code":786,"language":105,"meta":106,"style":106},"CREATE TABLE product_attributes (\n    product_id INT   NOT NULL REFERENCES products(id),\n    attributes JSONB NOT NULL DEFAULT '{}'  -- Postgres: binary JSON, indexed\n);\n\n-- Query a JSON field\nSELECT product_id, attributes->>'colour' AS colour\nFROM product_attributes\nWHERE attributes @> '{\"material\": \"leather\"}';\n\n-- Index a JSON field\nCREATE INDEX ON product_attributes USING GIN (attributes);\n",[19,788,789,800,816,832,836,840,845,864,872,890,895,901],{"__ignoreMap":106},[110,790,791,793,795,798],{"class":112,"line":113},[110,792,117],{"class":116},[110,794,120],{"class":116},[110,796,797],{"class":123}," product_attributes",[110,799,128],{"class":127},[110,801,802,805,807,810,813],{"class":112,"line":131},[110,803,804],{"class":127},"    product_id ",[110,806,80],{"class":116},[110,808,809],{"class":116},"   NOT NULL",[110,811,812],{"class":116}," REFERENCES",[110,814,815],{"class":127}," products(id),\n",[110,817,818,821,823,825,829],{"class":112,"line":157},[110,819,820],{"class":127},"    attributes JSONB ",[110,822,302],{"class":116},[110,824,181],{"class":116},[110,826,828],{"class":827},"sZZnC"," '{}'",[110,830,831],{"class":332},"  -- Postgres: binary JSON, indexed\n",[110,833,834],{"class":112,"line":170},[110,835,208],{"class":127},[110,837,838],{"class":112,"line":190},[110,839,765],{"emptyLinePlaceholder":764},[110,841,842],{"class":112,"line":205},[110,843,844],{"class":332},"-- Query a JSON field\n",[110,846,847,850,853,856,859,861],{"class":112,"line":382},[110,848,849],{"class":116},"SELECT",[110,851,852],{"class":127}," product_id, attributes",[110,854,855],{"class":116},"->>",[110,857,858],{"class":827},"'colour'",[110,860,148],{"class":116},[110,862,863],{"class":127}," colour\n",[110,865,866,869],{"class":112,"line":773},[110,867,868],{"class":116},"FROM",[110,870,871],{"class":127}," product_attributes\n",[110,873,875,878,881,884,887],{"class":112,"line":874},9,[110,876,877],{"class":116},"WHERE",[110,879,880],{"class":127}," attributes @",[110,882,883],{"class":116},">",[110,885,886],{"class":827}," '{\"material\": \"leather\"}'",[110,888,889],{"class":127},";\n",[110,891,893],{"class":112,"line":892},10,[110,894,765],{"emptyLinePlaceholder":764},[110,896,898],{"class":112,"line":897},11,[110,899,900],{"class":332},"-- Index a JSON field\n",[110,902,904,906,909,912,915,918],{"class":112,"line":903},12,[110,905,117],{"class":116},[110,907,908],{"class":116}," INDEX",[110,910,911],{"class":123}," ON",[110,913,914],{"class":127}," product_attributes ",[110,916,917],{"class":116},"USING",[110,919,920],{"class":127}," GIN (attributes);\n",[15,922,211,923,926,927,930,931,934],{},[19,924,925],{},"JSONB"," (binary JSON) in Postgres rather than ",[19,928,929],{},"JSON"," — it stores the parsed\nform, supports indexing, and allows operators like ",[19,932,933],{},"@>"," (contains).",[10,936,938],{"id":937},"recap","Recap",[15,940,941,942,944,945,948,949,951,952,954,955,957,958,960],{},"Match the type to the data: ",[19,943,93],{}," for IDs, ",[19,946,947],{},"NUMERIC(p,s)"," for money, ",[19,950,416],{},"\nor ",[19,953,21],{}," for strings, ",[19,956,636],{}," for timestamps, ",[19,959,690],{}," for flags.\nThe wrong type — floats for money, strings for dates, strings for IDs — leads\nto rounding errors, missed indexes, and broken comparisons. Get the types right\nat schema creation time; changing them later requires an expensive table rewrite.",[962,963,964],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}",{"title":106,"searchDepth":131,"depth":131,"links":966},[967,968,969,970,971,972,973,974],{"id":12,"depth":131,"text":13},{"id":29,"depth":131,"text":30},{"id":227,"depth":131,"text":228},{"id":396,"depth":131,"text":397},{"id":561,"depth":131,"text":562},{"id":689,"depth":131,"text":690},{"id":779,"depth":131,"text":780},{"id":937,"depth":131,"text":938},"SQL data types explained — integers, decimals, TEXT vs VARCHAR, DATE vs TIMESTAMP, BOOLEAN, JSON, and the hidden cost of wrong type choices.","easy","md","SQL",{},"\u002Fblog\u002Fsql-data-types-choosing-right-type","\u002Fsql\u002Fschema\u002Fdata-types",{"title":5,"description":975},"blog\u002Fsql-data-types-choosing-right-type","Data Types","Schema & Data Types","schema","2026-06-20","YegBfpGv4cXz0zwf3obuaGgSqbDr_4BeA3FnEFeBTcM",1782244088135]