[{"data":1,"prerenderedAt":4572},["ShallowReactive",2],{"page-\u002Fsql\u002F10-data-types-and-null":3},{"id":4,"title":5,"body":6,"description":16,"extension":4566,"meta":4567,"navigation":482,"path":4568,"seo":4569,"stem":4570,"__hash__":4571},"content\u002Fsql\u002F10-data-types-and-null.md","10 — Data Types & NULL Handling",{"type":7,"value":8,"toc":4531},"minimark",[9,13,17,22,392,396,414,631,637,641,740,828,832,866,871,877,935,940,987,992,1023,1044,1048,1082,1225,1229,1246,1403,1407,1467,1471,1611,1615,1789,1793,1804,1977,1981,2143,2147,2257,2261,2305,2353,2357,2390,2394,2414,2418,2445,2449,3046,3050,3054,3198,3202,3367,3371,3374,3503,3506,3609,3613,3840,3844,3965,3969,4249,4253,4260,4306,4493,4496,4527],[10,11,5],"h1",{"id":12},"_10-data-types-null-handling",[14,15,16],"p",{},"SQL has rich types and a strict — but surprising — treatment of NULL. NULL is the single biggest source of bugs in SQL. This chapter covers the type system and the three-valued logic that NULL introduces.",[18,19,21],"h2",{"id":20},"core-data-types-postgresql-reference","Core Data Types — PostgreSQL Reference",[23,24,25,44],"table",{},[26,27,28],"thead",{},[29,30,31,35,38,41],"tr",{},[32,33,34],"th",{},"Category",[32,36,37],{},"PostgreSQL types",[32,39,40],{},"Storage",[32,42,43],{},"Notes",[45,46,47,87,116,140,175,202,242,259,281,308,329,347,367],"tbody",{},[29,48,49,56,75,78],{},[50,51,52],"td",{},[53,54,55],"strong",{},"Integer",[50,57,58,62,63,66,67,70,71,74],{},[59,60,61],"code",{},"smallint"," (2B), ",[59,64,65],{},"integer","\u002F",[59,68,69],{},"int"," (4B), ",[59,72,73],{},"bigint"," (8B)",[50,76,77],{},"2\u002F4\u002F8 bytes",[50,79,80,81,83,84,86],{},"Exact. ",[59,82,69],{}," range: −2,147,483,648 to +2,147,483,647. ",[59,85,73],{}," for PKs expecting > 2B rows.",[29,88,89,94,103,106],{},[50,90,91],{},[53,92,93],{},"Decimal",[50,95,96,99,100],{},[59,97,98],{},"numeric(p, s)",", ",[59,101,102],{},"decimal(p, s)",[50,104,105],{},"variable",[50,107,108,109,111,112,115],{},"Exact, arbitrary precision. ",[59,110,14],{}," = precision (total digits), ",[59,113,114],{},"s"," = scale (digits after decimal).",[29,117,118,123,131,134],{},[50,119,120],{},[53,121,122],{},"Floating",[50,124,125,70,128,74],{},[59,126,127],{},"real",[59,129,130],{},"double precision",[50,132,133],{},"4\u002F8 bytes",[50,135,136,137],{},"Inexact, IEEE 754. ",[53,138,139],{},"Never use for money.",[29,141,142,147,161,163],{},[50,143,144],{},[53,145,146],{},"Text",[50,148,149,152,153,156,157,160],{},[59,150,151],{},"text"," (variable, unlimited), ",[59,154,155],{},"varchar(n)"," (variable, max n), ",[59,158,159],{},"char(n)"," (fixed n, space-padded)",[50,162,105],{},[50,164,165,167,168,171,172,174],{},[59,166,151],{}," and ",[59,169,170],{},"varchar"," are identical in performance in PostgreSQL. ",[59,173,155],{}," is a length constraint, not a perf win.",[29,176,177,182,187,190],{},[50,178,179],{},[53,180,181],{},"Boolean",[50,183,184],{},[59,185,186],{},"boolean",[50,188,189],{},"1 byte",[50,191,192,66,195,66,198,201],{},[59,193,194],{},"TRUE",[59,196,197],{},"FALSE",[59,199,200],{},"NULL",".",[29,203,204,209,228,231],{},[50,205,206],{},[53,207,208],{},"Date\u002FTime",[50,210,211,70,214,217,218,217,221,217,224,227],{},[59,212,213],{},"date",[59,215,216],{},"time"," (8B), ",[59,219,220],{},"timestamp",[59,222,223],{},"timestamptz",[59,225,226],{},"interval"," (16B)",[50,229,230],{},"4-16 bytes",[50,232,233,235,236,238,239,241],{},[59,234,223],{}," stores as UTC, displays in session zone. Always prefer ",[59,237,223],{}," over ",[59,240,220],{}," for application timestamps.",[29,243,244,249,254,256],{},[50,245,246],{},[53,247,248],{},"Binary",[50,250,251],{},[59,252,253],{},"bytea",[50,255,105],{},[50,257,258],{},"Variable-length binary.",[29,260,261,266,271,274],{},[50,262,263],{},[53,264,265],{},"UUID",[50,267,268],{},[59,269,270],{},"uuid",[50,272,273],{},"16 bytes",[50,275,276,277,280],{},"128-bit. Generate with ",[59,278,279],{},"gen_random_uuid()"," (PG 13+).",[29,282,283,288,298,300],{},[50,284,285],{},[53,286,287],{},"JSON",[50,289,290,293,294,297],{},[59,291,292],{},"json"," (text), ",[59,295,296],{},"jsonb"," (binary)",[50,299,105],{},[50,301,302,304,305,307],{},[59,303,296],{}," is binary, indexable, deduplicated. Prefer ",[59,306,296],{}," unless you need to preserve input key order.",[29,309,310,315,324,326],{},[50,311,312],{},[53,313,314],{},"Array",[50,316,317,99,320,323],{},[59,318,319],{},"int[]",[59,321,322],{},"text[]",", etc.",[50,325,105],{},[50,327,328],{},"PostgreSQL-specific. Useful for small fixed-size lists; use a separate table for large\u002Fqueried collections.",[29,330,331,336,341,344],{},[50,332,333],{},[53,334,335],{},"Enumerated",[50,337,338],{},[59,339,340],{},"enum",[50,342,343],{},"4 bytes",[50,345,346],{},"Static set of labels. Immutable (can't remove values without a cast dance).",[29,348,349,354,362,364],{},[50,350,351],{},[53,352,353],{},"Bit",[50,355,356,99,359],{},[59,357,358],{},"bit(n)",[59,360,361],{},"bit varying(n)",[50,363,105],{},[50,365,366],{},"Bit strings. Rare.",[29,368,369,374,379,382],{},[50,370,371],{},[53,372,373],{},"Money",[50,375,376],{},[59,377,378],{},"money",[50,380,381],{},"8 bytes",[50,383,384,385,388,389,201],{},"Don't use — it's a locale-dependent wrapper around ",[59,386,387],{},"numeric",". Use ",[59,390,391],{},"numeric(p, 2)",[18,393,395],{"id":394},"numeric-vs-float-precision-matters","numeric vs float — Precision Matters",[14,397,398,400,401,404,405,408,409,66,411,413],{},[59,399,387],{}," is ",[53,402,403],{},"exact"," — ",[59,406,407],{},"0.1 + 0.2 = 0.3",", always. Floating-point (",[59,410,127],{},[59,412,130],{},") is inexact — IEEE 754 can't represent 0.1 exactly.",[415,416,418],"code-wrapper",{"language":417},"sql",[419,420,424],"pre",{"className":421,"code":422,"language":417,"meta":423,"style":423},"language-sql shiki shiki-themes github-light github-dark","-- numeric: exact arithmetic. 0.1 + 0.2 = 0.3 (stored as decimal digits, not binary float).\nSELECT 0.1::numeric + 0.2::numeric;          -- 0.3\n\n-- float: inexact. 0.1 in binary is a repeating fraction → rounded → accumulation error.\nSELECT 0.1::float + 0.2::float;              -- 0.30000000000000004\n\n-- The classic float trap: money calculations drift over many additions.\n-- Over 1 million additions of $0.10, float drifts by cents; numeric stays exact.\nSELECT sum_amount::float AS float_sum,       -- drifts: e.g., 100000.00000001\n       sum_amount::numeric AS numeric_sum    -- exact:   100000.00\nFROM (SELECT SUM(0.10::numeric) AS sum_amount FROM generate_series(1, 1000000)) t;\n","",[59,425,426,435,477,484,490,524,529,535,541,560,576],{"__ignoreMap":423},[427,428,431],"span",{"class":429,"line":430},"line",1,[427,432,434],{"class":433},"sdCPZ","-- numeric: exact arithmetic. 0.1 + 0.2 = 0.3 (stored as decimal digits, not binary float).\n",[427,436,438,442,446,449,452,455,457,460,462,464,467,469,471,474],{"class":429,"line":437},2,[427,439,441],{"class":440},"svdQ7","SELECT",[427,443,445],{"class":444},"snvgF"," 0",[427,447,201],{"class":448},"ssxIu",[427,450,451],{"class":444},"1",[427,453,454],{"class":448},"::",[427,456,387],{"class":440},[427,458,459],{"class":440}," +",[427,461,445],{"class":444},[427,463,201],{"class":448},[427,465,466],{"class":444},"2",[427,468,454],{"class":448},[427,470,387],{"class":440},[427,472,473],{"class":448},";          ",[427,475,476],{"class":433},"-- 0.3\n",[427,478,480],{"class":429,"line":479},3,[427,481,483],{"emptyLinePlaceholder":482},true,"\n",[427,485,487],{"class":429,"line":486},4,[427,488,489],{"class":433},"-- float: inexact. 0.1 in binary is a repeating fraction → rounded → accumulation error.\n",[427,491,493,495,497,499,501,503,506,508,510,512,514,516,518,521],{"class":429,"line":492},5,[427,494,441],{"class":440},[427,496,445],{"class":444},[427,498,201],{"class":448},[427,500,451],{"class":444},[427,502,454],{"class":448},[427,504,505],{"class":440},"float",[427,507,459],{"class":440},[427,509,445],{"class":444},[427,511,201],{"class":448},[427,513,466],{"class":444},[427,515,454],{"class":448},[427,517,505],{"class":440},[427,519,520],{"class":448},";              ",[427,522,523],{"class":433},"-- 0.30000000000000004\n",[427,525,527],{"class":429,"line":526},6,[427,528,483],{"emptyLinePlaceholder":482},[427,530,532],{"class":429,"line":531},7,[427,533,534],{"class":433},"-- The classic float trap: money calculations drift over many additions.\n",[427,536,538],{"class":429,"line":537},8,[427,539,540],{"class":433},"-- Over 1 million additions of $0.10, float drifts by cents; numeric stays exact.\n",[427,542,544,546,549,551,554,557],{"class":429,"line":543},9,[427,545,441],{"class":440},[427,547,548],{"class":448}," sum_amount::",[427,550,505],{"class":440},[427,552,553],{"class":440}," AS",[427,555,556],{"class":448}," float_sum,       ",[427,558,559],{"class":433},"-- drifts: e.g., 100000.00000001\n",[427,561,563,566,568,570,573],{"class":429,"line":562},10,[427,564,565],{"class":448},"       sum_amount::",[427,567,387],{"class":440},[427,569,553],{"class":440},[427,571,572],{"class":448}," numeric_sum    ",[427,574,575],{"class":433},"-- exact:   100000.00\n",[427,577,579,582,585,587,590,593,596,598,601,603,605,608,611,614,616,619,621,623,625,628],{"class":429,"line":578},11,[427,580,581],{"class":440},"FROM",[427,583,584],{"class":448}," (",[427,586,441],{"class":440},[427,588,589],{"class":444}," SUM",[427,591,592],{"class":448},"(",[427,594,595],{"class":444},"0",[427,597,201],{"class":448},[427,599,600],{"class":444},"10",[427,602,454],{"class":448},[427,604,387],{"class":440},[427,606,607],{"class":448},") ",[427,609,610],{"class":440},"AS",[427,612,613],{"class":448}," sum_amount ",[427,615,581],{"class":440},[427,617,618],{"class":444}," generate_series",[427,620,592],{"class":448},[427,622,451],{"class":444},[427,624,99],{"class":448},[427,626,627],{"class":444},"1000000",[427,629,630],{"class":448},")) t;\n",[14,632,633,634,636],{},"Use ",[59,635,387],{}," for money, scientific computations needing exact decimals, and anything where rounding errors are unacceptable. Use floats for approximate scientific work and when storage\u002Fspeed matter more than exactness.",[18,638,640],{"id":639},"varcharn-vs-text-vs-charn","varchar(n) vs text vs char(n)",[415,642,643],{"language":417},[419,644,646],{"className":421,"code":645,"language":417,"meta":423,"style":423},"-- In PostgreSQL, text and varchar (no limit) are IDENTICAL in performance and storage.\n-- Both use variable-length storage. varchar(n) is just a CHECK (length(col) \u003C= n) constraint.\n-- Use text by default; use varchar(n) only for real business-rule length limits.\n\n-- char(n) pads with spaces to width n. This causes subtle comparison bugs:\n-- 'NY' stored as char(3) becomes 'NY ' (padded).\n-- Depending on collation, 'NY' = 'NY ' may be TRUE (trailing spaces ignored) or FALSE.\n-- Avoid char(n) except for truly fixed-width codes (ISO country codes = char(2)).\nSELECT 'NY'::char(3) = 'NY';                  -- TRUE in PG (trailing spaces trimmed in comparison)\nSELECT 'NY'::varchar = 'NY ';                 -- FALSE (varchar does not trim)\n",[59,647,648,653,658,663,667,672,677,682,687,718],{"__ignoreMap":423},[427,649,650],{"class":429,"line":430},[427,651,652],{"class":433},"-- In PostgreSQL, text and varchar (no limit) are IDENTICAL in performance and storage.\n",[427,654,655],{"class":429,"line":437},[427,656,657],{"class":433},"-- Both use variable-length storage. varchar(n) is just a CHECK (length(col) \u003C= n) constraint.\n",[427,659,660],{"class":429,"line":479},[427,661,662],{"class":433},"-- Use text by default; use varchar(n) only for real business-rule length limits.\n",[427,664,665],{"class":429,"line":486},[427,666,483],{"emptyLinePlaceholder":482},[427,668,669],{"class":429,"line":492},[427,670,671],{"class":433},"-- char(n) pads with spaces to width n. This causes subtle comparison bugs:\n",[427,673,674],{"class":429,"line":526},[427,675,676],{"class":433},"-- 'NY' stored as char(3) becomes 'NY ' (padded).\n",[427,678,679],{"class":429,"line":531},[427,680,681],{"class":433},"-- Depending on collation, 'NY' = 'NY ' may be TRUE (trailing spaces ignored) or FALSE.\n",[427,683,684],{"class":429,"line":537},[427,685,686],{"class":433},"-- Avoid char(n) except for truly fixed-width codes (ISO country codes = char(2)).\n",[427,688,689,691,695,697,700,702,705,707,710,712,715],{"class":429,"line":543},[427,690,441],{"class":440},[427,692,694],{"class":693},"sJ6F3"," 'NY'",[427,696,454],{"class":448},[427,698,699],{"class":440},"char",[427,701,592],{"class":448},[427,703,704],{"class":444},"3",[427,706,607],{"class":448},[427,708,709],{"class":440},"=",[427,711,694],{"class":693},[427,713,714],{"class":448},";                  ",[427,716,717],{"class":433},"-- TRUE in PG (trailing spaces trimmed in comparison)\n",[427,719,720,722,724,726,728,731,734,737],{"class":429,"line":562},[427,721,441],{"class":440},[427,723,694],{"class":693},[427,725,454],{"class":448},[427,727,170],{"class":440},[427,729,730],{"class":440}," =",[427,732,733],{"class":693}," 'NY '",[427,735,736],{"class":448},";                 ",[427,738,739],{"class":433},"-- FALSE (varchar does not trim)\n",[23,741,742,758],{},[26,743,744],{},[29,745,746,749,752,755],{},[32,747,748],{},"Type",[32,750,751],{},"Padding",[32,753,754],{},"Performance",[32,756,757],{},"Use case",[45,759,760,775,789,809],{},[29,761,762,766,769,772],{},[50,763,764],{},[59,765,151],{},[50,767,768],{},"No",[50,770,771],{},"Same as varchar",[50,773,774],{},"Default for all variable-length strings",[29,776,777,781,783,786],{},[50,778,779],{},[59,780,155],{},[50,782,768],{},[50,784,785],{},"Same as text",[50,787,788],{},"When a business-rule length limit is needed",[29,790,791,796,798,800],{},[50,792,793,795],{},[59,794,170],{}," (no n)",[50,797,768],{},[50,799,785],{},[50,801,802,803,805,806,808],{},"Identical to ",[59,804,151],{}," — use ",[59,807,151],{}," instead",[29,810,811,815,818,821],{},[50,812,813],{},[59,814,159],{},[50,816,817],{},"Yes (space-padded)",[50,819,820],{},"Same (padding is overhead)",[50,822,823,824,827],{},"Fixed-width codes only (e.g., ",[59,825,826],{},"char(2)"," for ISO country codes)",[18,829,831],{"id":830},"null-three-valued-logic-3vl","NULL — Three-Valued Logic (3VL)",[14,833,834,836,837,840,841,99,843,846,847,849,850,853,854,99,856,858,859,862,863,865],{},[59,835,200],{}," means \"unknown\" or \"not applicable\" — it is ",[53,838,839],{},"not"," the same as ",[59,842,595],{},[59,844,845],{},"''"," (empty string), or ",[59,848,197],{},". SQL uses ",[53,851,852],{},"three-valued logic",": every comparison evaluates to ",[59,855,194],{},[59,857,197],{},", or ",[59,860,861],{},"UNKNOWN"," (also called ",[59,864,200],{},").",[867,868,870],"h3",{"id":869},"truth-tables","Truth Tables",[14,872,873,876],{},[53,874,875],{},"AND",":",[23,878,879,892],{},[26,880,881],{},[29,882,883,886,889],{},[32,884,885],{},"A",[32,887,888],{},"B",[32,890,891],{},"A AND B",[45,893,894,902,910,918,927],{},[29,895,896,898,900],{},[50,897,194],{},[50,899,194],{},[50,901,194],{},[29,903,904,906,908],{},[50,905,194],{},[50,907,197],{},[50,909,197],{},[29,911,912,914,916],{},[50,913,194],{},[50,915,861],{},[50,917,861],{},[29,919,920,922,925],{},[50,921,197],{},[50,923,924],{},"*",[50,926,197],{},[29,928,929,931,933],{},[50,930,861],{},[50,932,861],{},[50,934,861],{},[14,936,937,876],{},[53,938,939],{},"OR",[23,941,942,953],{},[26,943,944],{},[29,945,946,948,950],{},[32,947,885],{},[32,949,888],{},[32,951,952],{},"A OR B",[45,954,955,963,971,979],{},[29,956,957,959,961],{},[50,958,194],{},[50,960,924],{},[50,962,194],{},[29,964,965,967,969],{},[50,966,197],{},[50,968,197],{},[50,970,197],{},[29,972,973,975,977],{},[50,974,197],{},[50,976,861],{},[50,978,861],{},[29,980,981,983,985],{},[50,982,861],{},[50,984,861],{},[50,986,861],{},[14,988,989,876],{},[53,990,991],{},"NOT",[23,993,994,1003],{},[26,995,996],{},[29,997,998,1000],{},[32,999,885],{},[32,1001,1002],{},"NOT A",[45,1004,1005,1011,1017],{},[29,1006,1007,1009],{},[50,1008,194],{},[50,1010,197],{},[29,1012,1013,1015],{},[50,1014,197],{},[50,1016,194],{},[29,1018,1019,1021],{},[50,1020,861],{},[50,1022,861],{},[14,1024,1025,1028,1029,1032,1033,1036,1037,1040,1041,201],{},[53,1026,1027],{},"Key insight",": ",[59,1030,1031],{},"FALSE AND UNKNOWN = FALSE"," (one false makes AND false regardless of the unknown), but ",[59,1034,1035],{},"TRUE AND UNKNOWN = UNKNOWN"," (the unknown could still be false). Symmetrically, ",[59,1038,1039],{},"TRUE OR UNKNOWN = TRUE",", but ",[59,1042,1043],{},"FALSE OR UNKNOWN = UNKNOWN",[867,1045,1047],{"id":1046},"where-keeps-only-true","WHERE Keeps Only TRUE",[14,1049,1050,1053,1054,1056,1057,1059,1060,1059,1064,1066,1067,1070,1071,400,1074,1076,1077,1079,1080,201],{},[59,1051,1052],{},"WHERE"," retains rows where the condition is ",[53,1055,194],{},". Rows where it's ",[59,1058,197],{}," ",[1061,1062,1063],"em",{},"or",[59,1065,861],{}," are discarded. This is why ",[59,1068,1069],{},"WHERE col = NULL"," returns nothing — ",[59,1072,1073],{},"col = NULL",[59,1075,861],{},", and ",[59,1078,861],{}," is not ",[59,1081,194],{},[415,1083,1084],{"language":417},[419,1085,1087],{"className":421,"code":1086,"language":417,"meta":423,"style":423},"-- NULL comparisons — ALL of these evaluate to UNKNOWN, never TRUE or FALSE.\nNULL  = NULL    -- UNKNOWN (not TRUE! two NULLs are not \"equal\")\nNULL \u003C> NULL    -- UNKNOWN (not TRUE! can't confirm they're different)\nNULL  = 5       -- UNKNOWN\nNULL \u003C> 5       -- UNKNOWN\nNULL  > 5       -- UNKNOWN\nNULL  \u003C 5       -- UNKNOWN\n\n-- WHERE keeps only TRUE → WHERE col = NULL returns ZERO rows (UNKNOWN is not TRUE).\nSELECT * FROM customers WHERE city = NULL;    -- ❌ returns nothing, always\nSELECT * FROM customers WHERE city IS NULL;   -- ✅ correct NULL test\n",[59,1088,1089,1094,1107,1119,1131,1141,1152,1163,1167,1172,1200],{"__ignoreMap":423},[427,1090,1091],{"class":429,"line":430},[427,1092,1093],{"class":433},"-- NULL comparisons — ALL of these evaluate to UNKNOWN, never TRUE or FALSE.\n",[427,1095,1096,1098,1101,1104],{"class":429,"line":437},[427,1097,200],{"class":440},[427,1099,1100],{"class":440},"  =",[427,1102,1103],{"class":440}," NULL",[427,1105,1106],{"class":433},"    -- UNKNOWN (not TRUE! two NULLs are not \"equal\")\n",[427,1108,1109,1111,1114,1116],{"class":429,"line":479},[427,1110,200],{"class":440},[427,1112,1113],{"class":440}," \u003C>",[427,1115,1103],{"class":440},[427,1117,1118],{"class":433},"    -- UNKNOWN (not TRUE! can't confirm they're different)\n",[427,1120,1121,1123,1125,1128],{"class":429,"line":486},[427,1122,200],{"class":440},[427,1124,1100],{"class":440},[427,1126,1127],{"class":444}," 5",[427,1129,1130],{"class":433},"       -- UNKNOWN\n",[427,1132,1133,1135,1137,1139],{"class":429,"line":492},[427,1134,200],{"class":440},[427,1136,1113],{"class":440},[427,1138,1127],{"class":444},[427,1140,1130],{"class":433},[427,1142,1143,1145,1148,1150],{"class":429,"line":526},[427,1144,200],{"class":440},[427,1146,1147],{"class":440},"  >",[427,1149,1127],{"class":444},[427,1151,1130],{"class":433},[427,1153,1154,1156,1159,1161],{"class":429,"line":531},[427,1155,200],{"class":440},[427,1157,1158],{"class":440},"  \u003C",[427,1160,1127],{"class":444},[427,1162,1130],{"class":433},[427,1164,1165],{"class":429,"line":537},[427,1166,483],{"emptyLinePlaceholder":482},[427,1168,1169],{"class":429,"line":543},[427,1170,1171],{"class":433},"-- WHERE keeps only TRUE → WHERE col = NULL returns ZERO rows (UNKNOWN is not TRUE).\n",[427,1173,1174,1176,1179,1182,1185,1187,1190,1192,1194,1197],{"class":429,"line":562},[427,1175,441],{"class":440},[427,1177,1178],{"class":440}," *",[427,1180,1181],{"class":440}," FROM",[427,1183,1184],{"class":448}," customers ",[427,1186,1052],{"class":440},[427,1188,1189],{"class":448}," city ",[427,1191,709],{"class":440},[427,1193,1103],{"class":440},[427,1195,1196],{"class":448},";    ",[427,1198,1199],{"class":433},"-- ❌ returns nothing, always\n",[427,1201,1202,1204,1206,1208,1210,1212,1214,1217,1219,1222],{"class":429,"line":578},[427,1203,441],{"class":440},[427,1205,1178],{"class":440},[427,1207,1181],{"class":440},[427,1209,1184],{"class":448},[427,1211,1052],{"class":440},[427,1213,1189],{"class":448},[427,1215,1216],{"class":440},"IS",[427,1218,1103],{"class":440},[427,1220,1221],{"class":448},";   ",[427,1223,1224],{"class":433},"-- ✅ correct NULL test\n",[18,1226,1228],{"id":1227},"null-safe-equality-is-not-distinct-from","NULL-Safe Equality: IS NOT DISTINCT FROM",[14,1230,1231,1234,1235,1237,1238,1241,1242,1245],{},[59,1232,1233],{},"IS NOT DISTINCT FROM"," treats NULLs as equal to NULL (the \"null-safe ",[59,1236,709],{},"\"). ",[59,1239,1240],{},"IS DISTINCT FROM"," is the \"null-safe ",[59,1243,1244],{},"\u003C>","\":",[415,1247,1248],{"language":417},[419,1249,1251],{"className":421,"code":1250,"language":417,"meta":423,"style":423},"-- IS NOT DISTINCT FROM: NULL-safe equality (NULL = NULL → TRUE)\nNULL IS NOT DISTINCT FROM NULL   -- TRUE   (unlike NULL = NULL which is UNKNOWN)\nNULL IS DISTINCT FROM NULL       -- FALSE\n5   IS NOT DISTINCT FROM 5       -- TRUE\n5   IS DISTINCT FROM 6           -- TRUE\nNULL IS DISTINCT FROM 5          -- TRUE   (NULL is distinct from any non-NULL)\n\n-- Practical use: join on nullable columns where NULL should match NULL.\n-- Caveat: can't use a plain B-tree index → slower than = on indexed columns.\nSELECT * FROM a JOIN b ON a.key IS NOT DISTINCT FROM b.key;\n",[59,1252,1253,1258,1278,1293,1312,1328,1343,1347,1352,1357],{"__ignoreMap":423},[427,1254,1255],{"class":429,"line":430},[427,1256,1257],{"class":433},"-- IS NOT DISTINCT FROM: NULL-safe equality (NULL = NULL → TRUE)\n",[427,1259,1260,1262,1265,1268,1271,1273,1275],{"class":429,"line":437},[427,1261,200],{"class":440},[427,1263,1264],{"class":440}," IS",[427,1266,1267],{"class":440}," NOT",[427,1269,1270],{"class":440}," DISTINCT",[427,1272,1181],{"class":440},[427,1274,1103],{"class":440},[427,1276,1277],{"class":433},"   -- TRUE   (unlike NULL = NULL which is UNKNOWN)\n",[427,1279,1280,1282,1284,1286,1288,1290],{"class":429,"line":479},[427,1281,200],{"class":440},[427,1283,1264],{"class":440},[427,1285,1270],{"class":440},[427,1287,1181],{"class":440},[427,1289,1103],{"class":440},[427,1291,1292],{"class":433},"       -- FALSE\n",[427,1294,1295,1298,1301,1303,1305,1307,1309],{"class":429,"line":486},[427,1296,1297],{"class":444},"5",[427,1299,1300],{"class":440},"   IS",[427,1302,1267],{"class":440},[427,1304,1270],{"class":440},[427,1306,1181],{"class":440},[427,1308,1127],{"class":444},[427,1310,1311],{"class":433},"       -- TRUE\n",[427,1313,1314,1316,1318,1320,1322,1325],{"class":429,"line":492},[427,1315,1297],{"class":444},[427,1317,1300],{"class":440},[427,1319,1270],{"class":440},[427,1321,1181],{"class":440},[427,1323,1324],{"class":444}," 6",[427,1326,1327],{"class":433},"           -- TRUE\n",[427,1329,1330,1332,1334,1336,1338,1340],{"class":429,"line":526},[427,1331,200],{"class":440},[427,1333,1264],{"class":440},[427,1335,1270],{"class":440},[427,1337,1181],{"class":440},[427,1339,1127],{"class":444},[427,1341,1342],{"class":433},"          -- TRUE   (NULL is distinct from any non-NULL)\n",[427,1344,1345],{"class":429,"line":531},[427,1346,483],{"emptyLinePlaceholder":482},[427,1348,1349],{"class":429,"line":537},[427,1350,1351],{"class":433},"-- Practical use: join on nullable columns where NULL should match NULL.\n",[427,1353,1354],{"class":429,"line":543},[427,1355,1356],{"class":433},"-- Caveat: can't use a plain B-tree index → slower than = on indexed columns.\n",[427,1358,1359,1361,1363,1365,1368,1371,1374,1377,1380,1382,1385,1387,1389,1391,1393,1396,1398,1400],{"class":429,"line":562},[427,1360,441],{"class":440},[427,1362,1178],{"class":440},[427,1364,1181],{"class":440},[427,1366,1367],{"class":448}," a ",[427,1369,1370],{"class":440},"JOIN",[427,1372,1373],{"class":448}," b ",[427,1375,1376],{"class":440},"ON",[427,1378,1379],{"class":444}," a",[427,1381,201],{"class":448},[427,1383,1384],{"class":444},"key",[427,1386,1264],{"class":440},[427,1388,1267],{"class":440},[427,1390,1270],{"class":440},[427,1392,1181],{"class":440},[427,1394,1395],{"class":444}," b",[427,1397,201],{"class":448},[427,1399,1384],{"class":444},[427,1401,1402],{"class":448},";\n",[18,1404,1406],{"id":1405},"null-handling-functions","NULL-Handling Functions",[23,1408,1409,1419],{},[26,1410,1411],{},[29,1412,1413,1416],{},[32,1414,1415],{},"Function",[32,1417,1418],{},"Behavior",[45,1420,1421,1431,1449],{},[29,1422,1423,1428],{},[50,1424,1425],{},[59,1426,1427],{},"COALESCE(a, b, c, ...)",[50,1429,1430],{},"Returns the first non-NULL argument; NULL if all are NULL.",[29,1432,1433,1438],{},[50,1434,1435],{},[59,1436,1437],{},"NULLIF(a, b)",[50,1439,1440,1441,1444,1445,1448],{},"Returns NULL if ",[59,1442,1443],{},"a = b",", else ",[59,1446,1447],{},"a",". Idiom for avoiding divide-by-zero.",[29,1450,1451,1460],{},[50,1452,1453,1456,1457],{},[59,1454,1455],{},"GREATEST(a, b, ...)"," \u002F ",[59,1458,1459],{},"LEAST(a, b, ...)",[50,1461,1462,1463,1466],{},"Max\u002Fmin, but return NULL if ",[53,1464,1465],{},"any"," argument is NULL (PostgreSQL).",[867,1468,1470],{"id":1469},"coalesce-fallback-chain","COALESCE — Fallback Chain",[415,1472,1473],{"language":417},[419,1474,1476],{"className":421,"code":1475,"language":417,"meta":423,"style":423},"-- Provide a fallback for missing values.\nSELECT name, COALESCE(city, 'Unknown') AS city FROM customers;\n\n-- Multiple fallbacks: try preferred_name, then full_name, then email, then 'Anonymous'.\nSELECT COALESCE(preferred_name, full_name, email, 'Anonymous') AS display_name\nFROM users;\n\n-- Default a SUM to 0 (SUM of no rows is NULL, not 0).\n-- Without COALESCE, a customer with no orders shows NULL instead of 0.\nSELECT COALESCE(SUM(amount), 0) AS total\nFROM orders\nWHERE customer_id = 999;                     -- no rows match → SUM = NULL → COALESCE → 0\n",[59,1477,1478,1483,1512,1516,1521,1541,1548,1552,1557,1562,1585,1592],{"__ignoreMap":423},[427,1479,1480],{"class":429,"line":430},[427,1481,1482],{"class":433},"-- Provide a fallback for missing values.\n",[427,1484,1485,1487,1490,1492,1495,1498,1501,1503,1505,1507,1509],{"class":429,"line":437},[427,1486,441],{"class":440},[427,1488,1489],{"class":440}," name",[427,1491,99],{"class":448},[427,1493,1494],{"class":444},"COALESCE",[427,1496,1497],{"class":448},"(city, ",[427,1499,1500],{"class":693},"'Unknown'",[427,1502,607],{"class":448},[427,1504,610],{"class":440},[427,1506,1189],{"class":448},[427,1508,581],{"class":440},[427,1510,1511],{"class":448}," customers;\n",[427,1513,1514],{"class":429,"line":479},[427,1515,483],{"emptyLinePlaceholder":482},[427,1517,1518],{"class":429,"line":486},[427,1519,1520],{"class":433},"-- Multiple fallbacks: try preferred_name, then full_name, then email, then 'Anonymous'.\n",[427,1522,1523,1525,1528,1531,1534,1536,1538],{"class":429,"line":492},[427,1524,441],{"class":440},[427,1526,1527],{"class":444}," COALESCE",[427,1529,1530],{"class":448},"(preferred_name, full_name, email, ",[427,1532,1533],{"class":693},"'Anonymous'",[427,1535,607],{"class":448},[427,1537,610],{"class":440},[427,1539,1540],{"class":448}," display_name\n",[427,1542,1543,1545],{"class":429,"line":526},[427,1544,581],{"class":440},[427,1546,1547],{"class":448}," users;\n",[427,1549,1550],{"class":429,"line":531},[427,1551,483],{"emptyLinePlaceholder":482},[427,1553,1554],{"class":429,"line":537},[427,1555,1556],{"class":433},"-- Default a SUM to 0 (SUM of no rows is NULL, not 0).\n",[427,1558,1559],{"class":429,"line":543},[427,1560,1561],{"class":433},"-- Without COALESCE, a customer with no orders shows NULL instead of 0.\n",[427,1563,1564,1566,1568,1570,1573,1576,1578,1580,1582],{"class":429,"line":562},[427,1565,441],{"class":440},[427,1567,1527],{"class":444},[427,1569,592],{"class":448},[427,1571,1572],{"class":444},"SUM",[427,1574,1575],{"class":448},"(amount), ",[427,1577,595],{"class":444},[427,1579,607],{"class":448},[427,1581,610],{"class":440},[427,1583,1584],{"class":448}," total\n",[427,1586,1587,1589],{"class":429,"line":578},[427,1588,581],{"class":440},[427,1590,1591],{"class":448}," orders\n",[427,1593,1595,1597,1600,1602,1605,1608],{"class":429,"line":1594},12,[427,1596,1052],{"class":440},[427,1598,1599],{"class":448}," customer_id ",[427,1601,709],{"class":440},[427,1603,1604],{"class":444}," 999",[427,1606,1607],{"class":448},";                     ",[427,1609,1610],{"class":433},"-- no rows match → SUM = NULL → COALESCE → 0\n",[867,1612,1614],{"id":1613},"nullif-the-divide-by-zero-fix","NULLIF — The Divide-by-Zero Fix",[415,1616,1617],{"language":417},[419,1618,1620],{"className":421,"code":1619,"language":417,"meta":423,"style":423},"-- ❌ Division by zero error if any total is 0.\nSELECT sales \u002F total FROM metrics;\n\n-- ✅ NULLIF turns 0 into NULL; division by NULL yields NULL (not an error).\nSELECT sales \u002F NULLIF(total, 0) AS ratio FROM metrics;\n\n-- Then COALESCE the NULL result if you want 0 or some sentinel.\nSELECT COALESCE(sales \u002F NULLIF(total, 0), 0) AS ratio FROM metrics;\n\n-- NULLIF is also useful for normalizing sentinel values to NULL.\nSELECT NULLIF(status, '') AS status FROM logs;        -- empty string → NULL\nSELECT NULLIF(status, 'unknown') AS status FROM logs; -- 'unknown' → NULL\n",[59,1621,1622,1627,1644,1648,1653,1680,1684,1689,1721,1725,1730,1760],{"__ignoreMap":423},[427,1623,1624],{"class":429,"line":430},[427,1625,1626],{"class":433},"-- ❌ Division by zero error if any total is 0.\n",[427,1628,1629,1631,1634,1636,1639,1641],{"class":429,"line":437},[427,1630,441],{"class":440},[427,1632,1633],{"class":448}," sales ",[427,1635,66],{"class":440},[427,1637,1638],{"class":448}," total ",[427,1640,581],{"class":440},[427,1642,1643],{"class":448}," metrics;\n",[427,1645,1646],{"class":429,"line":479},[427,1647,483],{"emptyLinePlaceholder":482},[427,1649,1650],{"class":429,"line":486},[427,1651,1652],{"class":433},"-- ✅ NULLIF turns 0 into NULL; division by NULL yields NULL (not an error).\n",[427,1654,1655,1657,1659,1661,1664,1667,1669,1671,1673,1676,1678],{"class":429,"line":492},[427,1656,441],{"class":440},[427,1658,1633],{"class":448},[427,1660,66],{"class":440},[427,1662,1663],{"class":444}," NULLIF",[427,1665,1666],{"class":448},"(total, ",[427,1668,595],{"class":444},[427,1670,607],{"class":448},[427,1672,610],{"class":440},[427,1674,1675],{"class":448}," ratio ",[427,1677,581],{"class":440},[427,1679,1643],{"class":448},[427,1681,1682],{"class":429,"line":526},[427,1683,483],{"emptyLinePlaceholder":482},[427,1685,1686],{"class":429,"line":531},[427,1687,1688],{"class":433},"-- Then COALESCE the NULL result if you want 0 or some sentinel.\n",[427,1690,1691,1693,1695,1698,1700,1702,1704,1706,1709,1711,1713,1715,1717,1719],{"class":429,"line":537},[427,1692,441],{"class":440},[427,1694,1527],{"class":444},[427,1696,1697],{"class":448},"(sales ",[427,1699,66],{"class":440},[427,1701,1663],{"class":444},[427,1703,1666],{"class":448},[427,1705,595],{"class":444},[427,1707,1708],{"class":448},"), ",[427,1710,595],{"class":444},[427,1712,607],{"class":448},[427,1714,610],{"class":440},[427,1716,1675],{"class":448},[427,1718,581],{"class":440},[427,1720,1643],{"class":448},[427,1722,1723],{"class":429,"line":543},[427,1724,483],{"emptyLinePlaceholder":482},[427,1726,1727],{"class":429,"line":562},[427,1728,1729],{"class":433},"-- NULLIF is also useful for normalizing sentinel values to NULL.\n",[427,1731,1732,1734,1736,1738,1741,1743,1745,1747,1749,1752,1754,1757],{"class":429,"line":578},[427,1733,441],{"class":440},[427,1735,1663],{"class":444},[427,1737,592],{"class":448},[427,1739,1740],{"class":440},"status",[427,1742,99],{"class":448},[427,1744,845],{"class":693},[427,1746,607],{"class":448},[427,1748,610],{"class":440},[427,1750,1751],{"class":440}," status",[427,1753,1181],{"class":440},[427,1755,1756],{"class":448}," logs;        ",[427,1758,1759],{"class":433},"-- empty string → NULL\n",[427,1761,1762,1764,1766,1768,1770,1772,1775,1777,1779,1781,1783,1786],{"class":429,"line":1594},[427,1763,441],{"class":440},[427,1765,1663],{"class":444},[427,1767,592],{"class":448},[427,1769,1740],{"class":440},[427,1771,99],{"class":448},[427,1773,1774],{"class":693},"'unknown'",[427,1776,607],{"class":448},[427,1778,610],{"class":440},[427,1780,1751],{"class":440},[427,1782,1181],{"class":440},[427,1784,1785],{"class":448}," logs; ",[427,1787,1788],{"class":433},"-- 'unknown' → NULL\n",[18,1790,1792],{"id":1791},"null-in-aggregates","NULL in Aggregates",[14,1794,1795,1796,1799,1800,1803],{},"Aggregates ",[53,1797,1798],{},"ignore NULL"," (except ",[59,1801,1802],{},"COUNT(*)","):",[415,1805,1806],{"language":417},[419,1807,1809],{"className":421,"code":1808,"language":417,"meta":423,"style":423},"-- AVG ignores NULLs: AVG of [10, 20, NULL] = 15 (avg of 10 and 20), NOT 10 (avg of 10, 20, 0).\nSELECT AVG(amount) FROM orders;\n\n-- COUNT(*) counts ALL rows (including NULL rows). COUNT(col) counts non-NULL values only.\n-- If amount has NULLs, COUNT(amount) \u003C COUNT(*).\nSELECT COUNT(*) AS total_rows, COUNT(amount) AS non_null_amounts FROM orders;\n\n-- SUM of all-NULL (or empty set) is NULL, not 0. Use COALESCE for 0.\nSELECT SUM(amount) FROM orders WHERE FALSE;              -- NULL\nSELECT COALESCE(SUM(amount), 0) FROM orders WHERE FALSE; -- 0\n\n-- If NULL means \"zero\" (not \"missing\"), average them as 0:\nSELECT AVG(COALESCE(amount, 0)) FROM orders;  -- AVG of [10, 20, 0] = 10, not 15\n-- But ask: is NULL really 0, or is it \"unknown\"? Semantically different.\n",[59,1810,1811,1816,1831,1835,1840,1845,1877,1881,1886,1907,1935,1939,1944,1971],{"__ignoreMap":423},[427,1812,1813],{"class":429,"line":430},[427,1814,1815],{"class":433},"-- AVG ignores NULLs: AVG of [10, 20, NULL] = 15 (avg of 10 and 20), NOT 10 (avg of 10, 20, 0).\n",[427,1817,1818,1820,1823,1826,1828],{"class":429,"line":437},[427,1819,441],{"class":440},[427,1821,1822],{"class":444}," AVG",[427,1824,1825],{"class":448},"(amount) ",[427,1827,581],{"class":440},[427,1829,1830],{"class":448}," orders;\n",[427,1832,1833],{"class":429,"line":479},[427,1834,483],{"emptyLinePlaceholder":482},[427,1836,1837],{"class":429,"line":486},[427,1838,1839],{"class":433},"-- COUNT(*) counts ALL rows (including NULL rows). COUNT(col) counts non-NULL values only.\n",[427,1841,1842],{"class":429,"line":492},[427,1843,1844],{"class":433},"-- If amount has NULLs, COUNT(amount) \u003C COUNT(*).\n",[427,1846,1847,1849,1852,1854,1856,1858,1860,1863,1866,1868,1870,1873,1875],{"class":429,"line":526},[427,1848,441],{"class":440},[427,1850,1851],{"class":444}," COUNT",[427,1853,592],{"class":448},[427,1855,924],{"class":440},[427,1857,607],{"class":448},[427,1859,610],{"class":440},[427,1861,1862],{"class":448}," total_rows, ",[427,1864,1865],{"class":444},"COUNT",[427,1867,1825],{"class":448},[427,1869,610],{"class":440},[427,1871,1872],{"class":448}," non_null_amounts ",[427,1874,581],{"class":440},[427,1876,1830],{"class":448},[427,1878,1879],{"class":429,"line":531},[427,1880,483],{"emptyLinePlaceholder":482},[427,1882,1883],{"class":429,"line":537},[427,1884,1885],{"class":433},"-- SUM of all-NULL (or empty set) is NULL, not 0. Use COALESCE for 0.\n",[427,1887,1888,1890,1892,1894,1896,1899,1901,1904],{"class":429,"line":543},[427,1889,441],{"class":440},[427,1891,589],{"class":444},[427,1893,1825],{"class":448},[427,1895,581],{"class":440},[427,1897,1898],{"class":448}," orders ",[427,1900,1052],{"class":440},[427,1902,1903],{"class":448}," FALSE;              ",[427,1905,1906],{"class":433},"-- NULL\n",[427,1908,1909,1911,1913,1915,1917,1919,1921,1923,1925,1927,1929,1932],{"class":429,"line":562},[427,1910,441],{"class":440},[427,1912,1527],{"class":444},[427,1914,592],{"class":448},[427,1916,1572],{"class":444},[427,1918,1575],{"class":448},[427,1920,595],{"class":444},[427,1922,607],{"class":448},[427,1924,581],{"class":440},[427,1926,1898],{"class":448},[427,1928,1052],{"class":440},[427,1930,1931],{"class":448}," FALSE; ",[427,1933,1934],{"class":433},"-- 0\n",[427,1936,1937],{"class":429,"line":578},[427,1938,483],{"emptyLinePlaceholder":482},[427,1940,1941],{"class":429,"line":1594},[427,1942,1943],{"class":433},"-- If NULL means \"zero\" (not \"missing\"), average them as 0:\n",[427,1945,1947,1949,1951,1953,1955,1958,1960,1963,1965,1968],{"class":429,"line":1946},13,[427,1948,441],{"class":440},[427,1950,1822],{"class":444},[427,1952,592],{"class":448},[427,1954,1494],{"class":444},[427,1956,1957],{"class":448},"(amount, ",[427,1959,595],{"class":444},[427,1961,1962],{"class":448},")) ",[427,1964,581],{"class":440},[427,1966,1967],{"class":448}," orders;  ",[427,1969,1970],{"class":433},"-- AVG of [10, 20, 0] = 10, not 15\n",[427,1972,1974],{"class":429,"line":1973},14,[427,1975,1976],{"class":433},"-- But ask: is NULL really 0, or is it \"unknown\"? Semantically different.\n",[18,1978,1980],{"id":1979},"null-in-string-concatenation","NULL in String Concatenation",[415,1982,1983],{"language":417},[419,1984,1986],{"className":421,"code":1985,"language":417,"meta":423,"style":423},"-- String concatenation with || : any NULL operand → entire result is NULL.\nSELECT 'Hello ' || NULL;           -- NULL (the whole string becomes NULL)\nSELECT 'Hello ' || name || '!' FROM customers;  -- NULL if name is NULL\n\n-- Fix 1: COALESCE each operand to empty string.\nSELECT 'Hello ' || COALESCE(name, '') || '!' FROM customers;\n\n-- Fix 2: CONCAT() ignores NULLs (PostgreSQL, MySQL).\nSELECT CONCAT('Hello ', name, '!') FROM customers;  -- NULLs skipped, not propagated\n\n-- Fix 3: CONCAT_WS (with separator) — skips NULLs but keeps separators between non-NULLs.\nSELECT CONCAT_WS(' ', first_name, middle_name, last_name) FROM customers;\n-- 'John Q Public' or 'John Public' (middle_name NULL → separator not doubled)\n",[59,1987,1988,1993,2011,2034,2038,2043,2073,2077,2082,2112,2116,2121,2138],{"__ignoreMap":423},[427,1989,1990],{"class":429,"line":430},[427,1991,1992],{"class":433},"-- String concatenation with || : any NULL operand → entire result is NULL.\n",[427,1994,1995,1997,2000,2003,2005,2008],{"class":429,"line":437},[427,1996,441],{"class":440},[427,1998,1999],{"class":693}," 'Hello '",[427,2001,2002],{"class":440}," ||",[427,2004,1103],{"class":440},[427,2006,2007],{"class":448},";           ",[427,2009,2010],{"class":433},"-- NULL (the whole string becomes NULL)\n",[427,2012,2013,2015,2017,2019,2021,2023,2026,2028,2031],{"class":429,"line":479},[427,2014,441],{"class":440},[427,2016,1999],{"class":693},[427,2018,2002],{"class":440},[427,2020,1489],{"class":440},[427,2022,2002],{"class":440},[427,2024,2025],{"class":693}," '!'",[427,2027,1181],{"class":440},[427,2029,2030],{"class":448}," customers;  ",[427,2032,2033],{"class":433},"-- NULL if name is NULL\n",[427,2035,2036],{"class":429,"line":486},[427,2037,483],{"emptyLinePlaceholder":482},[427,2039,2040],{"class":429,"line":492},[427,2041,2042],{"class":433},"-- Fix 1: COALESCE each operand to empty string.\n",[427,2044,2045,2047,2049,2051,2053,2055,2058,2060,2062,2064,2067,2069,2071],{"class":429,"line":526},[427,2046,441],{"class":440},[427,2048,1999],{"class":693},[427,2050,2002],{"class":440},[427,2052,1527],{"class":444},[427,2054,592],{"class":448},[427,2056,2057],{"class":440},"name",[427,2059,99],{"class":448},[427,2061,845],{"class":693},[427,2063,607],{"class":448},[427,2065,2066],{"class":440},"||",[427,2068,2025],{"class":693},[427,2070,1181],{"class":440},[427,2072,1511],{"class":448},[427,2074,2075],{"class":429,"line":531},[427,2076,483],{"emptyLinePlaceholder":482},[427,2078,2079],{"class":429,"line":537},[427,2080,2081],{"class":433},"-- Fix 2: CONCAT() ignores NULLs (PostgreSQL, MySQL).\n",[427,2083,2084,2086,2089,2091,2094,2096,2098,2100,2103,2105,2107,2109],{"class":429,"line":543},[427,2085,441],{"class":440},[427,2087,2088],{"class":444}," CONCAT",[427,2090,592],{"class":448},[427,2092,2093],{"class":693},"'Hello '",[427,2095,99],{"class":448},[427,2097,2057],{"class":440},[427,2099,99],{"class":448},[427,2101,2102],{"class":693},"'!'",[427,2104,607],{"class":448},[427,2106,581],{"class":440},[427,2108,2030],{"class":448},[427,2110,2111],{"class":433},"-- NULLs skipped, not propagated\n",[427,2113,2114],{"class":429,"line":562},[427,2115,483],{"emptyLinePlaceholder":482},[427,2117,2118],{"class":429,"line":578},[427,2119,2120],{"class":433},"-- Fix 3: CONCAT_WS (with separator) — skips NULLs but keeps separators between non-NULLs.\n",[427,2122,2123,2125,2128,2131,2134,2136],{"class":429,"line":1594},[427,2124,441],{"class":440},[427,2126,2127],{"class":448}," CONCAT_WS(",[427,2129,2130],{"class":693},"' '",[427,2132,2133],{"class":448},", first_name, middle_name, last_name) ",[427,2135,581],{"class":440},[427,2137,1511],{"class":448},[427,2139,2140],{"class":429,"line":1946},[427,2141,2142],{"class":433},"-- 'John Q Public' or 'John Public' (middle_name NULL → separator not doubled)\n",[18,2144,2146],{"id":2145},"null-in-boolean-contexts","NULL in Boolean Contexts",[415,2148,2149],{"language":417},[419,2150,2152],{"className":421,"code":2151,"language":417,"meta":423,"style":423},"-- In WHERE, NULL is treated as \"not true\" (excluded).\nWHERE col           -- keeps rows where col IS TRUE (NULL and FALSE excluded)\nWHERE NOT col       -- keeps rows where col IS FALSE (NULL and TRUE excluded) — NULL is NOT excluded!\nWHERE col = TRUE    -- keeps only TRUE (NULL → UNKNOWN → excluded)\nWHERE col IS TRUE   -- keeps only TRUE (clearer, explicit)\nWHERE col IS FALSE  -- keeps only FALSE\nWHERE col IS UNKNOWN -- keeps only NULL (the only test that finds NULL booleans)\n\n-- The subtle trap: WHERE NOT col EXCLUDES NULLs.\n-- NOT NULL = UNKNOWN (not TRUE). So 'WHERE NOT active' drops rows where active IS NULL.\n-- English \"not active\" feels like it should include NULL — SQL doesn't agree.\n",[59,2153,2154,2159,2169,2181,2196,2210,2224,2238,2242,2247,2252],{"__ignoreMap":423},[427,2155,2156],{"class":429,"line":430},[427,2157,2158],{"class":433},"-- In WHERE, NULL is treated as \"not true\" (excluded).\n",[427,2160,2161,2163,2166],{"class":429,"line":437},[427,2162,1052],{"class":440},[427,2164,2165],{"class":448}," col           ",[427,2167,2168],{"class":433},"-- keeps rows where col IS TRUE (NULL and FALSE excluded)\n",[427,2170,2171,2173,2175,2178],{"class":429,"line":479},[427,2172,1052],{"class":440},[427,2174,1267],{"class":440},[427,2176,2177],{"class":448}," col       ",[427,2179,2180],{"class":433},"-- keeps rows where col IS FALSE (NULL and TRUE excluded) — NULL is NOT excluded!\n",[427,2182,2183,2185,2188,2190,2193],{"class":429,"line":486},[427,2184,1052],{"class":440},[427,2186,2187],{"class":448}," col ",[427,2189,709],{"class":440},[427,2191,2192],{"class":448}," TRUE    ",[427,2194,2195],{"class":433},"-- keeps only TRUE (NULL → UNKNOWN → excluded)\n",[427,2197,2198,2200,2202,2204,2207],{"class":429,"line":492},[427,2199,1052],{"class":440},[427,2201,2187],{"class":448},[427,2203,1216],{"class":440},[427,2205,2206],{"class":448}," TRUE   ",[427,2208,2209],{"class":433},"-- keeps only TRUE (clearer, explicit)\n",[427,2211,2212,2214,2216,2218,2221],{"class":429,"line":526},[427,2213,1052],{"class":440},[427,2215,2187],{"class":448},[427,2217,1216],{"class":440},[427,2219,2220],{"class":448}," FALSE  ",[427,2222,2223],{"class":433},"-- keeps only FALSE\n",[427,2225,2226,2228,2230,2232,2235],{"class":429,"line":531},[427,2227,1052],{"class":440},[427,2229,2187],{"class":448},[427,2231,1216],{"class":440},[427,2233,2234],{"class":448}," UNKNOWN ",[427,2236,2237],{"class":433},"-- keeps only NULL (the only test that finds NULL booleans)\n",[427,2239,2240],{"class":429,"line":537},[427,2241,483],{"emptyLinePlaceholder":482},[427,2243,2244],{"class":429,"line":543},[427,2245,2246],{"class":433},"-- The subtle trap: WHERE NOT col EXCLUDES NULLs.\n",[427,2248,2249],{"class":429,"line":562},[427,2250,2251],{"class":433},"-- NOT NULL = UNKNOWN (not TRUE). So 'WHERE NOT active' drops rows where active IS NULL.\n",[427,2253,2254],{"class":429,"line":578},[427,2255,2256],{"class":433},"-- English \"not active\" feels like it should include NULL — SQL doesn't agree.\n",[18,2258,2260],{"id":2259},"null-in-constraints","NULL in Constraints",[2262,2263,2264,2273,2284,2297],"ul",{},[2265,2266,2267,2272],"li",{},[53,2268,2269],{},[59,2270,2271],{},"NOT NULL"," — the column can't be NULL. The most important constraint; add it to every column unless you have a reason not to.",[2265,2274,2275,404,2280,2283],{},[53,2276,2277],{},[59,2278,2279],{},"UNIQUE",[53,2281,2282],{},"allows multiple NULLs"," in PostgreSQL and the SQL standard (NULLs aren't \"equal,\" so they don't violate uniqueness). SQL Server allows only one NULL. Be aware of this divergence.",[2265,2285,2286,2291,2292,167,2294,2296],{},[53,2287,2288],{},[59,2289,2290],{},"PRIMARY KEY"," — implies ",[59,2293,2271],{},[59,2295,2279],{},". A PK column can never be NULL.",[2265,2298,2299,2304],{},[53,2300,2301],{},[59,2302,2303],{},"FOREIGN KEY"," — a NULL FK value is allowed and means \"no reference\" (doesn't violate the FK constraint). This is how optional relationships are modeled.",[415,2306,2307],{"language":417},[419,2308,2310],{"className":421,"code":2309,"language":417,"meta":423,"style":423},"-- Partial unique index: \"at most one non-NULL value per group\" (portable across engines).\n-- UNIQUE constraint allows multiple NULLs; this index enforces uniqueness only on non-NULL rows.\nCREATE UNIQUE INDEX idx_users_email_unique\nON users (email)\nWHERE email IS NOT NULL;\n",[59,2311,2312,2317,2322,2334,2341],{"__ignoreMap":423},[427,2313,2314],{"class":429,"line":430},[427,2315,2316],{"class":433},"-- Partial unique index: \"at most one non-NULL value per group\" (portable across engines).\n",[427,2318,2319],{"class":429,"line":437},[427,2320,2321],{"class":433},"-- UNIQUE constraint allows multiple NULLs; this index enforces uniqueness only on non-NULL rows.\n",[427,2323,2324,2327,2330],{"class":429,"line":479},[427,2325,2326],{"class":440},"CREATE",[427,2328,2329],{"class":440}," UNIQUE INDEX",[427,2331,2333],{"class":2332},"sIsaT"," idx_users_email_unique\n",[427,2335,2336,2338],{"class":429,"line":486},[427,2337,1376],{"class":440},[427,2339,2340],{"class":448}," users (email)\n",[427,2342,2343,2345,2348,2351],{"class":429,"line":492},[427,2344,1052],{"class":440},[427,2346,2347],{"class":448}," email ",[427,2349,2350],{"class":440},"IS NOT NULL",[427,2352,1402],{"class":448},[18,2354,2356],{"id":2355},"null-and-set-operations","NULL and Set Operations",[14,2358,2359,2360,99,2363,99,2366,2369,2370,2373,2374,167,2377,2379,2380,2382,2383,2386,2387,2389],{},"Set operations (",[59,2361,2362],{},"UNION",[59,2364,2365],{},"INTERSECT",[59,2367,2368],{},"EXCEPT",") treat NULLs as ",[53,2371,2372],{},"equal"," for deduplication — ",[59,2375,2376],{},"(1, NULL)",[59,2378,2376],{}," are considered duplicates, and ",[59,2381,2362],{}," keeps one. This differs from ",[59,2384,2385],{},"NULL = NULL"," being UNKNOWN in ",[59,2388,1052],{},". Set ops use \"is not distinct from\" semantics internally.",[18,2391,2393],{"id":2392},"null-and-joins","NULL and Joins",[14,2395,2396,2397,2400,2401,2403,2404,2407,2408,2410,2411,2413],{},"A join ",[59,2398,2399],{},"ON a.col = b.col"," does ",[53,2402,839],{}," match rows where both ",[59,2405,2406],{},"col","s are NULL — ",[59,2409,2385],{}," is UNKNOWN, not TRUE. If you need NULL keys to match, use ",[59,2412,1233],{}," (slower, no index) or reconsider your schema (use a sentinel value, or split the table).",[18,2415,2417],{"id":2416},"null-and-not-in-the-killer-trap","NULL and NOT IN — The Killer Trap",[14,2419,2420,400,2423,2426,2427,2426,2430,2432,2433,2436,2437,2440,2441,2444],{},[59,2421,2422],{},"x NOT IN (a, b, NULL)",[59,2424,2425],{},"x \u003C> a AND x \u003C> b AND x \u003C> NULL"," = ",[59,2428,2429],{},"... AND UNKNOWN",[59,2431,861],{}," for every ",[59,2434,2435],{},"x",". The ",[53,2438,2439],{},"entire query returns zero rows",". See chapter 06. Use ",[59,2442,2443],{},"NOT EXISTS"," or filter NULLs.",[18,2446,2448],{"id":2447},"complex-implementation-production-schema-with-annotated-type-choices","Complex Implementation: Production Schema with Annotated Type Choices",[415,2450,2451],{"language":417},[419,2452,2454],{"className":421,"code":2453,"language":417,"meta":423,"style":423},"-- Production e-commerce schema with type choices annotated with reasoning.\n-- Every type is chosen deliberately; every NULL is chosen deliberately.\n\n-- Create the enum type (must be done before the table if used in a column).\nCREATE TYPE order_status AS ENUM ('pending', 'paid', 'shipped', 'delivered', 'returned', 'cancelled');\n\nCREATE TABLE customers (\n  -- bigint for PK: 8 bytes, supports > 2B rows. int (4B) would overflow at 2.1B.\n  id          bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,\n\n  -- text for names: no business-rule length limit, variable-length storage.\n  -- varchar(255) would be an arbitrary limit with no perf benefit in PostgreSQL.\n  name        text        NOT NULL,\n\n  -- citext (case-insensitive text) for email: queries and unique constraint\n  -- are case-insensitive without LOWER() in every query. Requires citext extension.\n  email       citext      NOT NULL UNIQUE,\n\n  -- numeric(10,2) for account_balance: exact decimal, 10 total digits, 2 after decimal.\n  -- Max value: $99,999,999.99. Never use float for money (0.1+0.2 ≠ 0.3 in float).\n  account_balance numeric(10,2) NOT NULL DEFAULT 0,\n\n  -- boolean for flags: 1 byte, clear semantics. NOT NULL with default.\n  is_active   boolean     NOT NULL DEFAULT true,\n\n  -- timestamptz for timestamps: stored as UTC, displayed in session timezone.\n  -- Never use plain 'timestamp' (without tz) for app timestamps — timezone bugs.\n  created_at  timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,\n  updated_at  timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,\n\n  -- jsonb for flexible metadata: binary, indexable, deduplicated keys.\n  -- Use for semi-structured data that doesn't warrant its own table.\n  metadata    jsonb\n);\n\nCREATE TABLE orders (\n  id          bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,\n\n  -- bigint FK: must match the PK type exactly (bigint → bigint).\n  customer_id bigint      NOT NULL REFERENCES customers(id),\n\n  -- numeric(12,2) for amount: 12 total digits, 2 after decimal.\n  -- Max: $9,999,999,999.99. Exact decimal for financial accuracy.\n  amount      numeric(12,2) NOT NULL CHECK (amount >= 0),\n\n  -- enum for status: small fixed set of labels, 4 bytes, type-safe.\n  -- Can't add values without ALTER TYPE (and can't remove values at all).\n  status      order_status NOT NULL DEFAULT 'pending',\n\n  -- uuid for external-facing IDs: 16 bytes, globally unique, no sequential leak.\n  -- Generated by the application or gen_random_uuid() (PG 13+).\n  external_ref uuid       UNIQUE,\n\n  -- timestamp with tz for the order date.\n  ordered_on  timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,\n\n  -- text[] array for tags: small, fixed-size list of labels.\n  -- For large or frequently-queried collections, use a separate tags table + join table.\n  tags        text[]      NOT NULL DEFAULT '{}'\n);\n\n-- Index on (customer_id, ordered_on DESC) for \"recent orders per customer\" queries.\nCREATE INDEX idx_orders_customer_date ON orders (customer_id, ordered_on DESC);\n",[59,2455,2456,2461,2466,2470,2475,2521,2525,2538,2543,2567,2571,2576,2581,2594,2598,2604,2610,2623,2628,2634,2640,2668,2673,2679,2695,2700,2706,2712,2728,2742,2747,2753,2759,2765,2770,2775,2787,2806,2811,2817,2834,2839,2845,2851,2886,2891,2897,2903,2921,2926,2932,2938,2948,2953,2959,2973,2978,2984,2990,3008,3013,3018,3024],{"__ignoreMap":423},[427,2457,2458],{"class":429,"line":430},[427,2459,2460],{"class":433},"-- Production e-commerce schema with type choices annotated with reasoning.\n",[427,2462,2463],{"class":429,"line":437},[427,2464,2465],{"class":433},"-- Every type is chosen deliberately; every NULL is chosen deliberately.\n",[427,2467,2468],{"class":429,"line":479},[427,2469,483],{"emptyLinePlaceholder":482},[427,2471,2472],{"class":429,"line":486},[427,2473,2474],{"class":433},"-- Create the enum type (must be done before the table if used in a column).\n",[427,2476,2477,2479,2482,2485,2487,2490,2493,2495,2498,2500,2503,2505,2508,2510,2513,2515,2518],{"class":429,"line":492},[427,2478,2326],{"class":440},[427,2480,2481],{"class":440}," TYPE",[427,2483,2484],{"class":2332}," order_status",[427,2486,553],{"class":440},[427,2488,2489],{"class":448}," ENUM (",[427,2491,2492],{"class":693},"'pending'",[427,2494,99],{"class":448},[427,2496,2497],{"class":693},"'paid'",[427,2499,99],{"class":448},[427,2501,2502],{"class":693},"'shipped'",[427,2504,99],{"class":448},[427,2506,2507],{"class":693},"'delivered'",[427,2509,99],{"class":448},[427,2511,2512],{"class":693},"'returned'",[427,2514,99],{"class":448},[427,2516,2517],{"class":693},"'cancelled'",[427,2519,2520],{"class":448},");\n",[427,2522,2523],{"class":429,"line":526},[427,2524,483],{"emptyLinePlaceholder":482},[427,2526,2527,2529,2532,2535],{"class":429,"line":531},[427,2528,2326],{"class":440},[427,2530,2531],{"class":440}," TABLE",[427,2533,2534],{"class":2332}," customers",[427,2536,2537],{"class":448}," (\n",[427,2539,2540],{"class":429,"line":537},[427,2541,2542],{"class":433},"  -- bigint for PK: 8 bytes, supports > 2B rows. int (4B) would overflow at 2.1B.\n",[427,2544,2545,2548,2550,2553,2556,2558,2561,2564],{"class":429,"line":543},[427,2546,2547],{"class":448},"  id          ",[427,2549,73],{"class":440},[427,2551,2552],{"class":440}," GENERATED",[427,2554,2555],{"class":440}," ALWAYS",[427,2557,553],{"class":440},[427,2559,2560],{"class":440}," IDENTITY",[427,2562,2563],{"class":440}," PRIMARY KEY",[427,2565,2566],{"class":448},",\n",[427,2568,2569],{"class":429,"line":562},[427,2570,483],{"emptyLinePlaceholder":482},[427,2572,2573],{"class":429,"line":578},[427,2574,2575],{"class":433},"  -- text for names: no business-rule length limit, variable-length storage.\n",[427,2577,2578],{"class":429,"line":1594},[427,2579,2580],{"class":433},"  -- varchar(255) would be an arbitrary limit with no perf benefit in PostgreSQL.\n",[427,2582,2583,2586,2589,2592],{"class":429,"line":1946},[427,2584,2585],{"class":440},"  name",[427,2587,2588],{"class":440},"        text",[427,2590,2591],{"class":440},"        NOT NULL",[427,2593,2566],{"class":448},[427,2595,2596],{"class":429,"line":1973},[427,2597,483],{"emptyLinePlaceholder":482},[427,2599,2601],{"class":429,"line":2600},15,[427,2602,2603],{"class":433},"  -- citext (case-insensitive text) for email: queries and unique constraint\n",[427,2605,2607],{"class":429,"line":2606},16,[427,2608,2609],{"class":433},"  -- are case-insensitive without LOWER() in every query. Requires citext extension.\n",[427,2611,2613,2616,2618,2621],{"class":429,"line":2612},17,[427,2614,2615],{"class":448},"  email       citext      ",[427,2617,2271],{"class":440},[427,2619,2620],{"class":440}," UNIQUE",[427,2622,2566],{"class":448},[427,2624,2626],{"class":429,"line":2625},18,[427,2627,483],{"emptyLinePlaceholder":482},[427,2629,2631],{"class":429,"line":2630},19,[427,2632,2633],{"class":433},"  -- numeric(10,2) for account_balance: exact decimal, 10 total digits, 2 after decimal.\n",[427,2635,2637],{"class":429,"line":2636},20,[427,2638,2639],{"class":433},"  -- Max value: $99,999,999.99. Never use float for money (0.1+0.2 ≠ 0.3 in float).\n",[427,2641,2643,2646,2648,2650,2652,2655,2657,2659,2661,2664,2666],{"class":429,"line":2642},21,[427,2644,2645],{"class":448},"  account_balance ",[427,2647,387],{"class":440},[427,2649,592],{"class":448},[427,2651,600],{"class":444},[427,2653,2654],{"class":448},",",[427,2656,466],{"class":444},[427,2658,607],{"class":448},[427,2660,2271],{"class":440},[427,2662,2663],{"class":440}," DEFAULT",[427,2665,445],{"class":444},[427,2667,2566],{"class":448},[427,2669,2671],{"class":429,"line":2670},22,[427,2672,483],{"emptyLinePlaceholder":482},[427,2674,2676],{"class":429,"line":2675},23,[427,2677,2678],{"class":433},"  -- boolean for flags: 1 byte, clear semantics. NOT NULL with default.\n",[427,2680,2682,2685,2687,2690,2692],{"class":429,"line":2681},24,[427,2683,2684],{"class":448},"  is_active   ",[427,2686,186],{"class":440},[427,2688,2689],{"class":440},"     NOT NULL",[427,2691,2663],{"class":440},[427,2693,2694],{"class":448}," true,\n",[427,2696,2698],{"class":429,"line":2697},25,[427,2699,483],{"emptyLinePlaceholder":482},[427,2701,2703],{"class":429,"line":2702},26,[427,2704,2705],{"class":433},"  -- timestamptz for timestamps: stored as UTC, displayed in session timezone.\n",[427,2707,2709],{"class":429,"line":2708},27,[427,2710,2711],{"class":433},"  -- Never use plain 'timestamp' (without tz) for app timestamps — timezone bugs.\n",[427,2713,2715,2718,2720,2723,2725],{"class":429,"line":2714},28,[427,2716,2717],{"class":448},"  created_at  ",[427,2719,223],{"class":440},[427,2721,2722],{"class":440}," NOT NULL",[427,2724,2663],{"class":440},[427,2726,2727],{"class":448}," CURRENT_TIMESTAMP,\n",[427,2729,2731,2734,2736,2738,2740],{"class":429,"line":2730},29,[427,2732,2733],{"class":448},"  updated_at  ",[427,2735,223],{"class":440},[427,2737,2722],{"class":440},[427,2739,2663],{"class":440},[427,2741,2727],{"class":448},[427,2743,2745],{"class":429,"line":2744},30,[427,2746,483],{"emptyLinePlaceholder":482},[427,2748,2750],{"class":429,"line":2749},31,[427,2751,2752],{"class":433},"  -- jsonb for flexible metadata: binary, indexable, deduplicated keys.\n",[427,2754,2756],{"class":429,"line":2755},32,[427,2757,2758],{"class":433},"  -- Use for semi-structured data that doesn't warrant its own table.\n",[427,2760,2762],{"class":429,"line":2761},33,[427,2763,2764],{"class":448},"  metadata    jsonb\n",[427,2766,2768],{"class":429,"line":2767},34,[427,2769,2520],{"class":448},[427,2771,2773],{"class":429,"line":2772},35,[427,2774,483],{"emptyLinePlaceholder":482},[427,2776,2778,2780,2782,2785],{"class":429,"line":2777},36,[427,2779,2326],{"class":440},[427,2781,2531],{"class":440},[427,2783,2784],{"class":2332}," orders",[427,2786,2537],{"class":448},[427,2788,2790,2792,2794,2796,2798,2800,2802,2804],{"class":429,"line":2789},37,[427,2791,2547],{"class":448},[427,2793,73],{"class":440},[427,2795,2552],{"class":440},[427,2797,2555],{"class":440},[427,2799,553],{"class":440},[427,2801,2560],{"class":440},[427,2803,2563],{"class":440},[427,2805,2566],{"class":448},[427,2807,2809],{"class":429,"line":2808},38,[427,2810,483],{"emptyLinePlaceholder":482},[427,2812,2814],{"class":429,"line":2813},39,[427,2815,2816],{"class":433},"  -- bigint FK: must match the PK type exactly (bigint → bigint).\n",[427,2818,2820,2823,2825,2828,2831],{"class":429,"line":2819},40,[427,2821,2822],{"class":448},"  customer_id ",[427,2824,73],{"class":440},[427,2826,2827],{"class":440},"      NOT NULL",[427,2829,2830],{"class":440}," REFERENCES",[427,2832,2833],{"class":448}," customers(id),\n",[427,2835,2837],{"class":429,"line":2836},41,[427,2838,483],{"emptyLinePlaceholder":482},[427,2840,2842],{"class":429,"line":2841},42,[427,2843,2844],{"class":433},"  -- numeric(12,2) for amount: 12 total digits, 2 after decimal.\n",[427,2846,2848],{"class":429,"line":2847},43,[427,2849,2850],{"class":433},"  -- Max: $9,999,999,999.99. Exact decimal for financial accuracy.\n",[427,2852,2854,2857,2859,2861,2864,2866,2868,2870,2872,2875,2878,2881,2883],{"class":429,"line":2853},44,[427,2855,2856],{"class":448},"  amount      ",[427,2858,387],{"class":440},[427,2860,592],{"class":448},[427,2862,2863],{"class":444},"12",[427,2865,2654],{"class":448},[427,2867,466],{"class":444},[427,2869,607],{"class":448},[427,2871,2271],{"class":440},[427,2873,2874],{"class":440}," CHECK",[427,2876,2877],{"class":448}," (amount ",[427,2879,2880],{"class":440},">=",[427,2882,445],{"class":444},[427,2884,2885],{"class":448},"),\n",[427,2887,2889],{"class":429,"line":2888},45,[427,2890,483],{"emptyLinePlaceholder":482},[427,2892,2894],{"class":429,"line":2893},46,[427,2895,2896],{"class":433},"  -- enum for status: small fixed set of labels, 4 bytes, type-safe.\n",[427,2898,2900],{"class":429,"line":2899},47,[427,2901,2902],{"class":433},"  -- Can't add values without ALTER TYPE (and can't remove values at all).\n",[427,2904,2906,2909,2912,2914,2916,2919],{"class":429,"line":2905},48,[427,2907,2908],{"class":440},"  status",[427,2910,2911],{"class":448},"      order_status ",[427,2913,2271],{"class":440},[427,2915,2663],{"class":440},[427,2917,2918],{"class":693}," 'pending'",[427,2920,2566],{"class":448},[427,2922,2924],{"class":429,"line":2923},49,[427,2925,483],{"emptyLinePlaceholder":482},[427,2927,2929],{"class":429,"line":2928},50,[427,2930,2931],{"class":433},"  -- uuid for external-facing IDs: 16 bytes, globally unique, no sequential leak.\n",[427,2933,2935],{"class":429,"line":2934},51,[427,2936,2937],{"class":433},"  -- Generated by the application or gen_random_uuid() (PG 13+).\n",[427,2939,2941,2944,2946],{"class":429,"line":2940},52,[427,2942,2943],{"class":448},"  external_ref uuid       ",[427,2945,2279],{"class":440},[427,2947,2566],{"class":448},[427,2949,2951],{"class":429,"line":2950},53,[427,2952,483],{"emptyLinePlaceholder":482},[427,2954,2956],{"class":429,"line":2955},54,[427,2957,2958],{"class":433},"  -- timestamp with tz for the order date.\n",[427,2960,2962,2965,2967,2969,2971],{"class":429,"line":2961},55,[427,2963,2964],{"class":448},"  ordered_on  ",[427,2966,223],{"class":440},[427,2968,2722],{"class":440},[427,2970,2663],{"class":440},[427,2972,2727],{"class":448},[427,2974,2976],{"class":429,"line":2975},56,[427,2977,483],{"emptyLinePlaceholder":482},[427,2979,2981],{"class":429,"line":2980},57,[427,2982,2983],{"class":433},"  -- text[] array for tags: small, fixed-size list of labels.\n",[427,2985,2987],{"class":429,"line":2986},58,[427,2988,2989],{"class":433},"  -- For large or frequently-queried collections, use a separate tags table + join table.\n",[427,2991,2993,2996,2998,3001,3003,3005],{"class":429,"line":2992},59,[427,2994,2995],{"class":448},"  tags        ",[427,2997,151],{"class":440},[427,2999,3000],{"class":448},"[]      ",[427,3002,2271],{"class":440},[427,3004,2663],{"class":440},[427,3006,3007],{"class":693}," '{}'\n",[427,3009,3011],{"class":429,"line":3010},60,[427,3012,2520],{"class":448},[427,3014,3016],{"class":429,"line":3015},61,[427,3017,483],{"emptyLinePlaceholder":482},[427,3019,3021],{"class":429,"line":3020},62,[427,3022,3023],{"class":433},"-- Index on (customer_id, ordered_on DESC) for \"recent orders per customer\" queries.\n",[427,3025,3027,3029,3032,3035,3038,3041,3044],{"class":429,"line":3026},63,[427,3028,2326],{"class":440},[427,3030,3031],{"class":440}," INDEX",[427,3033,3034],{"class":2332}," idx_orders_customer_date",[427,3036,3037],{"class":440}," ON",[427,3039,3040],{"class":448}," orders (customer_id, ordered_on ",[427,3042,3043],{"class":440},"DESC",[427,3045,2520],{"class":448},[18,3047,3049],{"id":3048},"anti-pattern-using-float-for-money","Anti-Pattern: Using float for Money",[867,3051,3053],{"id":3052},"wrong-way","❌ Wrong Way",[415,3055,3056],{"language":417},[419,3057,3059],{"className":421,"code":3058,"language":417,"meta":423,"style":423},"-- float for money: 0.1 + 0.2 ≠ 0.3 in IEEE 754 binary floating-point.\n-- Over millions of transactions, cents drift. Financial reports don't tie out.\nCREATE TABLE bad_orders (\n  id     int PRIMARY KEY,\n  amount double precision NOT NULL  -- ❌ float: 0.1 + 0.2 = 0.30000000000000004\n);\n\n-- The drift is invisible at first, then catastrophic:\nSELECT 0.10::float + 0.20::float;           -- 0.30000000000000004\nSELECT (0.10::float + 0.20::float) = 0.30::float;  -- FALSE (they're not equal!)\n",[59,3060,3061,3066,3071,3082,3093,3105,3109,3113,3118,3149],{"__ignoreMap":423},[427,3062,3063],{"class":429,"line":430},[427,3064,3065],{"class":433},"-- float for money: 0.1 + 0.2 ≠ 0.3 in IEEE 754 binary floating-point.\n",[427,3067,3068],{"class":429,"line":437},[427,3069,3070],{"class":433},"-- Over millions of transactions, cents drift. Financial reports don't tie out.\n",[427,3072,3073,3075,3077,3080],{"class":429,"line":479},[427,3074,2326],{"class":440},[427,3076,2531],{"class":440},[427,3078,3079],{"class":2332}," bad_orders",[427,3081,2537],{"class":448},[427,3083,3084,3087,3089,3091],{"class":429,"line":486},[427,3085,3086],{"class":448},"  id     ",[427,3088,69],{"class":440},[427,3090,2563],{"class":440},[427,3092,2566],{"class":448},[427,3094,3095,3098,3100,3102],{"class":429,"line":492},[427,3096,3097],{"class":448},"  amount ",[427,3099,130],{"class":440},[427,3101,2722],{"class":440},[427,3103,3104],{"class":433},"  -- ❌ float: 0.1 + 0.2 = 0.30000000000000004\n",[427,3106,3107],{"class":429,"line":526},[427,3108,2520],{"class":448},[427,3110,3111],{"class":429,"line":531},[427,3112,483],{"emptyLinePlaceholder":482},[427,3114,3115],{"class":429,"line":537},[427,3116,3117],{"class":433},"-- The drift is invisible at first, then catastrophic:\n",[427,3119,3120,3122,3124,3126,3128,3130,3132,3134,3136,3138,3141,3143,3145,3147],{"class":429,"line":543},[427,3121,441],{"class":440},[427,3123,445],{"class":444},[427,3125,201],{"class":448},[427,3127,600],{"class":444},[427,3129,454],{"class":448},[427,3131,505],{"class":440},[427,3133,459],{"class":440},[427,3135,445],{"class":444},[427,3137,201],{"class":448},[427,3139,3140],{"class":444},"20",[427,3142,454],{"class":448},[427,3144,505],{"class":440},[427,3146,2007],{"class":448},[427,3148,523],{"class":433},[427,3150,3151,3153,3155,3157,3159,3161,3163,3165,3167,3169,3171,3173,3175,3177,3179,3181,3183,3185,3188,3190,3192,3195],{"class":429,"line":562},[427,3152,441],{"class":440},[427,3154,584],{"class":448},[427,3156,595],{"class":444},[427,3158,201],{"class":448},[427,3160,600],{"class":444},[427,3162,454],{"class":448},[427,3164,505],{"class":440},[427,3166,459],{"class":440},[427,3168,445],{"class":444},[427,3170,201],{"class":448},[427,3172,3140],{"class":444},[427,3174,454],{"class":448},[427,3176,505],{"class":440},[427,3178,607],{"class":448},[427,3180,709],{"class":440},[427,3182,445],{"class":444},[427,3184,201],{"class":448},[427,3186,3187],{"class":444},"30",[427,3189,454],{"class":448},[427,3191,505],{"class":440},[427,3193,3194],{"class":448},";  ",[427,3196,3197],{"class":433},"-- FALSE (they're not equal!)\n",[867,3199,3201],{"id":3200},"right-way","✅ Right Way",[415,3203,3204],{"language":417},[419,3205,3207],{"className":421,"code":3206,"language":417,"meta":423,"style":423},"-- numeric for money: exact decimal arithmetic. 0.1 + 0.2 = 0.3, always.\n-- numeric(p, s): p = total digits, s = digits after decimal point.\nCREATE TABLE good_orders (\n  id     bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,\n  amount numeric(12,2) NOT NULL CHECK (amount >= 0)  -- ✅ exact, max $9,999,999,999.99\n);\n\nSELECT 0.10::numeric + 0.20::numeric;        -- 0.30 (exact)\nSELECT (0.10::numeric + 0.20::numeric) = 0.30::numeric;  -- TRUE\n",[59,3208,3209,3214,3219,3230,3248,3280,3284,3288,3320],{"__ignoreMap":423},[427,3210,3211],{"class":429,"line":430},[427,3212,3213],{"class":433},"-- numeric for money: exact decimal arithmetic. 0.1 + 0.2 = 0.3, always.\n",[427,3215,3216],{"class":429,"line":437},[427,3217,3218],{"class":433},"-- numeric(p, s): p = total digits, s = digits after decimal point.\n",[427,3220,3221,3223,3225,3228],{"class":429,"line":479},[427,3222,2326],{"class":440},[427,3224,2531],{"class":440},[427,3226,3227],{"class":2332}," good_orders",[427,3229,2537],{"class":448},[427,3231,3232,3234,3236,3238,3240,3242,3244,3246],{"class":429,"line":486},[427,3233,3086],{"class":448},[427,3235,73],{"class":440},[427,3237,2552],{"class":440},[427,3239,2555],{"class":440},[427,3241,553],{"class":440},[427,3243,2560],{"class":440},[427,3245,2563],{"class":440},[427,3247,2566],{"class":448},[427,3249,3250,3252,3254,3256,3258,3260,3262,3264,3266,3268,3270,3272,3274,3277],{"class":429,"line":492},[427,3251,3097],{"class":448},[427,3253,387],{"class":440},[427,3255,592],{"class":448},[427,3257,2863],{"class":444},[427,3259,2654],{"class":448},[427,3261,466],{"class":444},[427,3263,607],{"class":448},[427,3265,2271],{"class":440},[427,3267,2874],{"class":440},[427,3269,2877],{"class":448},[427,3271,2880],{"class":440},[427,3273,445],{"class":444},[427,3275,3276],{"class":448},")  ",[427,3278,3279],{"class":433},"-- ✅ exact, max $9,999,999,999.99\n",[427,3281,3282],{"class":429,"line":526},[427,3283,2520],{"class":448},[427,3285,3286],{"class":429,"line":531},[427,3287,483],{"emptyLinePlaceholder":482},[427,3289,3290,3292,3294,3296,3298,3300,3302,3304,3306,3308,3310,3312,3314,3317],{"class":429,"line":537},[427,3291,441],{"class":440},[427,3293,445],{"class":444},[427,3295,201],{"class":448},[427,3297,600],{"class":444},[427,3299,454],{"class":448},[427,3301,387],{"class":440},[427,3303,459],{"class":440},[427,3305,445],{"class":444},[427,3307,201],{"class":448},[427,3309,3140],{"class":444},[427,3311,454],{"class":448},[427,3313,387],{"class":440},[427,3315,3316],{"class":448},";        ",[427,3318,3319],{"class":433},"-- 0.30 (exact)\n",[427,3321,3322,3324,3326,3328,3330,3332,3334,3336,3338,3340,3342,3344,3346,3348,3350,3352,3354,3356,3358,3360,3362,3364],{"class":429,"line":543},[427,3323,441],{"class":440},[427,3325,584],{"class":448},[427,3327,595],{"class":444},[427,3329,201],{"class":448},[427,3331,600],{"class":444},[427,3333,454],{"class":448},[427,3335,387],{"class":440},[427,3337,459],{"class":440},[427,3339,445],{"class":444},[427,3341,201],{"class":448},[427,3343,3140],{"class":444},[427,3345,454],{"class":448},[427,3347,387],{"class":440},[427,3349,607],{"class":448},[427,3351,709],{"class":440},[427,3353,445],{"class":444},[427,3355,201],{"class":448},[427,3357,3187],{"class":444},[427,3359,454],{"class":448},[427,3361,387],{"class":440},[427,3363,3194],{"class":448},[427,3365,3366],{"class":433},"-- TRUE\n",[18,3368,3370],{"id":3369},"anti-pattern-charn-for-variable-length-strings","Anti-Pattern: char(n) for Variable-Length Strings",[867,3372,3053],{"id":3373},"wrong-way-1",[415,3375,3376],{"language":417},[419,3377,3379],{"className":421,"code":3378,"language":417,"meta":423,"style":423},"-- char(50) for names: pads with spaces to 50 chars. Wastes storage, causes comparison bugs.\n-- 'Alice' stored as 'Alice' + 45 spaces. 'Alice' = 'Alice     ' may be TRUE or FALSE\n-- depending on collation and whether trailing spaces are trimmed.\nCREATE TABLE bad_customers (\n  id   int PRIMARY KEY,\n  name char(50) NOT NULL  -- ❌ padded, comparison surprises\n);\n\nSELECT 'Alice'::char(50) = 'Alice';          -- TRUE in PG (trailing spaces trimmed in char comparison)\nSELECT 'Alice'::char(50) = 'Alice   ';       -- TRUE in PG (both sides trimmed)\n-- But in other engines or with certain collations, this may be FALSE.\n",[59,3380,3381,3386,3391,3396,3407,3418,3437,3441,3445,3471,3498],{"__ignoreMap":423},[427,3382,3383],{"class":429,"line":430},[427,3384,3385],{"class":433},"-- char(50) for names: pads with spaces to 50 chars. Wastes storage, causes comparison bugs.\n",[427,3387,3388],{"class":429,"line":437},[427,3389,3390],{"class":433},"-- 'Alice' stored as 'Alice' + 45 spaces. 'Alice' = 'Alice     ' may be TRUE or FALSE\n",[427,3392,3393],{"class":429,"line":479},[427,3394,3395],{"class":433},"-- depending on collation and whether trailing spaces are trimmed.\n",[427,3397,3398,3400,3402,3405],{"class":429,"line":486},[427,3399,2326],{"class":440},[427,3401,2531],{"class":440},[427,3403,3404],{"class":2332}," bad_customers",[427,3406,2537],{"class":448},[427,3408,3409,3412,3414,3416],{"class":429,"line":492},[427,3410,3411],{"class":448},"  id   ",[427,3413,69],{"class":440},[427,3415,2563],{"class":440},[427,3417,2566],{"class":448},[427,3419,3420,3422,3425,3427,3430,3432,3434],{"class":429,"line":526},[427,3421,2585],{"class":440},[427,3423,3424],{"class":440}," char",[427,3426,592],{"class":448},[427,3428,3429],{"class":444},"50",[427,3431,607],{"class":448},[427,3433,2271],{"class":440},[427,3435,3436],{"class":433},"  -- ❌ padded, comparison surprises\n",[427,3438,3439],{"class":429,"line":531},[427,3440,2520],{"class":448},[427,3442,3443],{"class":429,"line":537},[427,3444,483],{"emptyLinePlaceholder":482},[427,3446,3447,3449,3452,3454,3456,3458,3460,3462,3464,3466,3468],{"class":429,"line":543},[427,3448,441],{"class":440},[427,3450,3451],{"class":693}," 'Alice'",[427,3453,454],{"class":448},[427,3455,699],{"class":440},[427,3457,592],{"class":448},[427,3459,3429],{"class":444},[427,3461,607],{"class":448},[427,3463,709],{"class":440},[427,3465,3451],{"class":693},[427,3467,473],{"class":448},[427,3469,3470],{"class":433},"-- TRUE in PG (trailing spaces trimmed in char comparison)\n",[427,3472,3473,3475,3477,3479,3481,3483,3485,3487,3489,3492,3495],{"class":429,"line":562},[427,3474,441],{"class":440},[427,3476,3451],{"class":693},[427,3478,454],{"class":448},[427,3480,699],{"class":440},[427,3482,592],{"class":448},[427,3484,3429],{"class":444},[427,3486,607],{"class":448},[427,3488,709],{"class":440},[427,3490,3491],{"class":693}," 'Alice   '",[427,3493,3494],{"class":448},";       ",[427,3496,3497],{"class":433},"-- TRUE in PG (both sides trimmed)\n",[427,3499,3500],{"class":429,"line":578},[427,3501,3502],{"class":433},"-- But in other engines or with certain collations, this may be FALSE.\n",[867,3504,3201],{"id":3505},"right-way-1",[415,3507,3508],{"language":417},[419,3509,3511],{"className":421,"code":3510,"language":417,"meta":423,"style":423},"-- text for variable-length strings: no padding, no comparison surprises, same performance.\n-- Use varchar(n) only if there's a real business-rule length limit.\nCREATE TABLE good_customers (\n  id   bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,\n  name text NOT NULL  -- ✅ variable-length, no padding, exact comparison\n);\n\nSELECT 'Alice'::text = 'Alice';              -- TRUE\nSELECT 'Alice'::text = 'Alice   ';           -- FALSE (trailing spaces are significant in text)\n",[59,3512,3513,3518,3523,3534,3552,3564,3568,3572,3590],{"__ignoreMap":423},[427,3514,3515],{"class":429,"line":430},[427,3516,3517],{"class":433},"-- text for variable-length strings: no padding, no comparison surprises, same performance.\n",[427,3519,3520],{"class":429,"line":437},[427,3521,3522],{"class":433},"-- Use varchar(n) only if there's a real business-rule length limit.\n",[427,3524,3525,3527,3529,3532],{"class":429,"line":479},[427,3526,2326],{"class":440},[427,3528,2531],{"class":440},[427,3530,3531],{"class":2332}," good_customers",[427,3533,2537],{"class":448},[427,3535,3536,3538,3540,3542,3544,3546,3548,3550],{"class":429,"line":486},[427,3537,3411],{"class":448},[427,3539,73],{"class":440},[427,3541,2552],{"class":440},[427,3543,2555],{"class":440},[427,3545,553],{"class":440},[427,3547,2560],{"class":440},[427,3549,2563],{"class":440},[427,3551,2566],{"class":448},[427,3553,3554,3556,3559,3561],{"class":429,"line":492},[427,3555,2585],{"class":440},[427,3557,3558],{"class":440}," text",[427,3560,2722],{"class":440},[427,3562,3563],{"class":433},"  -- ✅ variable-length, no padding, exact comparison\n",[427,3565,3566],{"class":429,"line":526},[427,3567,2520],{"class":448},[427,3569,3570],{"class":429,"line":531},[427,3571,483],{"emptyLinePlaceholder":482},[427,3573,3574,3576,3578,3580,3582,3584,3586,3588],{"class":429,"line":537},[427,3575,441],{"class":440},[427,3577,3451],{"class":693},[427,3579,454],{"class":448},[427,3581,151],{"class":440},[427,3583,730],{"class":440},[427,3585,3451],{"class":693},[427,3587,520],{"class":448},[427,3589,3366],{"class":433},[427,3591,3592,3594,3596,3598,3600,3602,3604,3606],{"class":429,"line":543},[427,3593,441],{"class":440},[427,3595,3451],{"class":693},[427,3597,454],{"class":448},[427,3599,151],{"class":440},[427,3601,730],{"class":440},[427,3603,3491],{"class":693},[427,3605,2007],{"class":448},[427,3607,3608],{"class":433},"-- FALSE (trailing spaces are significant in text)\n",[18,3610,3612],{"id":3611},"type-coercion-and-casting-rules","Type Coercion and Casting Rules",[415,3614,3615],{"language":417},[419,3616,3618],{"className":421,"code":3617,"language":417,"meta":423,"style":423},"-- Explicit casting with :: (PostgreSQL-specific, concise).\nSELECT '2024-01-15'::date;\nSELECT 42::text;\nSELECT '123.45'::numeric(10,2);\n\n-- Standard SQL CAST (portable to all engines).\nSELECT CAST('2024-01-15' AS date);\nSELECT CAST(42 AS text);\n\n-- Implicit coercion: PostgreSQL coerces in some contexts, but not all.\nSELECT 1 + 1.0;           -- int + numeric → numeric (implicit upcast)\nSELECT '5'::text + 3;     -- ERROR: text + int is not valid (no implicit text→int)\n\n-- generate_series for testing: produce a range of values for testing queries.\nSELECT * FROM generate_series(1, 10);                    -- 1..10\nSELECT * FROM generate_series('2024-01-01'::date, '2024-01-07'::date, '1 day');  -- 7 dates\n",[59,3619,3620,3625,3638,3651,3672,3676,3681,3700,3717,3721,3726,3746,3768,3772,3777,3801],{"__ignoreMap":423},[427,3621,3622],{"class":429,"line":430},[427,3623,3624],{"class":433},"-- Explicit casting with :: (PostgreSQL-specific, concise).\n",[427,3626,3627,3629,3632,3634,3636],{"class":429,"line":437},[427,3628,441],{"class":440},[427,3630,3631],{"class":693}," '2024-01-15'",[427,3633,454],{"class":448},[427,3635,213],{"class":440},[427,3637,1402],{"class":448},[427,3639,3640,3642,3645,3647,3649],{"class":429,"line":479},[427,3641,441],{"class":440},[427,3643,3644],{"class":444}," 42",[427,3646,454],{"class":448},[427,3648,151],{"class":440},[427,3650,1402],{"class":448},[427,3652,3653,3655,3658,3660,3662,3664,3666,3668,3670],{"class":429,"line":486},[427,3654,441],{"class":440},[427,3656,3657],{"class":693}," '123.45'",[427,3659,454],{"class":448},[427,3661,387],{"class":440},[427,3663,592],{"class":448},[427,3665,600],{"class":444},[427,3667,2654],{"class":448},[427,3669,466],{"class":444},[427,3671,2520],{"class":448},[427,3673,3674],{"class":429,"line":492},[427,3675,483],{"emptyLinePlaceholder":482},[427,3677,3678],{"class":429,"line":526},[427,3679,3680],{"class":433},"-- Standard SQL CAST (portable to all engines).\n",[427,3682,3683,3685,3688,3690,3693,3695,3698],{"class":429,"line":531},[427,3684,441],{"class":440},[427,3686,3687],{"class":444}," CAST",[427,3689,592],{"class":448},[427,3691,3692],{"class":693},"'2024-01-15'",[427,3694,553],{"class":440},[427,3696,3697],{"class":440}," date",[427,3699,2520],{"class":448},[427,3701,3702,3704,3706,3708,3711,3713,3715],{"class":429,"line":537},[427,3703,441],{"class":440},[427,3705,3687],{"class":444},[427,3707,592],{"class":448},[427,3709,3710],{"class":444},"42",[427,3712,553],{"class":440},[427,3714,3558],{"class":440},[427,3716,2520],{"class":448},[427,3718,3719],{"class":429,"line":543},[427,3720,483],{"emptyLinePlaceholder":482},[427,3722,3723],{"class":429,"line":562},[427,3724,3725],{"class":433},"-- Implicit coercion: PostgreSQL coerces in some contexts, but not all.\n",[427,3727,3728,3730,3733,3735,3737,3739,3741,3743],{"class":429,"line":578},[427,3729,441],{"class":440},[427,3731,3732],{"class":444}," 1",[427,3734,459],{"class":440},[427,3736,3732],{"class":444},[427,3738,201],{"class":448},[427,3740,595],{"class":444},[427,3742,2007],{"class":448},[427,3744,3745],{"class":433},"-- int + numeric → numeric (implicit upcast)\n",[427,3747,3748,3750,3753,3755,3757,3759,3762,3765],{"class":429,"line":1594},[427,3749,441],{"class":440},[427,3751,3752],{"class":693}," '5'",[427,3754,454],{"class":448},[427,3756,151],{"class":440},[427,3758,459],{"class":440},[427,3760,3761],{"class":444}," 3",[427,3763,3764],{"class":448},";     ",[427,3766,3767],{"class":433},"-- ERROR: text + int is not valid (no implicit text→int)\n",[427,3769,3770],{"class":429,"line":1946},[427,3771,483],{"emptyLinePlaceholder":482},[427,3773,3774],{"class":429,"line":1973},[427,3775,3776],{"class":433},"-- generate_series for testing: produce a range of values for testing queries.\n",[427,3778,3779,3781,3783,3785,3787,3789,3791,3793,3795,3798],{"class":429,"line":2600},[427,3780,441],{"class":440},[427,3782,1178],{"class":440},[427,3784,1181],{"class":440},[427,3786,618],{"class":444},[427,3788,592],{"class":448},[427,3790,451],{"class":444},[427,3792,99],{"class":448},[427,3794,600],{"class":444},[427,3796,3797],{"class":448},");                    ",[427,3799,3800],{"class":433},"-- 1..10\n",[427,3802,3803,3805,3807,3809,3811,3813,3816,3818,3820,3822,3825,3827,3829,3831,3834,3837],{"class":429,"line":2606},[427,3804,441],{"class":440},[427,3806,1178],{"class":440},[427,3808,1181],{"class":440},[427,3810,618],{"class":444},[427,3812,592],{"class":448},[427,3814,3815],{"class":693},"'2024-01-01'",[427,3817,454],{"class":448},[427,3819,213],{"class":440},[427,3821,99],{"class":448},[427,3823,3824],{"class":693},"'2024-01-07'",[427,3826,454],{"class":448},[427,3828,213],{"class":440},[427,3830,99],{"class":448},[427,3832,3833],{"class":693},"'1 day'",[427,3835,3836],{"class":448},");  ",[427,3838,3839],{"class":433},"-- 7 dates\n",[18,3841,3843],{"id":3842},"tips-tricks","💡 Tips & Tricks",[2262,3845,3846,3859,3871,3884,3902,3921,3942,3954],{},[2265,3847,3848,3851,3852,3854,3855,3858],{},[53,3849,3850],{},"Idiom"," — mark every column ",[59,3853,2271],{}," unless you have a ",[1061,3856,3857],{},"specific"," reason to allow NULL. Most \"optional\" columns have a natural default (0, '', false) that's safer than NULL. NULLs infect every comparison, aggregate, and join; the fewer the better.",[2265,3860,3861,805,3863,3866,3867,3870],{},[53,3862,3850],{},[59,3864,3865],{},"NULLIF(x, 0)"," everywhere you divide by a column that could be 0. It converts the 0 to NULL, making the division yield NULL instead of throwing \"division by zero.\" Wrap in ",[59,3868,3869],{},"COALESCE(..., 0)"," if you need a numeric result.",[2265,3872,3873,3875,3876,238,3878,3880,3881,201],{},[53,3874,3850],{}," — prefer ",[59,3877,1233],{},[59,3879,709],{}," when comparing nullable columns where NULL should match NULL. But be aware it's slower (no index). For indexed equality with NULLs, split the predicate: ",[59,3882,3883],{},"WHERE (a = b OR (a IS NULL AND b IS NULL))",[2265,3885,3886,805,3888,3891,3892,99,3894,858,3896,3898,3899,3901],{},[53,3887,3850],{},[59,3889,3890],{},"NUMERIC(p, 2)"," for money. Never use ",[59,3893,505],{},[59,3895,127],{},[59,3897,130],{}," for financial values. The ",[59,3900,378],{}," type is locale-dependent — avoid it too.",[2265,3903,3904,805,3906,3909,3910,3913,3914,3916,3917,3920],{},[53,3905,3850],{},[59,3907,3908],{},"CONCAT()"," or ",[59,3911,3912],{},"CONCAT_WS()"," for string concatenation with NULLs. ",[59,3915,2066],{}," propagates NULL (one NULL operand → NULL result); ",[59,3918,3919],{},"CONCAT"," ignores NULLs.",[2265,3922,3923,3926,3927,99,3929,1076,3932,3935,3936,3938,3939,3941],{},[53,3924,3925],{},"Debug"," — when a query returns fewer rows than expected, audit every ",[59,3928,1244],{},[59,3930,3931],{},"!=",[59,3933,3934],{},"NOT IN"," — these silently drop NULLs. Replace ",[59,3937,1244],{}," with ",[59,3940,1240],{}," if NULL is a meaningful \"other\" value.",[2265,3943,3944,805,3946,3949,3950,3953],{},[53,3945,3850],{},[59,3947,3948],{},"generate_series"," for testing: generate synthetic rows to test queries, fill date gaps for time-series, or create fixture data. ",[59,3951,3952],{},"SELECT * FROM generate_series(1, 1000000)"," gives you a million-row test table instantly.",[2265,3955,3956,404,3959,3961,3962,201],{},[53,3957,3958],{},"Portability",[59,3960,2279],{}," constraint with NULLs is engine-dependent (standard: multiple NULLs allowed; SQL Server: one NULL). If you need \"at most one non-NULL value per group\" portably, use a partial unique index: ",[59,3963,3964],{},"CREATE UNIQUE INDEX ON t(col) WHERE col IS NOT NULL",[18,3966,3968],{"id":3967},"️-edge-cases-gotchas","⚠️ Edge Cases & Gotchas",[2262,3970,3971,3985,4009,4031,4047,4061,4089,4107,4118,4138,4149,4166,4176,4186,4194,4203,4227],{},[2265,3972,3973,3978,3979,3981,3982,201],{},[53,3974,3975,3977],{},[59,3976,2385],{}," is UNKNOWN, not TRUE"," — the #1 NULL gotcha. Never compare to NULL with ",[59,3980,709],{},"; use ",[59,3983,3984],{},"IS NULL",[2265,3986,3987,404,3993,2426,3996,2426,3999,4001,4002,167,4005,4008],{},[53,3988,3989,3992],{},[59,3990,3991],{},"NOT (col = 5)"," excludes NULLs",[59,3994,3995],{},"NOT (NULL = 5)",[59,3997,3998],{},"NOT UNKNOWN",[59,4000,861],{}," ≠ TRUE. So ",[59,4003,4004],{},"WHERE col \u003C> 5",[59,4006,4007],{},"WHERE NOT (col = 5)"," both drop NULL rows.",[2265,4010,4011,404,4020,4023,4024,4026,4027,4030],{},[53,4012,4013,4016,4017,4019],{},[59,4014,4015],{},"COUNT(col)"," ignores NULLs, ",[59,4018,1802],{}," doesn't",[59,4021,4022],{},"COUNT(amount)"," may be less than ",[59,4025,1802],{}," if ",[59,4028,4029],{},"amount"," has NULLs. Pick deliberately.",[2265,4032,4033,404,4039,4042,4043,4046],{},[53,4034,4035,4038],{},[59,4036,4037],{},"AVG"," ignores NULLs, doesn't average them as 0",[59,4040,4041],{},"AVG([10, 20, NULL])"," = 15. If NULL means 0, ",[59,4044,4045],{},"AVG(COALESCE(amount, 0))"," = 10.",[2265,4048,4049,404,4054,4056,4057,4060],{},[53,4050,4051,4053],{},[59,4052,1572],{}," of all-NULL is NULL",[59,4055,1572],{}," over an empty or all-NULL set is NULL, not 0. ",[59,4058,4059],{},"COALESCE(SUM(x), 0)"," for 0.",[2265,4062,4063,404,4066,2426,4069,4071,4072,4075,4076,4079,4080,400,4082,4084,4085,4088],{},[53,4064,4065],{},"String concatenation with NULL",[59,4067,4068],{},"'hello' || NULL",[59,4070,200],{}," (the whole string becomes NULL). Use ",[59,4073,4074],{},"CONCAT(a, b)"," (ignores NULLs) or ",[59,4077,4078],{},"COALESCE(a, '') || COALESCE(b, '')",". MySQL's ",[59,4081,2066],{},[59,4083,939],{}," by default (not concat) unless ",[59,4086,4087],{},"PIPES_AS_CONCAT"," is set.",[2265,4090,4091,404,4094,4097,4098,2426,4100,4102,4103,4106],{},[53,4092,4093],{},"NULL in boolean logic",[59,4095,4096],{},"WHERE NOT col"," excludes NULLs because ",[59,4099,2271],{},[59,4101,861],{},", which isn't TRUE. Use ",[59,4104,4105],{},"WHERE col IS NOT TRUE"," if you want FALSE + NULL.",[2265,4108,4109,4114,4115,4117],{},[53,4110,4111,4113],{},[59,4112,2279],{}," allows multiple NULLs"," (in most engines) — a ",[59,4116,2279],{}," constraint on a nullable column permits many rows with NULL. Use a partial index if you need \"at most one NULL.\"",[2265,4119,4120,4129,4130,4133,4134,4137],{},[53,4121,4122,66,4125,4128],{},[59,4123,4124],{},"GREATEST",[59,4126,4127],{},"LEAST"," return NULL if any arg is NULL"," (PostgreSQL) — ",[59,4131,4132],{},"GREATEST(5, NULL)"," = NULL. Use ",[59,4135,4136],{},"GREATEST(COALESCE(a, -1), COALESCE(b, -1))"," or restructure.",[2265,4139,4140,404,4143,4146,4147,201],{},[53,4141,4142],{},"Float precision issues",[59,4144,4145],{},"0.1::float + 0.2::float ≠ 0.3::float",". Never use float for money or exact decimal arithmetic. Use ",[59,4148,387],{},[2265,4150,4151,4159,4160,4162,4163,4165],{},[53,4152,4153,4155,4156,4158],{},[59,4154,170],{}," vs ",[59,4157,151],{}," equivalence in PG"," — identical in performance and storage. ",[59,4161,155],{}," is just a length constraint. Use ",[59,4164,151],{}," by default.",[2265,4167,4168,404,4173,4175],{},[53,4169,4170,4172],{},[59,4171,159],{}," padding bugs",[59,4174,159],{}," pads with spaces, causing comparison surprises across engines and collations. Avoid except for truly fixed-width codes.",[2265,4177,4178,4181,4182,4185],{},[53,4179,4180],{},"Enum immutability"," — you can ",[59,4183,4184],{},"ALTER TYPE ... ADD VALUE"," but can't remove values. If the value set might change, use a lookup table + FK instead of an enum.",[2265,4187,4188,404,4191,4193],{},[53,4189,4190],{},"Array vs separate table",[59,4192,322],{}," is fine for small, rarely-queried lists. For large collections or ones you need to filter\u002Fjoin on, use a separate table — arrays can't be indexed for individual element queries efficiently (except GIN indexes).",[2265,4195,4196,404,4199,4202],{},[53,4197,4198],{},"Numeric scale overflow",[59,4200,4201],{},"numeric(10, 2)"," can store up to $99,999,999.99. Inserting $100,000,000.00 overflows. Choose precision with headroom for growth.",[2265,4204,4205,404,4213,4216,4217,4220,4221,4223,4224,201],{},[53,4206,4207,4209,4210],{},[59,4208,200],{}," in ",[59,4211,4212],{},"CASE",[59,4214,4215],{},"CASE WHEN NULL THEN ..."," never matches (NULL is not TRUE). ",[59,4218,4219],{},"CASE col WHEN NULL THEN ..."," (simple form) also never matches (it compares ",[59,4222,1073],{},"). Use ",[59,4225,4226],{},"CASE WHEN col IS NULL THEN ...",[2265,4228,4229,4235,4236,4239,4240,4243,4244,66,4246,4248],{},[53,4230,4231,4234],{},[59,4232,4233],{},"ORDER BY"," with NULLs is engine-specific"," — PostgreSQL defaults to ",[59,4237,4238],{},"NULLS LAST"," for ASC, ",[59,4241,4242],{},"NULLS FIRST"," for DESC. Always specify ",[59,4245,4242],{},[59,4247,4238],{}," if the order matters.",[18,4250,4252],{"id":4251},"spot-the-bug","🧠 Spot the Bug",[14,4254,4255,4256,4259],{},"This query builds a customer display name by concatenating title, first name, and last name. For some customers, ",[59,4257,4258],{},"display_name"," is NULL even though all three columns have values. Why?",[415,4261,4262],{"language":417},[419,4263,4265],{"className":421,"code":4264,"language":417,"meta":423,"style":423},"SELECT\n  title || ' ' || first_name || ' ' || last_name AS display_name\nFROM customers;\n",[59,4266,4267,4272,4300],{"__ignoreMap":423},[427,4268,4269],{"class":429,"line":430},[427,4270,4271],{"class":440},"SELECT\n",[427,4273,4274,4277,4279,4282,4284,4287,4289,4291,4293,4296,4298],{"class":429,"line":437},[427,4275,4276],{"class":448},"  title ",[427,4278,2066],{"class":440},[427,4280,4281],{"class":693}," ' '",[427,4283,2002],{"class":440},[427,4285,4286],{"class":448}," first_name ",[427,4288,2066],{"class":440},[427,4290,4281],{"class":693},[427,4292,2002],{"class":440},[427,4294,4295],{"class":448}," last_name ",[427,4297,610],{"class":440},[427,4299,1540],{"class":448},[427,4301,4302,4304],{"class":429,"line":479},[427,4303,581],{"class":440},[427,4305,1511],{"class":448},[4307,4308,4309,4313,4333,4341,4474,4480],"details",{},[4310,4311,4312],"summary",{},"Answer",[14,4314,4315,4316,4318,4319,4321,4322,4325,4326,167,4329,4332],{},"The ",[59,4317,2066],{}," operator propagates NULL: if ",[53,4320,1465],{}," operand is NULL, the entire concatenation result is NULL. So if ",[59,4323,4324],{},"title"," is NULL (e.g., a customer with no title like \"Mr\"\u002F\"Ms\"), the whole expression becomes NULL — even though ",[59,4327,4328],{},"first_name",[59,4330,4331],{},"last_name"," have values. The display name vanishes silently.",[14,4334,4335,4336,4075,4338,4340],{},"The fix — use ",[59,4337,3908],{},[59,4339,1494],{}," each operand:",[415,4342,4343],{"language":417},[419,4344,4346],{"className":421,"code":4345,"language":417,"meta":423,"style":423},"-- Option 1: CONCAT ignores NULLs (PostgreSQL, MySQL).\nSELECT CONCAT(title, ' ', first_name, ' ', last_name) AS display_name\nFROM customers;\n-- 'John Smith' (title NULL → skipped, but extra spaces may appear: ' John Smith ')\n\n-- Option 2: CONCAT_WS (with separator) — cleaner, no double spaces from NULLs.\nSELECT CONCAT_WS(' ', title, first_name, last_name) AS display_name\nFROM customers;\n-- 'John Smith' (NULL title → separator not emitted → no leading space)\n\n-- Option 3: COALESCE each operand to empty string (works with ||).\nSELECT COALESCE(title, '') || ' ' || first_name || ' ' || last_name AS display_name\nFROM customers;\n-- ' John Smith' (leading space from the title being '' — may need trimming)\n",[59,4347,4348,4353,4376,4382,4387,4391,4396,4411,4417,4422,4426,4431,4463,4469],{"__ignoreMap":423},[427,4349,4350],{"class":429,"line":430},[427,4351,4352],{"class":433},"-- Option 1: CONCAT ignores NULLs (PostgreSQL, MySQL).\n",[427,4354,4355,4357,4359,4362,4364,4367,4369,4372,4374],{"class":429,"line":437},[427,4356,441],{"class":440},[427,4358,2088],{"class":444},[427,4360,4361],{"class":448},"(title, ",[427,4363,2130],{"class":693},[427,4365,4366],{"class":448},", first_name, ",[427,4368,2130],{"class":693},[427,4370,4371],{"class":448},", last_name) ",[427,4373,610],{"class":440},[427,4375,1540],{"class":448},[427,4377,4378,4380],{"class":429,"line":479},[427,4379,581],{"class":440},[427,4381,1511],{"class":448},[427,4383,4384],{"class":429,"line":486},[427,4385,4386],{"class":433},"-- 'John Smith' (title NULL → skipped, but extra spaces may appear: ' John Smith ')\n",[427,4388,4389],{"class":429,"line":492},[427,4390,483],{"emptyLinePlaceholder":482},[427,4392,4393],{"class":429,"line":526},[427,4394,4395],{"class":433},"-- Option 2: CONCAT_WS (with separator) — cleaner, no double spaces from NULLs.\n",[427,4397,4398,4400,4402,4404,4407,4409],{"class":429,"line":531},[427,4399,441],{"class":440},[427,4401,2127],{"class":448},[427,4403,2130],{"class":693},[427,4405,4406],{"class":448},", title, first_name, last_name) ",[427,4408,610],{"class":440},[427,4410,1540],{"class":448},[427,4412,4413,4415],{"class":429,"line":537},[427,4414,581],{"class":440},[427,4416,1511],{"class":448},[427,4418,4419],{"class":429,"line":543},[427,4420,4421],{"class":433},"-- 'John Smith' (NULL title → separator not emitted → no leading space)\n",[427,4423,4424],{"class":429,"line":562},[427,4425,483],{"emptyLinePlaceholder":482},[427,4427,4428],{"class":429,"line":578},[427,4429,4430],{"class":433},"-- Option 3: COALESCE each operand to empty string (works with ||).\n",[427,4432,4433,4435,4437,4439,4441,4443,4445,4447,4449,4451,4453,4455,4457,4459,4461],{"class":429,"line":1594},[427,4434,441],{"class":440},[427,4436,1527],{"class":444},[427,4438,4361],{"class":448},[427,4440,845],{"class":693},[427,4442,607],{"class":448},[427,4444,2066],{"class":440},[427,4446,4281],{"class":693},[427,4448,2002],{"class":440},[427,4450,4286],{"class":448},[427,4452,2066],{"class":440},[427,4454,4281],{"class":693},[427,4456,2002],{"class":440},[427,4458,4295],{"class":448},[427,4460,610],{"class":440},[427,4462,1540],{"class":448},[427,4464,4465,4467],{"class":429,"line":1946},[427,4466,581],{"class":440},[427,4468,1511],{"class":448},[427,4470,4471],{"class":429,"line":1973},[427,4472,4473],{"class":433},"-- ' John Smith' (leading space from the title being '' — may need trimming)\n",[14,4475,4476,4479],{},[59,4477,4478],{},"CONCAT_WS"," is the cleanest — it handles NULLs gracefully and doesn't emit separators for NULL values, avoiding the double-space problem.",[14,4481,4482,1028,4485,4487,4488,3909,4490,4492],{},[53,4483,4484],{},"The lesson",[59,4486,2066],{}," propagates NULL — one NULL operand makes the entire result NULL. Use ",[59,4489,3919],{},[59,4491,4478],{}," for NULL-safe string concatenation.",[18,4494,4495],{"id":4310},"Summary",[14,4497,4498,4499,4501,4502,4155,4504,4155,4506,99,4508,4155,4510,99,4512,4155,4514,4516,4517,1456,4519,1456,4521,1456,4523,4526],{},"You now understand SQL's type system (",[59,4500,387],{}," vs float, ",[59,4503,151],{},[59,4505,170],{},[59,4507,699],{},[59,4509,223],{},[59,4511,220],{},[59,4513,296],{},[59,4515,292],{},"), the three-valued logic of NULL, the ",[59,4518,3984],{},[59,4520,1233],{},[59,4522,1494],{},[59,4524,4525],{},"NULLIF"," toolkit, and the dozen ways NULL silently breaks queries. Next: defining tables and schema with DDL.",[4528,4529,4530],"style",{},"html pre.shiki code .sdCPZ, html code.shiki .sdCPZ{--shiki-default:#6A737D;--shiki-github-dark:#6A737D}html pre.shiki code .svdQ7, html code.shiki .svdQ7{--shiki-default:#D73A49;--shiki-github-dark:#F97583}html pre.shiki code .snvgF, html code.shiki .snvgF{--shiki-default:#005CC5;--shiki-github-dark:#79B8FF}html pre.shiki code .ssxIu, html code.shiki .ssxIu{--shiki-default:#24292E;--shiki-github-dark:#E1E4E8}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 .github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}html.github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}html pre.shiki code .sJ6F3, html code.shiki .sJ6F3{--shiki-default:#032F62;--shiki-github-dark:#9ECBFF}html pre.shiki code .sIsaT, html code.shiki .sIsaT{--shiki-default:#6F42C1;--shiki-github-dark:#B392F0}",{"title":423,"searchDepth":437,"depth":437,"links":4532},[4533,4534,4535,4536,4540,4541,4545,4546,4547,4548,4549,4550,4551,4552,4553,4557,4561,4562,4563,4564,4565],{"id":20,"depth":437,"text":21},{"id":394,"depth":437,"text":395},{"id":639,"depth":437,"text":640},{"id":830,"depth":437,"text":831,"children":4537},[4538,4539],{"id":869,"depth":479,"text":870},{"id":1046,"depth":479,"text":1047},{"id":1227,"depth":437,"text":1228},{"id":1405,"depth":437,"text":1406,"children":4542},[4543,4544],{"id":1469,"depth":479,"text":1470},{"id":1613,"depth":479,"text":1614},{"id":1791,"depth":437,"text":1792},{"id":1979,"depth":437,"text":1980},{"id":2145,"depth":437,"text":2146},{"id":2259,"depth":437,"text":2260},{"id":2355,"depth":437,"text":2356},{"id":2392,"depth":437,"text":2393},{"id":2416,"depth":437,"text":2417},{"id":2447,"depth":437,"text":2448},{"id":3048,"depth":437,"text":3049,"children":4554},[4555,4556],{"id":3052,"depth":479,"text":3053},{"id":3200,"depth":479,"text":3201},{"id":3369,"depth":437,"text":3370,"children":4558},[4559,4560],{"id":3373,"depth":479,"text":3053},{"id":3505,"depth":479,"text":3201},{"id":3611,"depth":437,"text":3612},{"id":3842,"depth":437,"text":3843},{"id":3967,"depth":437,"text":3968},{"id":4251,"depth":437,"text":4252},{"id":4310,"depth":437,"text":4495},"md",{},"\u002Fsql\u002F10-data-types-and-null",{"title":5,"description":16},"sql\u002F10-data-types-and-null","oLppoZy0AUICgMtlaF_etX_vAB9uFGdWkVrfvXT1NZE",1789924654410]