[{"data":1,"prerenderedAt":3750},["ShallowReactive",2],{"page-\u002Fsql\u002F01-introduction-and-setup":3},{"id":4,"title":5,"body":6,"description":108,"extension":3744,"meta":3745,"navigation":132,"path":3746,"seo":3747,"stem":3748,"__hash__":3749},"content\u002Fsql\u002F01-introduction-and-setup.md","01 — Introduction & Setup",{"type":7,"value":8,"toc":3718},"minimark",[9,13,18,27,70,74,93,98,1149,1168,1172,1175,1440,1446,1450,1454,1507,1511,1570,1574,1673,1676,1679,1759,1766,1771,2014,2100,2104,2271,2275,2343,2346,2637,2641,2749,2754,2758,2761,2782,2786,2796,2806,2833,3001,3005,3097,3105,3126,3216,3220,3302,3356,3360,3500,3504,3510,3566,3573,3697,3700,3714],[10,11,5],"h1",{"id":12},"_01-introduction-setup",[14,15,17],"h2",{"id":16},"what-is-sql","What Is SQL?",[19,20,21,22,26],"p",{},"SQL (Structured Query Language) is the standard language for interacting with ",[23,24,25],"strong",{},"relational databases"," — systems that store data in tables of rows and columns with explicit relationships between them.",[28,29,30,46,52,58,64],"ul",{},[31,32,33,36,37,41,42,45],"li",{},[23,34,35],{},"Declarative"," — you describe ",[38,39,40],"em",{},"what"," data you want, not ",[38,43,44],{},"how"," to fetch it. The query planner chooses the algorithm.",[31,47,48,51],{},[23,49,50],{},"Set-based"," — operations work on entire sets (tables) of rows at once, not one row at a time.",[31,53,54,57],{},[23,55,56],{},"Strongly typed"," — every column has a declared type; the database enforces and coerces.",[31,59,60,63],{},[23,61,62],{},"Transactional"," — grouped operations are atomic, consistent, isolated, and durable (ACID).",[31,65,66,69],{},[23,67,68],{},"Standardized"," — ANSI\u002FISO SQL is a real standard, but every engine extends it. Portability is aspirational, not guaranteed.",[14,71,73],{"id":72},"the-relational-model","The Relational Model",[19,75,76,77,80,81,84,85,88,89,92],{},"Data lives in ",[23,78,79],{},"tables"," (relations). Each table has ",[23,82,83],{},"columns"," (attributes) — each with a name and a type, ",[23,86,87],{},"rows"," (tuples) — each row is one record, and ",[23,90,91],{},"keys"," — a primary key uniquely identifies a row; foreign keys reference rows in other tables.",[94,95,97],"h3",{"id":96},"production-schema-example","Production Schema Example",[99,100,103],"code-wrapper",{"language":101,"filename":102},"sql","schema.sql",[104,105,109],"pre",{"className":106,"code":107,"language":101,"meta":108,"style":108},"language-sql shiki shiki-themes github-light github-dark","-- A multi-table production schema for an e-commerce system.\n-- Each table has explicit constraints, proper types, and indexes.\n\nCREATE TABLE customers (\n  -- UUID primary key avoids sequential enumeration attacks and\n  -- allows distributed ID generation without central coordination.\n  id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n  email       CITEXT NOT NULL UNIQUE,           -- CITEXT = case-insensitive email lookup\n  full_name   TEXT NOT NULL CHECK (full_name \u003C> ''),\n  phone       TEXT,                              -- nullable: not every customer has a phone\n  created_at  TIMESTAMPTZ NOT NULL DEFAULT now(),\n  -- Exclude soft-deleted customers from the unique email constraint\n  deleted_at  TIMESTAMPTZ\n);\n\n-- Partial unique index: email must be unique among non-deleted rows only.\n-- This allows re-registration after soft-delete without violating uniqueness.\nCREATE UNIQUE INDEX customers_email_active_uniq\n  ON customers (email)\n  WHERE deleted_at IS NULL;\n\nCREATE TABLE orders (\n  id           BIGSERIAL PRIMARY KEY,            -- sequential BIGINT (8 bytes); gap-free NOT guaranteed\n  customer_id  UUID NOT NULL REFERENCES customers(id) ON DELETE RESTRICT,\n  status       TEXT NOT NULL DEFAULT 'pending'\n               CHECK (status IN ('pending','paid','shipped','cancelled','refunded')),\n  total        NUMERIC(12, 2) NOT NULL DEFAULT 0\n               CHECK (total >= 0),               -- NUMERIC, not REAL — money needs exact arithmetic\n  currency     CHAR(3) NOT NULL DEFAULT 'USD',   -- ISO 4217\n  placed_at    TIMESTAMPTZ NOT NULL DEFAULT now(),\n  updated_at   TIMESTAMPTZ NOT NULL DEFAULT now()\n);\n\n-- Composite index for \"show me customer X's recent orders\" (hot path).\n-- Column order matters: equality on customer_id, then sort on placed_at DESC.\nCREATE INDEX idx_orders_customer_placed\n  ON orders (customer_id, placed_at DESC);\n\n-- Partial index: only pending orders need scanning — skips 99% of rows.\nCREATE INDEX idx_orders_pending\n  ON orders (placed_at)\n  WHERE status = 'pending';\n\nCREATE TABLE products (\n  id          BIGSERIAL PRIMARY KEY,\n  sku         TEXT NOT NULL UNIQUE,              -- business identifier, stable across renames\n  name        TEXT NOT NULL,\n  price       NUMERIC(10, 2) NOT NULL CHECK (price > 0),\n  stock       INTEGER NOT NULL DEFAULT 0 CHECK (stock >= 0),\n  created_at  TIMESTAMPTZ NOT NULL DEFAULT now()\n);\n\nCREATE TABLE order_items (\n  id           BIGSERIAL PRIMARY KEY,\n  order_id     BIGINT NOT NULL REFERENCES orders(id) ON DELETE CASCADE,\n  product_id   BIGINT NOT NULL REFERENCES products(id) ON DELETE RESTRICT,\n  quantity     INTEGER NOT NULL CHECK (quantity > 0),\n  unit_price   NUMERIC(10, 2) NOT NULL CHECK (unit_price >= 0),\n  -- Snapshot of price at order time — price in products may change later.\n  -- Without this, historical order totals would silently change.\n  CHECK (quantity * unit_price \u003C= 1000000)       -- sanity bound, prevents overflow in totals\n);\n\n-- Covering index: the join path order_items→products often only needs product_id + name.\n-- A covering index lets the planner satisfy the query from the index alone (index-only scan).\nCREATE INDEX idx_order_items_order_product\n  ON order_items (order_id, product_id)\n  INCLUDE (quantity, unit_price);\n\n-- Auto-maintain updated_at on every row update.\nCREATE OR REPLACE FUNCTION touch_updated_at()\nRETURNS TRIGGER AS $$\nBEGIN\n  NEW.updated_at = now();                         -- set before the row is written\n  RETURN NEW;\nEND;\n$$ LANGUAGE plpgsql;\n\nCREATE TRIGGER orders_touch_updated_at\n  BEFORE UPDATE ON orders\n  FOR EACH ROW EXECUTE FUNCTION touch_updated_at();\n","",[110,111,112,121,127,134,152,158,164,179,197,225,239,258,264,273,279,284,290,296,307,316,334,339,351,369,389,405,449,481,501,530,546,563,568,573,579,585,596,609,614,620,630,638,654,659,671,684,702,715,748,774,789,794,799,811,822,843,862,883,914,920,926,952,957,962,968,974,984,992,1001,1006,1012,1026,1041,1047,1069,1078,1086,1098,1103,1114,1129],"code",{"__ignoreMap":108},[113,114,117],"span",{"class":115,"line":116},"line",1,[113,118,120],{"class":119},"sdCPZ","-- A multi-table production schema for an e-commerce system.\n",[113,122,124],{"class":115,"line":123},2,[113,125,126],{"class":119},"-- Each table has explicit constraints, proper types, and indexes.\n",[113,128,130],{"class":115,"line":129},3,[113,131,133],{"emptyLinePlaceholder":132},true,"\n",[113,135,137,141,144,148],{"class":115,"line":136},4,[113,138,140],{"class":139},"svdQ7","CREATE",[113,142,143],{"class":139}," TABLE",[113,145,147],{"class":146},"sIsaT"," customers",[113,149,151],{"class":150},"ssxIu"," (\n",[113,153,155],{"class":115,"line":154},5,[113,156,157],{"class":119},"  -- UUID primary key avoids sequential enumeration attacks and\n",[113,159,161],{"class":115,"line":160},6,[113,162,163],{"class":119},"  -- allows distributed ID generation without central coordination.\n",[113,165,167,170,173,176],{"class":115,"line":166},7,[113,168,169],{"class":150},"  id          UUID ",[113,171,172],{"class":139},"PRIMARY KEY",[113,174,175],{"class":139}," DEFAULT",[113,177,178],{"class":150}," gen_random_uuid(),\n",[113,180,182,185,188,191,194],{"class":115,"line":181},8,[113,183,184],{"class":150},"  email       CITEXT ",[113,186,187],{"class":139},"NOT NULL",[113,189,190],{"class":139}," UNIQUE",[113,192,193],{"class":150},",           ",[113,195,196],{"class":119},"-- CITEXT = case-insensitive email lookup\n",[113,198,200,203,206,209,212,215,218,222],{"class":115,"line":199},9,[113,201,202],{"class":150},"  full_name   ",[113,204,205],{"class":139},"TEXT",[113,207,208],{"class":139}," NOT NULL",[113,210,211],{"class":139}," CHECK",[113,213,214],{"class":150}," (full_name ",[113,216,217],{"class":139},"\u003C>",[113,219,221],{"class":220},"sJ6F3"," ''",[113,223,224],{"class":150},"),\n",[113,226,228,231,233,236],{"class":115,"line":227},10,[113,229,230],{"class":150},"  phone       ",[113,232,205],{"class":139},[113,234,235],{"class":150},",                              ",[113,237,238],{"class":119},"-- nullable: not every customer has a phone\n",[113,240,242,245,248,250,252,255],{"class":115,"line":241},11,[113,243,244],{"class":150},"  created_at  ",[113,246,247],{"class":139},"TIMESTAMPTZ",[113,249,208],{"class":139},[113,251,175],{"class":139},[113,253,254],{"class":139}," now",[113,256,257],{"class":150},"(),\n",[113,259,261],{"class":115,"line":260},12,[113,262,263],{"class":119},"  -- Exclude soft-deleted customers from the unique email constraint\n",[113,265,267,270],{"class":115,"line":266},13,[113,268,269],{"class":150},"  deleted_at  ",[113,271,272],{"class":139},"TIMESTAMPTZ\n",[113,274,276],{"class":115,"line":275},14,[113,277,278],{"class":150},");\n",[113,280,282],{"class":115,"line":281},15,[113,283,133],{"emptyLinePlaceholder":132},[113,285,287],{"class":115,"line":286},16,[113,288,289],{"class":119},"-- Partial unique index: email must be unique among non-deleted rows only.\n",[113,291,293],{"class":115,"line":292},17,[113,294,295],{"class":119},"-- This allows re-registration after soft-delete without violating uniqueness.\n",[113,297,299,301,304],{"class":115,"line":298},18,[113,300,140],{"class":139},[113,302,303],{"class":139}," UNIQUE INDEX",[113,305,306],{"class":146}," customers_email_active_uniq\n",[113,308,310,313],{"class":115,"line":309},19,[113,311,312],{"class":139},"  ON",[113,314,315],{"class":150}," customers (email)\n",[113,317,319,322,325,328,331],{"class":115,"line":318},20,[113,320,321],{"class":139},"  WHERE",[113,323,324],{"class":150}," deleted_at ",[113,326,327],{"class":139},"IS",[113,329,330],{"class":139}," NULL",[113,332,333],{"class":150},";\n",[113,335,337],{"class":115,"line":336},21,[113,338,133],{"emptyLinePlaceholder":132},[113,340,342,344,346,349],{"class":115,"line":341},22,[113,343,140],{"class":139},[113,345,143],{"class":139},[113,347,348],{"class":146}," orders",[113,350,151],{"class":150},[113,352,354,357,360,363,366],{"class":115,"line":353},23,[113,355,356],{"class":150},"  id           ",[113,358,359],{"class":139},"BIGSERIAL",[113,361,362],{"class":139}," PRIMARY KEY",[113,364,365],{"class":150},",            ",[113,367,368],{"class":119},"-- sequential BIGINT (8 bytes); gap-free NOT guaranteed\n",[113,370,372,375,377,380,383,386],{"class":115,"line":371},24,[113,373,374],{"class":150},"  customer_id  UUID ",[113,376,187],{"class":139},[113,378,379],{"class":139}," REFERENCES",[113,381,382],{"class":150}," customers(id) ",[113,384,385],{"class":139},"ON DELETE",[113,387,388],{"class":150}," RESTRICT,\n",[113,390,392,395,398,400,402],{"class":115,"line":391},25,[113,393,394],{"class":139},"  status",[113,396,397],{"class":139},"       TEXT",[113,399,208],{"class":139},[113,401,175],{"class":139},[113,403,404],{"class":220}," 'pending'\n",[113,406,408,411,414,417,420,422,425,428,431,433,436,438,441,443,446],{"class":115,"line":407},26,[113,409,410],{"class":139},"               CHECK",[113,412,413],{"class":150}," (",[113,415,416],{"class":139},"status",[113,418,419],{"class":139}," IN",[113,421,413],{"class":150},[113,423,424],{"class":220},"'pending'",[113,426,427],{"class":150},",",[113,429,430],{"class":220},"'paid'",[113,432,427],{"class":150},[113,434,435],{"class":220},"'shipped'",[113,437,427],{"class":150},[113,439,440],{"class":220},"'cancelled'",[113,442,427],{"class":150},[113,444,445],{"class":220},"'refunded'",[113,447,448],{"class":150},")),\n",[113,450,452,455,458,461,465,468,471,474,476,478],{"class":115,"line":451},27,[113,453,454],{"class":150},"  total        ",[113,456,457],{"class":139},"NUMERIC",[113,459,460],{"class":150},"(",[113,462,464],{"class":463},"snvgF","12",[113,466,467],{"class":150},", ",[113,469,470],{"class":463},"2",[113,472,473],{"class":150},") ",[113,475,187],{"class":139},[113,477,175],{"class":139},[113,479,480],{"class":463}," 0\n",[113,482,484,486,489,492,495,498],{"class":115,"line":483},28,[113,485,410],{"class":139},[113,487,488],{"class":150}," (total ",[113,490,491],{"class":139},">=",[113,493,494],{"class":463}," 0",[113,496,497],{"class":150},"),               ",[113,499,500],{"class":119},"-- NUMERIC, not REAL — money needs exact arithmetic\n",[113,502,504,507,510,512,515,517,519,521,524,527],{"class":115,"line":503},29,[113,505,506],{"class":150},"  currency     ",[113,508,509],{"class":139},"CHAR",[113,511,460],{"class":150},[113,513,514],{"class":463},"3",[113,516,473],{"class":150},[113,518,187],{"class":139},[113,520,175],{"class":139},[113,522,523],{"class":220}," 'USD'",[113,525,526],{"class":150},",   ",[113,528,529],{"class":119},"-- ISO 4217\n",[113,531,533,536,538,540,542,544],{"class":115,"line":532},30,[113,534,535],{"class":150},"  placed_at    ",[113,537,247],{"class":139},[113,539,208],{"class":139},[113,541,175],{"class":139},[113,543,254],{"class":139},[113,545,257],{"class":150},[113,547,549,552,554,556,558,560],{"class":115,"line":548},31,[113,550,551],{"class":150},"  updated_at   ",[113,553,247],{"class":139},[113,555,208],{"class":139},[113,557,175],{"class":139},[113,559,254],{"class":139},[113,561,562],{"class":150},"()\n",[113,564,566],{"class":115,"line":565},32,[113,567,278],{"class":150},[113,569,571],{"class":115,"line":570},33,[113,572,133],{"emptyLinePlaceholder":132},[113,574,576],{"class":115,"line":575},34,[113,577,578],{"class":119},"-- Composite index for \"show me customer X's recent orders\" (hot path).\n",[113,580,582],{"class":115,"line":581},35,[113,583,584],{"class":119},"-- Column order matters: equality on customer_id, then sort on placed_at DESC.\n",[113,586,588,590,593],{"class":115,"line":587},36,[113,589,140],{"class":139},[113,591,592],{"class":139}," INDEX",[113,594,595],{"class":146}," idx_orders_customer_placed\n",[113,597,599,601,604,607],{"class":115,"line":598},37,[113,600,312],{"class":139},[113,602,603],{"class":150}," orders (customer_id, placed_at ",[113,605,606],{"class":139},"DESC",[113,608,278],{"class":150},[113,610,612],{"class":115,"line":611},38,[113,613,133],{"emptyLinePlaceholder":132},[113,615,617],{"class":115,"line":616},39,[113,618,619],{"class":119},"-- Partial index: only pending orders need scanning — skips 99% of rows.\n",[113,621,623,625,627],{"class":115,"line":622},40,[113,624,140],{"class":139},[113,626,592],{"class":139},[113,628,629],{"class":146}," idx_orders_pending\n",[113,631,633,635],{"class":115,"line":632},41,[113,634,312],{"class":139},[113,636,637],{"class":150}," orders (placed_at)\n",[113,639,641,643,646,649,652],{"class":115,"line":640},42,[113,642,321],{"class":139},[113,644,645],{"class":139}," status",[113,647,648],{"class":139}," =",[113,650,651],{"class":220}," 'pending'",[113,653,333],{"class":150},[113,655,657],{"class":115,"line":656},43,[113,658,133],{"emptyLinePlaceholder":132},[113,660,662,664,666,669],{"class":115,"line":661},44,[113,663,140],{"class":139},[113,665,143],{"class":139},[113,667,668],{"class":146}," products",[113,670,151],{"class":150},[113,672,674,677,679,681],{"class":115,"line":673},45,[113,675,676],{"class":150},"  id          ",[113,678,359],{"class":139},[113,680,362],{"class":139},[113,682,683],{"class":150},",\n",[113,685,687,690,692,694,696,699],{"class":115,"line":686},46,[113,688,689],{"class":150},"  sku         ",[113,691,205],{"class":139},[113,693,208],{"class":139},[113,695,190],{"class":139},[113,697,698],{"class":150},",              ",[113,700,701],{"class":119},"-- business identifier, stable across renames\n",[113,703,705,708,711,713],{"class":115,"line":704},47,[113,706,707],{"class":139},"  name",[113,709,710],{"class":139},"        TEXT",[113,712,208],{"class":139},[113,714,683],{"class":150},[113,716,718,721,723,725,728,730,732,734,736,738,741,744,746],{"class":115,"line":717},48,[113,719,720],{"class":150},"  price       ",[113,722,457],{"class":139},[113,724,460],{"class":150},[113,726,727],{"class":463},"10",[113,729,467],{"class":150},[113,731,470],{"class":463},[113,733,473],{"class":150},[113,735,187],{"class":139},[113,737,211],{"class":139},[113,739,740],{"class":150}," (price ",[113,742,743],{"class":139},">",[113,745,494],{"class":463},[113,747,224],{"class":150},[113,749,751,754,757,759,761,763,765,768,770,772],{"class":115,"line":750},49,[113,752,753],{"class":150},"  stock       ",[113,755,756],{"class":139},"INTEGER",[113,758,208],{"class":139},[113,760,175],{"class":139},[113,762,494],{"class":463},[113,764,211],{"class":139},[113,766,767],{"class":150}," (stock ",[113,769,491],{"class":139},[113,771,494],{"class":463},[113,773,224],{"class":150},[113,775,777,779,781,783,785,787],{"class":115,"line":776},50,[113,778,244],{"class":150},[113,780,247],{"class":139},[113,782,208],{"class":139},[113,784,175],{"class":139},[113,786,254],{"class":139},[113,788,562],{"class":150},[113,790,792],{"class":115,"line":791},51,[113,793,278],{"class":150},[113,795,797],{"class":115,"line":796},52,[113,798,133],{"emptyLinePlaceholder":132},[113,800,802,804,806,809],{"class":115,"line":801},53,[113,803,140],{"class":139},[113,805,143],{"class":139},[113,807,808],{"class":146}," order_items",[113,810,151],{"class":150},[113,812,814,816,818,820],{"class":115,"line":813},54,[113,815,356],{"class":150},[113,817,359],{"class":139},[113,819,362],{"class":139},[113,821,683],{"class":150},[113,823,825,828,831,833,835,838,841],{"class":115,"line":824},55,[113,826,827],{"class":150},"  order_id     ",[113,829,830],{"class":139},"BIGINT",[113,832,208],{"class":139},[113,834,379],{"class":139},[113,836,837],{"class":150}," orders(id) ",[113,839,840],{"class":139},"ON DELETE CASCADE",[113,842,683],{"class":150},[113,844,846,849,851,853,855,858,860],{"class":115,"line":845},56,[113,847,848],{"class":150},"  product_id   ",[113,850,830],{"class":139},[113,852,208],{"class":139},[113,854,379],{"class":139},[113,856,857],{"class":150}," products(id) ",[113,859,385],{"class":139},[113,861,388],{"class":150},[113,863,865,868,870,872,874,877,879,881],{"class":115,"line":864},57,[113,866,867],{"class":150},"  quantity     ",[113,869,756],{"class":139},[113,871,208],{"class":139},[113,873,211],{"class":139},[113,875,876],{"class":150}," (quantity ",[113,878,743],{"class":139},[113,880,494],{"class":463},[113,882,224],{"class":150},[113,884,886,889,891,893,895,897,899,901,903,905,908,910,912],{"class":115,"line":885},58,[113,887,888],{"class":150},"  unit_price   ",[113,890,457],{"class":139},[113,892,460],{"class":150},[113,894,727],{"class":463},[113,896,467],{"class":150},[113,898,470],{"class":463},[113,900,473],{"class":150},[113,902,187],{"class":139},[113,904,211],{"class":139},[113,906,907],{"class":150}," (unit_price ",[113,909,491],{"class":139},[113,911,494],{"class":463},[113,913,224],{"class":150},[113,915,917],{"class":115,"line":916},59,[113,918,919],{"class":119},"  -- Snapshot of price at order time — price in products may change later.\n",[113,921,923],{"class":115,"line":922},60,[113,924,925],{"class":119},"  -- Without this, historical order totals would silently change.\n",[113,927,929,932,934,937,940,943,946,949],{"class":115,"line":928},61,[113,930,931],{"class":139},"  CHECK",[113,933,876],{"class":150},[113,935,936],{"class":139},"*",[113,938,939],{"class":150}," unit_price ",[113,941,942],{"class":139},"\u003C=",[113,944,945],{"class":463}," 1000000",[113,947,948],{"class":150},")       ",[113,950,951],{"class":119},"-- sanity bound, prevents overflow in totals\n",[113,953,955],{"class":115,"line":954},62,[113,956,278],{"class":150},[113,958,960],{"class":115,"line":959},63,[113,961,133],{"emptyLinePlaceholder":132},[113,963,965],{"class":115,"line":964},64,[113,966,967],{"class":119},"-- Covering index: the join path order_items→products often only needs product_id + name.\n",[113,969,971],{"class":115,"line":970},65,[113,972,973],{"class":119},"-- A covering index lets the planner satisfy the query from the index alone (index-only scan).\n",[113,975,977,979,981],{"class":115,"line":976},66,[113,978,140],{"class":139},[113,980,592],{"class":139},[113,982,983],{"class":146}," idx_order_items_order_product\n",[113,985,987,989],{"class":115,"line":986},67,[113,988,312],{"class":139},[113,990,991],{"class":150}," order_items (order_id, product_id)\n",[113,993,995,998],{"class":115,"line":994},68,[113,996,997],{"class":139},"  INCLUDE",[113,999,1000],{"class":150}," (quantity, unit_price);\n",[113,1002,1004],{"class":115,"line":1003},69,[113,1005,133],{"emptyLinePlaceholder":132},[113,1007,1009],{"class":115,"line":1008},70,[113,1010,1011],{"class":119},"-- Auto-maintain updated_at on every row update.\n",[113,1013,1015,1018,1021,1024],{"class":115,"line":1014},71,[113,1016,1017],{"class":139},"CREATE OR REPLACE",[113,1019,1020],{"class":139}," FUNCTION",[113,1022,1023],{"class":146}," touch_updated_at",[113,1025,562],{"class":150},[113,1027,1029,1032,1035,1038],{"class":115,"line":1028},72,[113,1030,1031],{"class":139},"RETURNS",[113,1033,1034],{"class":150}," TRIGGER ",[113,1036,1037],{"class":139},"AS",[113,1039,1040],{"class":150}," $$\n",[113,1042,1044],{"class":115,"line":1043},73,[113,1045,1046],{"class":139},"BEGIN\n",[113,1048,1050,1053,1056,1059,1061,1063,1066],{"class":115,"line":1049},74,[113,1051,1052],{"class":463},"  NEW",[113,1054,1055],{"class":150},".",[113,1057,1058],{"class":463},"updated_at",[113,1060,648],{"class":139},[113,1062,254],{"class":139},[113,1064,1065],{"class":150},"();                         ",[113,1067,1068],{"class":119},"-- set before the row is written\n",[113,1070,1072,1075],{"class":115,"line":1071},75,[113,1073,1074],{"class":139},"  RETURN",[113,1076,1077],{"class":150}," NEW;\n",[113,1079,1081,1084],{"class":115,"line":1080},76,[113,1082,1083],{"class":139},"END",[113,1085,333],{"class":150},[113,1087,1089,1092,1095],{"class":115,"line":1088},77,[113,1090,1091],{"class":150},"$$ ",[113,1093,1094],{"class":139},"LANGUAGE",[113,1096,1097],{"class":150}," plpgsql;\n",[113,1099,1101],{"class":115,"line":1100},78,[113,1102,133],{"emptyLinePlaceholder":132},[113,1104,1106,1108,1111],{"class":115,"line":1105},79,[113,1107,140],{"class":139},[113,1109,1110],{"class":139}," TRIGGER",[113,1112,1113],{"class":146}," orders_touch_updated_at\n",[113,1115,1117,1120,1123,1126],{"class":115,"line":1116},80,[113,1118,1119],{"class":139},"  BEFORE",[113,1121,1122],{"class":139}," UPDATE",[113,1124,1125],{"class":139}," ON",[113,1127,1128],{"class":150}," orders\n",[113,1130,1132,1135,1138,1141,1144,1146],{"class":115,"line":1131},81,[113,1133,1134],{"class":139},"  FOR",[113,1136,1137],{"class":150}," EACH ",[113,1139,1140],{"class":139},"ROW",[113,1142,1143],{"class":139}," EXECUTE",[113,1145,1020],{"class":139},[113,1147,1148],{"class":150}," touch_updated_at();\n",[19,1150,1151,1152,1155,1156,1159,1160,1163,1164,1167],{},"A row in ",[110,1153,1154],{},"orders"," ",[38,1157,1158],{},"relates to"," a row in ",[110,1161,1162],{},"customers"," via ",[110,1165,1166],{},"customer_id",". The relational model's power is composing these relationships in queries (joins) without precomputing them.",[14,1169,1171],{"id":1170},"sql-dialects-matter","SQL Dialects Matter",[19,1173,1174],{},"The SQL standard is a baseline. Each engine (PostgreSQL, MySQL, SQLite, SQL Server, Oracle) extends and occasionally violates it.",[1176,1177,1178,1203],"table",{},[1179,1180,1181],"thead",{},[1182,1183,1184,1188,1191,1194,1197,1200],"tr",{},[1185,1186,1187],"th",{},"Feature",[1185,1189,1190],{},"PostgreSQL",[1185,1192,1193],{},"MySQL",[1185,1195,1196],{},"SQLite",[1185,1198,1199],{},"SQL Server",[1185,1201,1202],{},"Oracle",[1204,1205,1206,1233,1258,1274,1289,1310,1327,1352,1385,1408],"tbody",{},[1182,1207,1208,1215,1218,1221,1224,1231],{},[1209,1210,1211,1214],"td",{},[110,1212,1213],{},"RETURNING"," clause",[1209,1216,1217],{},"✅",[1209,1219,1220],{},"✅ (8.0+)",[1209,1222,1223],{},"❌",[1209,1225,1226,1227,1230],{},"✅ (",[110,1228,1229],{},"OUTPUT",")",[1209,1232,1223],{},[1182,1234,1235,1241,1243,1246,1249,1254],{},[1209,1236,1237,1240],{},[110,1238,1239],{},"JSONB"," type",[1209,1242,1217],{},[1209,1244,1245],{},"❌ (JSON only)",[1209,1247,1248],{},"❌ (TEXT)",[1209,1250,1226,1251,1230],{},[110,1252,1253],{},"JSON",[1209,1255,1226,1256,1230],{},[110,1257,1253],{},[1182,1259,1260,1263,1265,1267,1270,1272],{},[1209,1261,1262],{},"Window functions",[1209,1264,1217],{},[1209,1266,1220],{},[1209,1268,1269],{},"✅ (3.25+)",[1209,1271,1217],{},[1209,1273,1217],{},[1182,1275,1276,1279,1281,1283,1285,1287],{},[1209,1277,1278],{},"Recursive CTEs",[1209,1280,1217],{},[1209,1282,1220],{},[1209,1284,1217],{},[1209,1286,1217],{},[1209,1288,1217],{},[1182,1290,1291,1300,1302,1304,1306,1308],{},[1209,1292,1293,1296,1297],{},[110,1294,1295],{},"INTERSECT","\u002F",[110,1298,1299],{},"EXCEPT",[1209,1301,1217],{},[1209,1303,1220],{},[1209,1305,1217],{},[1209,1307,1217],{},[1209,1309,1217],{},[1182,1311,1312,1315,1317,1320,1322,1325],{},[1209,1313,1314],{},"Materialized views",[1209,1316,1217],{},[1209,1318,1319],{},"❌ (manual)",[1209,1321,1223],{},[1209,1323,1324],{},"✅ (indexed views)",[1209,1326,1217],{},[1182,1328,1329,1335,1338,1340,1345,1347],{},[1209,1330,1331,1334],{},[110,1332,1333],{},"IDENTITY"," columns",[1209,1336,1337],{},"✅ (10+)",[1209,1339,1217],{},[1209,1341,1342],{},[110,1343,1344],{},"AUTOINCREMENT",[1209,1346,1217],{},[1209,1348,1349],{},[110,1350,1351],{},"GENERATED",[1182,1353,1354,1357,1363,1368,1371,1376],{},[1209,1355,1356],{},"Booleans",[1209,1358,1359,1360],{},"real ",[110,1361,1362],{},"BOOLEAN",[1209,1364,1365],{},[110,1366,1367],{},"TINYINT(1)",[1209,1369,1370],{},"integer 0\u002F1",[1209,1372,1373],{},[110,1374,1375],{},"BIT",[1209,1377,1378,1379,1296,1382,1230],{},"❌ (use ",[110,1380,1381],{},"1",[110,1383,1384],{},"0",[1182,1386,1387,1393,1396,1399,1402,1405],{},[1209,1388,1389,1392],{},[110,1390,1391],{},"EXPLAIN"," output",[1209,1394,1395],{},"plan tree",[1209,1397,1398],{},"plan + estimates",[1209,1400,1401],{},"query plan",[1209,1403,1404],{},"plan XML",[1209,1406,1407],{},"plan table",[1182,1409,1410,1418,1420,1422,1424,1433],{},[1209,1411,1412,1296,1415],{},[110,1413,1414],{},"LIMIT",[110,1416,1417],{},"OFFSET",[1209,1419,1217],{},[1209,1421,1217],{},[1209,1423,1217],{},[1209,1425,1426,1427,1296,1430,1230],{},"❌ (",[110,1428,1429],{},"TOP",[110,1431,1432],{},"FETCH",[1209,1434,1426,1435,1296,1438,1230],{},[110,1436,1437],{},"ROWNUM",[110,1439,1432],{},[19,1441,1442,1443,1445],{},"This course uses ",[23,1444,1190],{}," syntax as the default (most standard-compliant, most feature-rich open-source engine) and flags dialect differences where they matter.",[14,1447,1449],{"id":1448},"installing-postgresql","Installing PostgreSQL",[94,1451,1453],{"id":1452},"macos-homebrew","macOS (Homebrew)",[99,1455,1457],{"language":1456},"bash",[104,1458,1461],{"className":1459,"code":1460,"language":1456,"meta":108,"style":108},"language-bash shiki shiki-themes github-light github-dark","brew install postgresql@16          # install PostgreSQL 16\nbrew services start postgresql@16   # start as a background service\n\npsql postgres                       # connect to default maintenance DB\n",[110,1462,1463,1477,1492,1496],{"__ignoreMap":108},[113,1464,1465,1468,1471,1474],{"class":115,"line":116},[113,1466,1467],{"class":146},"brew",[113,1469,1470],{"class":220}," install",[113,1472,1473],{"class":220}," postgresql@16",[113,1475,1476],{"class":119},"          # install PostgreSQL 16\n",[113,1478,1479,1481,1484,1487,1489],{"class":115,"line":123},[113,1480,1467],{"class":146},[113,1482,1483],{"class":220}," services",[113,1485,1486],{"class":220}," start",[113,1488,1473],{"class":220},[113,1490,1491],{"class":119},"   # start as a background service\n",[113,1493,1494],{"class":115,"line":129},[113,1495,133],{"emptyLinePlaceholder":132},[113,1497,1498,1501,1504],{"class":115,"line":136},[113,1499,1500],{"class":146},"psql",[113,1502,1503],{"class":220}," postgres",[113,1505,1506],{"class":119},"                       # connect to default maintenance DB\n",[94,1508,1510],{"id":1509},"linux-apt","Linux (apt)",[99,1512,1513],{"language":1456},[104,1514,1516],{"className":1459,"code":1515,"language":1456,"meta":108,"style":108},"sudo apt install postgresql postgresql-contrib   # contrib adds gen_random_uuid() etc.\nsudo systemctl enable --now postgresql            # start on boot + immediately\nsudo -u postgres psql                              # connect as the postgres superuser\n",[110,1517,1518,1537,1555],{"__ignoreMap":108},[113,1519,1520,1523,1526,1528,1531,1534],{"class":115,"line":116},[113,1521,1522],{"class":146},"sudo",[113,1524,1525],{"class":220}," apt",[113,1527,1470],{"class":220},[113,1529,1530],{"class":220}," postgresql",[113,1532,1533],{"class":220}," postgresql-contrib",[113,1535,1536],{"class":119},"   # contrib adds gen_random_uuid() etc.\n",[113,1538,1539,1541,1544,1547,1550,1552],{"class":115,"line":123},[113,1540,1522],{"class":146},[113,1542,1543],{"class":220}," systemctl",[113,1545,1546],{"class":220}," enable",[113,1548,1549],{"class":463}," --now",[113,1551,1530],{"class":220},[113,1553,1554],{"class":119},"            # start on boot + immediately\n",[113,1556,1557,1559,1562,1564,1567],{"class":115,"line":129},[113,1558,1522],{"class":146},[113,1560,1561],{"class":463}," -u",[113,1563,1503],{"class":220},[113,1565,1566],{"class":220}," psql",[113,1568,1569],{"class":119},"                              # connect as the postgres superuser\n",[94,1571,1573],{"id":1572},"docker-any-os","Docker (any OS)",[99,1575,1576],{"language":1456},[104,1577,1579],{"className":1459,"code":1578,"language":1456,"meta":108,"style":108},"# --name pg: container alias; -e: env vars; -p: map host 5432 → container 5432\ndocker run --name pg \\\n  -e POSTGRES_PASSWORD=secret \\\n  -e POSTGRES_DB=practice \\\n  -p 5432:5432 \\\n  -d postgres:16                   # -d: run detached (background)\n\npsql -h localhost -U postgres -d practice\n# password: secret\n",[110,1580,1581,1586,1603,1613,1622,1632,1643,1647,1668],{"__ignoreMap":108},[113,1582,1583],{"class":115,"line":116},[113,1584,1585],{"class":119},"# --name pg: container alias; -e: env vars; -p: map host 5432 → container 5432\n",[113,1587,1588,1591,1594,1597,1600],{"class":115,"line":123},[113,1589,1590],{"class":146},"docker",[113,1592,1593],{"class":220}," run",[113,1595,1596],{"class":463}," --name",[113,1598,1599],{"class":220}," pg",[113,1601,1602],{"class":463}," \\\n",[113,1604,1605,1608,1611],{"class":115,"line":129},[113,1606,1607],{"class":463},"  -e",[113,1609,1610],{"class":220}," POSTGRES_PASSWORD=secret",[113,1612,1602],{"class":463},[113,1614,1615,1617,1620],{"class":115,"line":136},[113,1616,1607],{"class":463},[113,1618,1619],{"class":220}," POSTGRES_DB=practice",[113,1621,1602],{"class":463},[113,1623,1624,1627,1630],{"class":115,"line":154},[113,1625,1626],{"class":463},"  -p",[113,1628,1629],{"class":220}," 5432:5432",[113,1631,1602],{"class":463},[113,1633,1634,1637,1640],{"class":115,"line":160},[113,1635,1636],{"class":463},"  -d",[113,1638,1639],{"class":220}," postgres:16",[113,1641,1642],{"class":119},"                   # -d: run detached (background)\n",[113,1644,1645],{"class":115,"line":166},[113,1646,133],{"emptyLinePlaceholder":132},[113,1648,1649,1651,1654,1657,1660,1662,1665],{"class":115,"line":181},[113,1650,1500],{"class":146},[113,1652,1653],{"class":463}," -h",[113,1655,1656],{"class":220}," localhost",[113,1658,1659],{"class":463}," -U",[113,1661,1503],{"class":220},[113,1663,1664],{"class":463}," -d",[113,1666,1667],{"class":220}," practice\n",[113,1669,1670],{"class":115,"line":199},[113,1671,1672],{"class":119},"# password: secret\n",[94,1674,1196],{"id":1675},"sqlite",[19,1677,1678],{},"SQLite is a single-file embedded database — no server. Perfect for local prototyping and mobile apps.",[99,1680,1681],{"language":1456},[104,1682,1684],{"className":1459,"code":1683,"language":1456,"meta":108,"style":108},"brew install sqlite                 # macOS\nsudo apt install sqlite3            # Linux (Debian\u002FUbuntu)\n\nsqlite3 --version                   # verify install\nsqlite3 practice.db                 # create\u002Fopen a DB file (created on first connect)\nsqlite> .tables                     # list tables\nsqlite> .quit\n",[110,1685,1686,1698,1712,1716,1727,1737,1750],{"__ignoreMap":108},[113,1687,1688,1690,1692,1695],{"class":115,"line":116},[113,1689,1467],{"class":146},[113,1691,1470],{"class":220},[113,1693,1694],{"class":220}," sqlite",[113,1696,1697],{"class":119},"                 # macOS\n",[113,1699,1700,1702,1704,1706,1709],{"class":115,"line":123},[113,1701,1522],{"class":146},[113,1703,1525],{"class":220},[113,1705,1470],{"class":220},[113,1707,1708],{"class":220}," sqlite3",[113,1710,1711],{"class":119},"            # Linux (Debian\u002FUbuntu)\n",[113,1713,1714],{"class":115,"line":129},[113,1715,133],{"emptyLinePlaceholder":132},[113,1717,1718,1721,1724],{"class":115,"line":136},[113,1719,1720],{"class":146},"sqlite3",[113,1722,1723],{"class":463}," --version",[113,1725,1726],{"class":119},"                   # verify install\n",[113,1728,1729,1731,1734],{"class":115,"line":154},[113,1730,1720],{"class":146},[113,1732,1733],{"class":220}," practice.db",[113,1735,1736],{"class":119},"                 # create\u002Fopen a DB file (created on first connect)\n",[113,1738,1739,1741,1744,1747],{"class":115,"line":160},[113,1740,1675],{"class":146},[113,1742,1743],{"class":150},"> ",[113,1745,1746],{"class":220},".tables",[113,1748,1749],{"class":119},"                     # list tables\n",[113,1751,1752,1754,1756],{"class":115,"line":166},[113,1753,1675],{"class":146},[113,1755,1743],{"class":150},[113,1757,1758],{"class":220},".quit\n",[14,1760,1762,1763,1765],{"id":1761},"the-psql-client","The ",[110,1764,1500],{}," Client",[19,1767,1768,1770],{},[110,1769,1500],{}," is PostgreSQL's official CLI. Learn it — it's far more powerful than GUI tools for quick work.",[1176,1772,1773,1783],{},[1179,1774,1775],{},[1182,1776,1777,1780],{},[1185,1778,1779],{},"Command",[1185,1781,1782],{},"Purpose",[1204,1784,1785,1795,1805,1815,1825,1835,1845,1855,1865,1875,1885,1895,1905,1915,1928,1940,1950,1964,1974,1984,1994,2004],{},[1182,1786,1787,1792],{},[1209,1788,1789],{},[110,1790,1791],{},"\\l",[1209,1793,1794],{},"List databases.",[1182,1796,1797,1802],{},[1209,1798,1799],{},[110,1800,1801],{},"\\c dbname",[1209,1803,1804],{},"Connect to a database.",[1182,1806,1807,1812],{},[1209,1808,1809],{},[110,1810,1811],{},"\\dt",[1209,1813,1814],{},"List tables.",[1182,1816,1817,1822],{},[1209,1818,1819],{},[110,1820,1821],{},"\\dt+",[1209,1823,1824],{},"List tables with size and description.",[1182,1826,1827,1832],{},[1209,1828,1829],{},[110,1830,1831],{},"\\d tablename",[1209,1833,1834],{},"Describe a table (columns, types, constraints).",[1182,1836,1837,1842],{},[1209,1838,1839],{},[110,1840,1841],{},"\\d+ tablename",[1209,1843,1844],{},"Describe with storage info and comments.",[1182,1846,1847,1852],{},[1209,1848,1849],{},[110,1850,1851],{},"\\df",[1209,1853,1854],{},"List functions.",[1182,1856,1857,1862],{},[1209,1858,1859],{},[110,1860,1861],{},"\\dv",[1209,1863,1864],{},"List views.",[1182,1866,1867,1872],{},[1209,1868,1869],{},[110,1870,1871],{},"\\dx",[1209,1873,1874],{},"List extensions.",[1182,1876,1877,1882],{},[1209,1878,1879],{},[110,1880,1881],{},"\\x",[1209,1883,1884],{},"Toggle expanded (vertical) display — essential for wide rows.",[1182,1886,1887,1892],{},[1209,1888,1889],{},[110,1890,1891],{},"\\x on",[1209,1893,1894],{},"Enable expanded display explicitly.",[1182,1896,1897,1902],{},[1209,1898,1899],{},[110,1900,1901],{},"\\timing",[1209,1903,1904],{},"Toggle query timing.",[1182,1906,1907,1912],{},[1209,1908,1909],{},[110,1910,1911],{},"\\timing on",[1209,1913,1914],{},"Enable query timing explicitly.",[1182,1916,1917,1922],{},[1209,1918,1919],{},[110,1920,1921],{},"\\e",[1209,1923,1924,1925,1055],{},"Open the last query in ",[110,1926,1927],{},"$EDITOR",[1182,1929,1930,1935],{},[1209,1931,1932],{},[110,1933,1934],{},"\\ef funcname",[1209,1936,1937,1938,1055],{},"Edit a function definition in ",[110,1939,1927],{},[1182,1941,1942,1947],{},[1209,1943,1944],{},[110,1945,1946],{},"\\i file.sql",[1209,1948,1949],{},"Execute a SQL file.",[1182,1951,1952,1957],{},[1209,1953,1954],{},[110,1955,1956],{},"\\o file.txt",[1209,1958,1959,1960,1963],{},"Send query output to a file (",[110,1961,1962],{},"\\o"," alone restores stdout).",[1182,1965,1966,1971],{},[1209,1967,1968],{},[110,1969,1970],{},"\\pset pager off",[1209,1972,1973],{},"Disable the pager (useful when piping output).",[1182,1975,1976,1981],{},[1209,1977,1978],{},[110,1979,1980],{},"\\set ECHO all",[1209,1982,1983],{},"Echo each statement before execution (debugging scripts).",[1182,1985,1986,1991],{},[1209,1987,1988],{},[110,1989,1990],{},"\\?",[1209,1992,1993],{},"Help for backslash commands.",[1182,1995,1996,2001],{},[1209,1997,1998],{},[110,1999,2000],{},"\\h CREATE TABLE",[1209,2002,2003],{},"Syntax help for a SQL command.",[1182,2005,2006,2011],{},[1209,2007,2008],{},[110,2009,2010],{},"\\q",[1209,2012,2013],{},"Quit.",[99,2015,2016],{"language":1456},[104,2017,2019],{"className":1459,"code":2018,"language":1456,"meta":108,"style":108},"psql -h localhost -U postgres -d practice -c \"SELECT now();\"  # run one query, exit\npsql -d practice -f setup.sql                                 # run a SQL file\npsql -d practice \u003C dump.sql                                   # restore from dump\npsql -d practice -At -c \"SELECT count(*) FROM customers\"     # -A: unaligned, -t: tuples only → raw number\n",[110,2020,2021,2047,2064,2081],{"__ignoreMap":108},[113,2022,2023,2025,2027,2029,2031,2033,2035,2038,2041,2044],{"class":115,"line":116},[113,2024,1500],{"class":146},[113,2026,1653],{"class":463},[113,2028,1656],{"class":220},[113,2030,1659],{"class":463},[113,2032,1503],{"class":220},[113,2034,1664],{"class":463},[113,2036,2037],{"class":220}," practice",[113,2039,2040],{"class":463}," -c",[113,2042,2043],{"class":220}," \"SELECT now();\"",[113,2045,2046],{"class":119},"  # run one query, exit\n",[113,2048,2049,2051,2053,2055,2058,2061],{"class":115,"line":123},[113,2050,1500],{"class":146},[113,2052,1664],{"class":463},[113,2054,2037],{"class":220},[113,2056,2057],{"class":463}," -f",[113,2059,2060],{"class":220}," setup.sql",[113,2062,2063],{"class":119},"                                 # run a SQL file\n",[113,2065,2066,2068,2070,2072,2075,2078],{"class":115,"line":129},[113,2067,1500],{"class":146},[113,2069,1664],{"class":463},[113,2071,2037],{"class":220},[113,2073,2074],{"class":139}," \u003C",[113,2076,2077],{"class":220}," dump.sql",[113,2079,2080],{"class":119},"                                   # restore from dump\n",[113,2082,2083,2085,2087,2089,2092,2094,2097],{"class":115,"line":136},[113,2084,1500],{"class":146},[113,2086,1664],{"class":463},[113,2088,2037],{"class":220},[113,2090,2091],{"class":463}," -At",[113,2093,2040],{"class":463},[113,2095,2096],{"class":220}," \"SELECT count(*) FROM customers\"",[113,2098,2099],{"class":119},"     # -A: unaligned, -t: tuples only → raw number\n",[14,2101,2103],{"id":2102},"creating-your-first-database","Creating Your First Database",[99,2105,2106],{"language":101},[104,2107,2109],{"className":106,"code":2108,"language":101,"meta":108,"style":108},"-- DDL: create a database. Note: CREATE DATABASE cannot run inside a\n-- transaction block (BEGIN\u002FCOMMIT), so it must be its own statement.\nCREATE DATABASE practice;\n\n-- In psql, switch to the new database:\n--   \\c practice\n\n-- DDL: define a table. Each column has a type and constraints.\nCREATE TABLE greetings (\n  id      SERIAL PRIMARY KEY,              -- SERIAL = INTEGER + sequence + default\n  message TEXT NOT NULL\n);\n\n-- DML: insert a row. The SERIAL column is auto-populated by the sequence.\nINSERT INTO greetings (message) VALUES ('Hello, SQL!');\n\n-- DQL: read it back. SELECT * is fine for exploration; in production code,\n-- always list explicit columns (see Chapter 02).\nSELECT * FROM greetings;\n--  id |   message\n-- ----+------------\n--   1 | Hello, SQL!\n",[110,2110,2111,2116,2121,2132,2136,2141,2146,2150,2155,2166,2181,2192,2196,2200,2205,2228,2232,2237,2242,2256,2261,2266],{"__ignoreMap":108},[113,2112,2113],{"class":115,"line":116},[113,2114,2115],{"class":119},"-- DDL: create a database. Note: CREATE DATABASE cannot run inside a\n",[113,2117,2118],{"class":115,"line":123},[113,2119,2120],{"class":119},"-- transaction block (BEGIN\u002FCOMMIT), so it must be its own statement.\n",[113,2122,2123,2125,2128,2130],{"class":115,"line":129},[113,2124,140],{"class":139},[113,2126,2127],{"class":139}," DATABASE",[113,2129,2037],{"class":146},[113,2131,333],{"class":150},[113,2133,2134],{"class":115,"line":136},[113,2135,133],{"emptyLinePlaceholder":132},[113,2137,2138],{"class":115,"line":154},[113,2139,2140],{"class":119},"-- In psql, switch to the new database:\n",[113,2142,2143],{"class":115,"line":160},[113,2144,2145],{"class":119},"--   \\c practice\n",[113,2147,2148],{"class":115,"line":166},[113,2149,133],{"emptyLinePlaceholder":132},[113,2151,2152],{"class":115,"line":181},[113,2153,2154],{"class":119},"-- DDL: define a table. Each column has a type and constraints.\n",[113,2156,2157,2159,2161,2164],{"class":115,"line":199},[113,2158,140],{"class":139},[113,2160,143],{"class":139},[113,2162,2163],{"class":146}," greetings",[113,2165,151],{"class":150},[113,2167,2168,2171,2174,2176,2178],{"class":115,"line":227},[113,2169,2170],{"class":150},"  id      ",[113,2172,2173],{"class":139},"SERIAL",[113,2175,362],{"class":139},[113,2177,698],{"class":150},[113,2179,2180],{"class":119},"-- SERIAL = INTEGER + sequence + default\n",[113,2182,2183,2186,2189],{"class":115,"line":241},[113,2184,2185],{"class":139},"  message",[113,2187,2188],{"class":139}," TEXT",[113,2190,2191],{"class":139}," NOT NULL\n",[113,2193,2194],{"class":115,"line":260},[113,2195,278],{"class":150},[113,2197,2198],{"class":115,"line":266},[113,2199,133],{"emptyLinePlaceholder":132},[113,2201,2202],{"class":115,"line":275},[113,2203,2204],{"class":119},"-- DML: insert a row. The SERIAL column is auto-populated by the sequence.\n",[113,2206,2207,2210,2213,2216,2218,2221,2223,2226],{"class":115,"line":281},[113,2208,2209],{"class":139},"INSERT INTO",[113,2211,2212],{"class":150}," greetings (",[113,2214,2215],{"class":139},"message",[113,2217,473],{"class":150},[113,2219,2220],{"class":139},"VALUES",[113,2222,413],{"class":150},[113,2224,2225],{"class":220},"'Hello, SQL!'",[113,2227,278],{"class":150},[113,2229,2230],{"class":115,"line":286},[113,2231,133],{"emptyLinePlaceholder":132},[113,2233,2234],{"class":115,"line":292},[113,2235,2236],{"class":119},"-- DQL: read it back. SELECT * is fine for exploration; in production code,\n",[113,2238,2239],{"class":115,"line":298},[113,2240,2241],{"class":119},"-- always list explicit columns (see Chapter 02).\n",[113,2243,2244,2247,2250,2253],{"class":115,"line":309},[113,2245,2246],{"class":139},"SELECT",[113,2248,2249],{"class":139}," *",[113,2251,2252],{"class":139}," FROM",[113,2254,2255],{"class":150}," greetings;\n",[113,2257,2258],{"class":115,"line":318},[113,2259,2260],{"class":119},"--  id |   message\n",[113,2262,2263],{"class":115,"line":336},[113,2264,2265],{"class":119},"-- ----+------------\n",[113,2267,2268],{"class":115,"line":341},[113,2269,2270],{"class":119},"--   1 | Hello, SQL!\n",[14,2272,2274],{"id":2273},"loading-sample-data","Loading Sample Data",[99,2276,2277],{"language":1456},[104,2278,2280],{"className":1459,"code":2279,"language":1456,"meta":108,"style":108},"# Load the dvdrental sample database (15 tables: customer, payment, rental, film, ...)\nwget https:\u002F\u002Fwww.postgresqltutorial.com\u002Fwp-content\u002Fuploads\u002F2019\u002F05\u002Fdvdrental.zip\nunzip dvdrental.zip\ncreatedb dvdrental\npg_restore -d dvdrental dvdrental.tar       # restore from the tar archive\npsql -d dvdrental -c \"\\dt\"                   # verify tables loaded\n",[110,2281,2282,2287,2295,2303,2311,2327],{"__ignoreMap":108},[113,2283,2284],{"class":115,"line":116},[113,2285,2286],{"class":119},"# Load the dvdrental sample database (15 tables: customer, payment, rental, film, ...)\n",[113,2288,2289,2292],{"class":115,"line":123},[113,2290,2291],{"class":146},"wget",[113,2293,2294],{"class":220}," https:\u002F\u002Fwww.postgresqltutorial.com\u002Fwp-content\u002Fuploads\u002F2019\u002F05\u002Fdvdrental.zip\n",[113,2296,2297,2300],{"class":115,"line":129},[113,2298,2299],{"class":146},"unzip",[113,2301,2302],{"class":220}," dvdrental.zip\n",[113,2304,2305,2308],{"class":115,"line":136},[113,2306,2307],{"class":146},"createdb",[113,2309,2310],{"class":220}," dvdrental\n",[113,2312,2313,2316,2318,2321,2324],{"class":115,"line":154},[113,2314,2315],{"class":146},"pg_restore",[113,2317,1664],{"class":463},[113,2319,2320],{"class":220}," dvdrental",[113,2322,2323],{"class":220}," dvdrental.tar",[113,2325,2326],{"class":119},"       # restore from the tar archive\n",[113,2328,2329,2331,2333,2335,2337,2340],{"class":115,"line":160},[113,2330,1500],{"class":146},[113,2332,1664],{"class":463},[113,2334,2320],{"class":220},[113,2336,2040],{"class":463},[113,2338,2339],{"class":220}," \"\\dt\"",[113,2341,2342],{"class":119},"                   # verify tables loaded\n",[19,2344,2345],{},"Minimal hand-typed schema for the examples in this course:",[99,2347,2349],{"language":101,"filename":2348},"seed.sql",[104,2350,2352],{"className":106,"code":2351,"language":101,"meta":108,"style":108},"CREATE TABLE customers (\n  id    SERIAL PRIMARY KEY,\n  name  TEXT NOT NULL,\n  city  TEXT                                  -- nullable: city may be unknown\n);\n\nCREATE TABLE orders (\n  id           SERIAL PRIMARY KEY,\n  customer_id  INTEGER NOT NULL REFERENCES customers(id),\n  amount       NUMERIC(10, 2) NOT NULL CHECK (amount >= 0),\n  ordered_on   DATE NOT NULL DEFAULT CURRENT_DATE\n);\n\nINSERT INTO customers (name, city) VALUES\n  ('Alice', 'NYC'),\n  ('Bob',   'LA'),\n  ('Carol', NULL);                            -- Carol's city is unknown — see NULL chapters\n\nINSERT INTO orders (customer_id, amount) VALUES\n  (1, 99.50),\n  (1, 12.00),\n  (2, 450.00),\n  (3, 7.25);\n",[110,2353,2354,2364,2375,2386,2396,2400,2404,2414,2424,2438,2468,2483,2487,2491,2507,2522,2536,2554,2558,2567,2585,2602,2619],{"__ignoreMap":108},[113,2355,2356,2358,2360,2362],{"class":115,"line":116},[113,2357,140],{"class":139},[113,2359,143],{"class":139},[113,2361,147],{"class":146},[113,2363,151],{"class":150},[113,2365,2366,2369,2371,2373],{"class":115,"line":123},[113,2367,2368],{"class":150},"  id    ",[113,2370,2173],{"class":139},[113,2372,362],{"class":139},[113,2374,683],{"class":150},[113,2376,2377,2379,2382,2384],{"class":115,"line":129},[113,2378,707],{"class":139},[113,2380,2381],{"class":139},"  TEXT",[113,2383,208],{"class":139},[113,2385,683],{"class":150},[113,2387,2388,2391,2393],{"class":115,"line":136},[113,2389,2390],{"class":150},"  city  ",[113,2392,205],{"class":139},[113,2394,2395],{"class":119},"                                  -- nullable: city may be unknown\n",[113,2397,2398],{"class":115,"line":154},[113,2399,278],{"class":150},[113,2401,2402],{"class":115,"line":160},[113,2403,133],{"emptyLinePlaceholder":132},[113,2405,2406,2408,2410,2412],{"class":115,"line":166},[113,2407,140],{"class":139},[113,2409,143],{"class":139},[113,2411,348],{"class":146},[113,2413,151],{"class":150},[113,2415,2416,2418,2420,2422],{"class":115,"line":181},[113,2417,356],{"class":150},[113,2419,2173],{"class":139},[113,2421,362],{"class":139},[113,2423,683],{"class":150},[113,2425,2426,2429,2431,2433,2435],{"class":115,"line":199},[113,2427,2428],{"class":150},"  customer_id  ",[113,2430,756],{"class":139},[113,2432,208],{"class":139},[113,2434,379],{"class":139},[113,2436,2437],{"class":150}," customers(id),\n",[113,2439,2440,2443,2445,2447,2449,2451,2453,2455,2457,2459,2462,2464,2466],{"class":115,"line":227},[113,2441,2442],{"class":150},"  amount       ",[113,2444,457],{"class":139},[113,2446,460],{"class":150},[113,2448,727],{"class":463},[113,2450,467],{"class":150},[113,2452,470],{"class":463},[113,2454,473],{"class":150},[113,2456,187],{"class":139},[113,2458,211],{"class":139},[113,2460,2461],{"class":150}," (amount ",[113,2463,491],{"class":139},[113,2465,494],{"class":463},[113,2467,224],{"class":150},[113,2469,2470,2473,2476,2478,2480],{"class":115,"line":241},[113,2471,2472],{"class":150},"  ordered_on   ",[113,2474,2475],{"class":139},"DATE",[113,2477,208],{"class":139},[113,2479,175],{"class":139},[113,2481,2482],{"class":150}," CURRENT_DATE\n",[113,2484,2485],{"class":115,"line":260},[113,2486,278],{"class":150},[113,2488,2489],{"class":115,"line":266},[113,2490,133],{"emptyLinePlaceholder":132},[113,2492,2493,2495,2498,2501,2504],{"class":115,"line":275},[113,2494,2209],{"class":139},[113,2496,2497],{"class":150}," customers (",[113,2499,2500],{"class":139},"name",[113,2502,2503],{"class":150},", city) ",[113,2505,2506],{"class":139},"VALUES\n",[113,2508,2509,2512,2515,2517,2520],{"class":115,"line":281},[113,2510,2511],{"class":150},"  (",[113,2513,2514],{"class":220},"'Alice'",[113,2516,467],{"class":150},[113,2518,2519],{"class":220},"'NYC'",[113,2521,224],{"class":150},[113,2523,2524,2526,2529,2531,2534],{"class":115,"line":286},[113,2525,2511],{"class":150},[113,2527,2528],{"class":220},"'Bob'",[113,2530,526],{"class":150},[113,2532,2533],{"class":220},"'LA'",[113,2535,224],{"class":150},[113,2537,2538,2540,2543,2545,2548,2551],{"class":115,"line":292},[113,2539,2511],{"class":150},[113,2541,2542],{"class":220},"'Carol'",[113,2544,467],{"class":150},[113,2546,2547],{"class":139},"NULL",[113,2549,2550],{"class":150},");                            ",[113,2552,2553],{"class":119},"-- Carol's city is unknown — see NULL chapters\n",[113,2555,2556],{"class":115,"line":298},[113,2557,133],{"emptyLinePlaceholder":132},[113,2559,2560,2562,2565],{"class":115,"line":309},[113,2561,2209],{"class":139},[113,2563,2564],{"class":150}," orders (customer_id, amount) ",[113,2566,2506],{"class":139},[113,2568,2569,2571,2573,2575,2578,2580,2583],{"class":115,"line":318},[113,2570,2511],{"class":150},[113,2572,1381],{"class":463},[113,2574,467],{"class":150},[113,2576,2577],{"class":463},"99",[113,2579,1055],{"class":150},[113,2581,2582],{"class":463},"50",[113,2584,224],{"class":150},[113,2586,2587,2589,2591,2593,2595,2597,2600],{"class":115,"line":336},[113,2588,2511],{"class":150},[113,2590,1381],{"class":463},[113,2592,467],{"class":150},[113,2594,464],{"class":463},[113,2596,1055],{"class":150},[113,2598,2599],{"class":463},"00",[113,2601,224],{"class":150},[113,2603,2604,2606,2608,2610,2613,2615,2617],{"class":115,"line":341},[113,2605,2511],{"class":150},[113,2607,470],{"class":463},[113,2609,467],{"class":150},[113,2611,2612],{"class":463},"450",[113,2614,1055],{"class":150},[113,2616,2599],{"class":463},[113,2618,224],{"class":150},[113,2620,2621,2623,2625,2627,2630,2632,2635],{"class":115,"line":353},[113,2622,2511],{"class":150},[113,2624,514],{"class":463},[113,2626,467],{"class":150},[113,2628,2629],{"class":463},"7",[113,2631,1055],{"class":150},[113,2633,2634],{"class":463},"25",[113,2636,278],{"class":150},[14,2638,2640],{"id":2639},"sql-statement-categories","SQL Statement Categories",[1176,2642,2643,2655],{},[1179,2644,2645],{},[1182,2646,2647,2650,2653],{},[1185,2648,2649],{},"Category",[1185,2651,2652],{},"Examples",[1185,2654,1782],{},[1204,2656,2657,2681,2705,2724],{},[1182,2658,2659,2665,2678],{},[1209,2660,2661,2664],{},[23,2662,2663],{},"DDL"," (Data Definition)",[1209,2666,2667,467,2669,467,2672,467,2675],{},[110,2668,140],{},[110,2670,2671],{},"ALTER",[110,2673,2674],{},"DROP",[110,2676,2677],{},"TRUNCATE",[1209,2679,2680],{},"Define\u002Fmodify schema.",[1182,2682,2683,2689,2702],{},[1209,2684,2685,2688],{},[23,2686,2687],{},"DML"," (Data Manipulation)",[1209,2690,2691,467,2693,467,2696,467,2699],{},[110,2692,2246],{},[110,2694,2695],{},"INSERT",[110,2697,2698],{},"UPDATE",[110,2700,2701],{},"DELETE",[1209,2703,2704],{},"Read\u002Fwrite rows.",[1182,2706,2707,2713,2721],{},[1209,2708,2709,2712],{},[23,2710,2711],{},"DCL"," (Data Control)",[1209,2714,2715,467,2718],{},[110,2716,2717],{},"GRANT",[110,2719,2720],{},"REVOKE",[1209,2722,2723],{},"Permissions.",[1182,2725,2726,2732,2746],{},[1209,2727,2728,2731],{},[23,2729,2730],{},"TCL"," (Transaction Control)",[1209,2733,2734,467,2737,467,2740,467,2743],{},[110,2735,2736],{},"BEGIN",[110,2738,2739],{},"COMMIT",[110,2741,2742],{},"ROLLBACK",[110,2744,2745],{},"SAVEPOINT",[1209,2747,2748],{},"Transaction boundaries.",[19,2750,2751,2753],{},[110,2752,2246],{}," is technically DQL (Data Query Language), a subcategory of DML. Most practitioners just call it \"queries.\"",[14,2755,2757],{"id":2756},"how-a-query-executes","How a Query Executes",[19,2759,2760],{},"A SQL statement passes through these physical phases:",[2762,2763,2764,2770,2776],"ol",{},[31,2765,2766,2769],{},[23,2767,2768],{},"Parse"," — syntax check, build a parse tree.",[31,2771,2772,2775],{},[23,2773,2774],{},"Plan"," — the optimizer chooses access paths (seq scan vs index), join order, join methods.",[31,2777,2778,2781],{},[23,2779,2780],{},"Execute"," — the executor runs the plan, fetching and combining rows.",[94,2783,2785],{"id":2784},"logical-evaluation-order","Logical Evaluation Order",[19,2787,2788,2789,2792,2793,2795],{},"The SQL standard defines a ",[23,2790,2791],{},"logical"," evaluation order for ",[110,2794,2246],{}," (the physical order differs — the planner reorders for efficiency):",[99,2797,2799],{"language":2798},"text",[104,2800,2804],{"className":2801,"code":2803,"language":2798,"meta":108},[2802],"language-text","FROM \u002F JOIN  →  WHERE  →  GROUP BY  →  HAVING  →  SELECT  →  DISTINCT  →  ORDER BY  →  LIMIT\u002FOFFSET\n",[110,2805,2803],{"__ignoreMap":108},[19,2807,2808,2809,2812,2813,2815,2816,1155,2819,2821,2822,2825,2826,2829,2830,2832],{},"This order explains why you can't reference a column alias in ",[110,2810,2811],{},"WHERE"," (the alias is created in ",[110,2814,2246],{},", which runs ",[38,2817,2818],{},"after",[110,2820,2811],{},") but ",[38,2823,2824],{},"can"," reference it in ",[110,2827,2828],{},"ORDER BY"," (which runs after ",[110,2831,2246],{},").",[99,2834,2835],{"language":101},[104,2836,2838],{"className":106,"code":2837,"language":101,"meta":108,"style":108},"-- ❌ ERROR: column \"total\" does not exist\n-- The alias \"total\" is assigned in SELECT (step 5), but WHERE runs at step 2.\n-- At WHERE time, the alias doesn't exist yet — the planner doesn't know what \"total\" is.\nSELECT amount * 1.08 AS total\nFROM orders\nWHERE total > 100;\n\n-- ✅ Correct: repeat the full expression in WHERE.\n-- The planner may optimize this to compute it once, but you must write it out.\nSELECT amount * 1.08 AS total\nFROM orders\nWHERE amount * 1.08 > 100;\n\n-- ✅ Alias IS visible in ORDER BY — ORDER BY runs after SELECT, so the alias exists.\nSELECT amount * 1.08 AS total\nFROM orders\nORDER BY total DESC;\n",[110,2839,2840,2845,2850,2855,2878,2885,2899,2903,2908,2913,2931,2937,2958,2962,2967,2985,2991],{"__ignoreMap":108},[113,2841,2842],{"class":115,"line":116},[113,2843,2844],{"class":119},"-- ❌ ERROR: column \"total\" does not exist\n",[113,2846,2847],{"class":115,"line":123},[113,2848,2849],{"class":119},"-- The alias \"total\" is assigned in SELECT (step 5), but WHERE runs at step 2.\n",[113,2851,2852],{"class":115,"line":129},[113,2853,2854],{"class":119},"-- At WHERE time, the alias doesn't exist yet — the planner doesn't know what \"total\" is.\n",[113,2856,2857,2859,2862,2864,2867,2869,2872,2875],{"class":115,"line":136},[113,2858,2246],{"class":139},[113,2860,2861],{"class":150}," amount ",[113,2863,936],{"class":139},[113,2865,2866],{"class":463}," 1",[113,2868,1055],{"class":150},[113,2870,2871],{"class":463},"08",[113,2873,2874],{"class":139}," AS",[113,2876,2877],{"class":150}," total\n",[113,2879,2880,2883],{"class":115,"line":154},[113,2881,2882],{"class":139},"FROM",[113,2884,1128],{"class":150},[113,2886,2887,2889,2892,2894,2897],{"class":115,"line":160},[113,2888,2811],{"class":139},[113,2890,2891],{"class":150}," total ",[113,2893,743],{"class":139},[113,2895,2896],{"class":463}," 100",[113,2898,333],{"class":150},[113,2900,2901],{"class":115,"line":166},[113,2902,133],{"emptyLinePlaceholder":132},[113,2904,2905],{"class":115,"line":181},[113,2906,2907],{"class":119},"-- ✅ Correct: repeat the full expression in WHERE.\n",[113,2909,2910],{"class":115,"line":199},[113,2911,2912],{"class":119},"-- The planner may optimize this to compute it once, but you must write it out.\n",[113,2914,2915,2917,2919,2921,2923,2925,2927,2929],{"class":115,"line":227},[113,2916,2246],{"class":139},[113,2918,2861],{"class":150},[113,2920,936],{"class":139},[113,2922,2866],{"class":463},[113,2924,1055],{"class":150},[113,2926,2871],{"class":463},[113,2928,2874],{"class":139},[113,2930,2877],{"class":150},[113,2932,2933,2935],{"class":115,"line":241},[113,2934,2882],{"class":139},[113,2936,1128],{"class":150},[113,2938,2939,2941,2943,2945,2947,2949,2951,2954,2956],{"class":115,"line":260},[113,2940,2811],{"class":139},[113,2942,2861],{"class":150},[113,2944,936],{"class":139},[113,2946,2866],{"class":463},[113,2948,1055],{"class":150},[113,2950,2871],{"class":463},[113,2952,2953],{"class":139}," >",[113,2955,2896],{"class":463},[113,2957,333],{"class":150},[113,2959,2960],{"class":115,"line":266},[113,2961,133],{"emptyLinePlaceholder":132},[113,2963,2964],{"class":115,"line":275},[113,2965,2966],{"class":119},"-- ✅ Alias IS visible in ORDER BY — ORDER BY runs after SELECT, so the alias exists.\n",[113,2968,2969,2971,2973,2975,2977,2979,2981,2983],{"class":115,"line":281},[113,2970,2246],{"class":139},[113,2972,2861],{"class":150},[113,2974,936],{"class":139},[113,2976,2866],{"class":463},[113,2978,1055],{"class":150},[113,2980,2871],{"class":463},[113,2982,2874],{"class":139},[113,2984,2877],{"class":150},[113,2986,2987,2989],{"class":115,"line":286},[113,2988,2882],{"class":139},[113,2990,1128],{"class":150},[113,2992,2993,2995,2997,2999],{"class":115,"line":292},[113,2994,2828],{"class":139},[113,2996,2891],{"class":150},[113,2998,606],{"class":139},[113,3000,333],{"class":150},[94,3002,3004],{"id":3003},"explain-seeing-the-real-plan","EXPLAIN: Seeing the Real Plan",[99,3006,3007],{"language":101},[104,3008,3010],{"className":106,"code":3009,"language":101,"meta":108,"style":108},"EXPLAIN SELECT c.name, o.id, o.amount\nFROM customers c\nJOIN orders o ON c.id = o.customer_id\nWHERE o.amount > 50;\n",[110,3011,3012,3045,3052,3079],{"__ignoreMap":108},[113,3013,3014,3017,3019,3022,3024,3026,3028,3031,3033,3036,3038,3040,3042],{"class":115,"line":116},[113,3015,3016],{"class":150},"EXPLAIN ",[113,3018,2246],{"class":139},[113,3020,3021],{"class":463}," c",[113,3023,1055],{"class":150},[113,3025,2500],{"class":463},[113,3027,467],{"class":150},[113,3029,3030],{"class":463},"o",[113,3032,1055],{"class":150},[113,3034,3035],{"class":463},"id",[113,3037,467],{"class":150},[113,3039,3030],{"class":463},[113,3041,1055],{"class":150},[113,3043,3044],{"class":463},"amount\n",[113,3046,3047,3049],{"class":115,"line":123},[113,3048,2882],{"class":139},[113,3050,3051],{"class":150}," customers c\n",[113,3053,3054,3057,3060,3063,3065,3067,3069,3071,3074,3076],{"class":115,"line":129},[113,3055,3056],{"class":139},"JOIN",[113,3058,3059],{"class":150}," orders o ",[113,3061,3062],{"class":139},"ON",[113,3064,3021],{"class":463},[113,3066,1055],{"class":150},[113,3068,3035],{"class":463},[113,3070,648],{"class":139},[113,3072,3073],{"class":463}," o",[113,3075,1055],{"class":150},[113,3077,3078],{"class":463},"customer_id\n",[113,3080,3081,3083,3085,3087,3090,3092,3095],{"class":115,"line":136},[113,3082,2811],{"class":139},[113,3084,3073],{"class":463},[113,3086,1055],{"class":150},[113,3088,3089],{"class":463},"amount",[113,3091,2953],{"class":139},[113,3093,3094],{"class":463}," 50",[113,3096,333],{"class":150},[99,3098,3099],{"language":2798},[104,3100,3103],{"className":3101,"code":3102,"language":2798,"meta":108},[2802]," Hash Join  (cost=38.05..60.15 rows=2 width=46)\n   Hash Cond: (c.id = o.customer_id)\n   ->  Seq Scan on customers c  (cost=0.00..22.00 rows=1200 width=42)\n   ->  Hash  (cost=37.75..37.75 rows=24 width=12)\n         ->  Seq Scan on orders o  (cost=0.00..37.75 rows=24 width=12)\n               Filter: (amount > 50)\n",[110,3104,3102],{"__ignoreMap":108},[19,3106,3107,3108,3110,3111,3114,3115,3117,3118,3121,3122,3125],{},"The plan reads inside-out: ",[110,3109,1154],{}," is seq-scanned and filtered (",[110,3112,3113],{},"amount > 50","), then a hash table is built from the filtered rows, then ",[110,3116,1162],{}," is seq-scanned and hash-joined. The ",[110,3119,3120],{},"cost"," is the planner's estimate (arbitrary units, not milliseconds). Use ",[110,3123,3124],{},"EXPLAIN ANALYZE"," to get actual execution times and compare estimates vs reality.",[99,3127,3128],{"language":101},[104,3129,3131],{"className":106,"code":3130,"language":101,"meta":108,"style":108},"-- EXPLAIN ANALYZE actually executes the query and shows real timings.\n-- Useful for finding where the planner's estimates diverge from reality.\nEXPLAIN ANALYZE SELECT c.name, o.id, o.amount\nFROM customers c\nJOIN orders o ON c.id = o.customer_id\nWHERE o.amount > 50;\n",[110,3132,3133,3138,3143,3172,3178,3200],{"__ignoreMap":108},[113,3134,3135],{"class":115,"line":116},[113,3136,3137],{"class":119},"-- EXPLAIN ANALYZE actually executes the query and shows real timings.\n",[113,3139,3140],{"class":115,"line":123},[113,3141,3142],{"class":119},"-- Useful for finding where the planner's estimates diverge from reality.\n",[113,3144,3145,3148,3150,3152,3154,3156,3158,3160,3162,3164,3166,3168,3170],{"class":115,"line":129},[113,3146,3147],{"class":150},"EXPLAIN ANALYZE ",[113,3149,2246],{"class":139},[113,3151,3021],{"class":463},[113,3153,1055],{"class":150},[113,3155,2500],{"class":463},[113,3157,467],{"class":150},[113,3159,3030],{"class":463},[113,3161,1055],{"class":150},[113,3163,3035],{"class":463},[113,3165,467],{"class":150},[113,3167,3030],{"class":463},[113,3169,1055],{"class":150},[113,3171,3044],{"class":463},[113,3173,3174,3176],{"class":115,"line":136},[113,3175,2882],{"class":139},[113,3177,3051],{"class":150},[113,3179,3180,3182,3184,3186,3188,3190,3192,3194,3196,3198],{"class":115,"line":154},[113,3181,3056],{"class":139},[113,3183,3059],{"class":150},[113,3185,3062],{"class":139},[113,3187,3021],{"class":463},[113,3189,1055],{"class":150},[113,3191,3035],{"class":463},[113,3193,648],{"class":139},[113,3195,3073],{"class":463},[113,3197,1055],{"class":150},[113,3199,3078],{"class":463},[113,3201,3202,3204,3206,3208,3210,3212,3214],{"class":115,"line":160},[113,3203,2811],{"class":139},[113,3205,3073],{"class":463},[113,3207,1055],{"class":150},[113,3209,3089],{"class":463},[113,3211,2953],{"class":139},[113,3213,3094],{"class":463},[113,3215,333],{"class":150},[14,3217,3219],{"id":3218},"tips-tricks","💡 Tips & Tricks",[28,3221,3222,3238,3250,3264,3284],{},[31,3223,3224,3227,3228,3230,3231,3233,3234,3237],{},[23,3225,3226],{},"Idiom",": always run ",[110,3229,1391],{}," (or ",[110,3232,3124],{},") on a slow query ",[38,3235,3236],{},"before"," adding an index — the plan tells you whether an index would even help, and adding indexes that aren't used just slows down writes.",[31,3239,3240,3243,3244,3246,3247,3249],{},[23,3241,3242],{},"Debug",": in ",[110,3245,1500],{},", prefix a query with ",[110,3248,1881],{}," (expanded display) when a row is too wide to read in horizontal mode — the vertical layout puts each column on its own line and makes inspecting a single wide row trivial.",[31,3251,3252,3254,3255,3257,3258,3260,3261,3263],{},[23,3253,3242],{},": use ",[110,3256,1921],{}," in ",[110,3259,1500],{}," to edit your last query in ",[110,3262,1927],{}," (vim\u002Fnano\u002FVS Code) — far faster than re-typing a multi-line query that had a typo, and the edited buffer re-executes on save\u002Fquit.",[31,3265,3266,3269,3270,3273,3274,3277,3278,3280,3281,3283],{},[23,3267,3268],{},"Portability",": prefer ",[110,3271,3272],{},"CURRENT_TIMESTAMP"," over ",[110,3275,3276],{},"now()"," in portable SQL — ",[110,3279,3276],{}," is PostgreSQL-specific (though widely supported); ",[110,3282,3272],{}," is ANSI standard.",[31,3285,3286,3289,3290,3293,3294,3297,3298,3301],{},[23,3287,3288],{},"Performance",": ",[110,3291,3292],{},"psql -c \"SELECT count(*) FROM big_table\""," can take minutes on a huge table because PostgreSQL's MVCC forces a full scan for ",[110,3295,3296],{},"count(*)"," — use an approximate count from ",[110,3299,3300],{},"pg_class.reltuples"," for a quick estimate:",[99,3303,3304],{"language":101},[104,3305,3307],{"className":106,"code":3306,"language":101,"meta":108,"style":108},"-- Approximate row count — reads from the catalog, no table scan.\n-- reltuples is updated by ANALYZE\u002FVACUUM, so it may be slightly stale.\nSELECT reltuples::bigint AS approx_rows\nFROM pg_class\nWHERE relname = 'big_table';\n",[110,3308,3309,3314,3319,3334,3341],{"__ignoreMap":108},[113,3310,3311],{"class":115,"line":116},[113,3312,3313],{"class":119},"-- Approximate row count — reads from the catalog, no table scan.\n",[113,3315,3316],{"class":115,"line":123},[113,3317,3318],{"class":119},"-- reltuples is updated by ANALYZE\u002FVACUUM, so it may be slightly stale.\n",[113,3320,3321,3323,3326,3329,3331],{"class":115,"line":129},[113,3322,2246],{"class":139},[113,3324,3325],{"class":150}," reltuples::",[113,3327,3328],{"class":139},"bigint",[113,3330,2874],{"class":139},[113,3332,3333],{"class":150}," approx_rows\n",[113,3335,3336,3338],{"class":115,"line":136},[113,3337,2882],{"class":139},[113,3339,3340],{"class":150}," pg_class\n",[113,3342,3343,3345,3348,3351,3354],{"class":115,"line":154},[113,3344,2811],{"class":139},[113,3346,3347],{"class":150}," relname ",[113,3349,3350],{"class":139},"=",[113,3352,3353],{"class":220}," 'big_table'",[113,3355,333],{"class":150},[14,3357,3359],{"id":3358},"️-edge-cases-gotchas","⚠️ Edge Cases & Gotchas",[28,3361,3362,3388,3405,3414,3448,3465,3481],{},[31,3363,3364,3367,3368,3371,3372,3375,3376,3379,3380,3383,3384,3387],{},[23,3365,3366],{},"Case sensitivity",": unquoted identifiers in PostgreSQL are folded to lowercase. ",[110,3369,3370],{},"CREATE TABLE Users (...)"," creates a table named ",[110,3373,3374],{},"users","; ",[110,3377,3378],{},"SELECT * FROM Users"," works. But ",[110,3381,3382],{},"CREATE TABLE \"Users\" (...)"," (quoted) creates a literally-cased table that you must ",[38,3385,3386],{},"always"," quote — a common source of \"table does not exist\" errors. Avoid quoted identifiers unless you have a specific reason.",[31,3389,3390,3289,3395,3397,3398,3401,3402,3404],{},[23,3391,3392,3394],{},[110,3393,1500],{}," semicolons",[110,3396,1500],{}," buffers input until it sees a ",[110,3399,3400],{},";"," (or a backslash command). If nothing happens when you press Enter, you forgot the semicolon — type ",[110,3403,3400],{}," and press Enter to run the buffered statement.",[31,3406,3407,3410,3411,3413],{},[23,3408,3409],{},"SQLite type affinity",": SQLite doesn't strictly enforce column types — it uses \"type affinity\" and will happily store a string in an ",[110,3412,756],{}," column. This masks bugs that PostgreSQL would catch. Use PostgreSQL for learning schema discipline.",[31,3415,3416,3289,3419,467,3422,467,3425,467,3428,467,3430,3433,3434,3436,3437,3439,3440,467,3442,467,3444,3447],{},[23,3417,3418],{},"Keyword conflicts",[110,3420,3421],{},"order",[110,3423,3424],{},"user",[110,3426,3427],{},"group",[110,3429,1176],{},[110,3431,3432],{},"select"," are reserved words. Naming a table ",[110,3435,3421],{}," or ",[110,3438,3424],{}," forces you to quote it forever. Use ",[110,3441,1154],{},[110,3443,3374],{},[110,3445,3446],{},"groups"," instead.",[31,3449,3450,3455,3456,3459,3460,1296,3462,3464],{},[23,3451,3452,3454],{},[110,3453,2173],{}," gaps",": if a transaction inserts a row and then rolls back, the sequence value is ",[38,3457,3458],{},"not"," returned to the pool — gaps in ",[110,3461,2173],{},[110,3463,1333],{}," columns are expected and normal, not a bug. Sequences live outside transaction semantics for performance.",[31,3466,3467,3289,3470,3473,3474,3436,3477,3480],{},[23,3468,3469],{},"Connection string security",[110,3471,3472],{},"psql \"postgresql:\u002F\u002Fuser:pass@host:5432\u002Fdbname\""," — the password in the URL is visible in shell history and process lists. Prefer ",[110,3475,3476],{},"~\u002F.pgpass",[110,3478,3479],{},"PGPASSWORD"," env var.",[31,3482,3483,3289,3489,3491,3492,1296,3494,3496,3497,3499],{},[23,3484,3485,3488],{},[110,3486,3487],{},"CREATE DATABASE"," in transactions",[110,3490,3487],{}," cannot run inside a transaction block, so you can't put it in a ",[110,3493,2736],{},[110,3495,2739],{}," script. Use the ",[110,3498,2307],{}," shell command instead, or run it standalone.",[14,3501,3503],{"id":3502},"spot-the-bug","🧠 Spot the Bug",[19,3505,3506,3507,3509],{},"A developer writes this and is surprised the alias doesn't work in the ",[110,3508,2811],{}," clause:",[99,3511,3512],{"language":101},[104,3513,3515],{"className":106,"code":3514,"language":101,"meta":108,"style":108},"SELECT\n  customer_id,\n  SUM(amount) AS total_spent\nFROM orders\nWHERE total_spent > 100\nGROUP BY customer_id;\n",[110,3516,3517,3522,3527,3540,3546,3558],{"__ignoreMap":108},[113,3518,3519],{"class":115,"line":116},[113,3520,3521],{"class":139},"SELECT\n",[113,3523,3524],{"class":115,"line":123},[113,3525,3526],{"class":150},"  customer_id,\n",[113,3528,3529,3532,3535,3537],{"class":115,"line":129},[113,3530,3531],{"class":463},"  SUM",[113,3533,3534],{"class":150},"(amount) ",[113,3536,1037],{"class":139},[113,3538,3539],{"class":150}," total_spent\n",[113,3541,3542,3544],{"class":115,"line":136},[113,3543,2882],{"class":139},[113,3545,1128],{"class":150},[113,3547,3548,3550,3553,3555],{"class":115,"line":154},[113,3549,2811],{"class":139},[113,3551,3552],{"class":150}," total_spent ",[113,3554,743],{"class":139},[113,3556,3557],{"class":463}," 100\n",[113,3559,3560,3563],{"class":115,"line":160},[113,3561,3562],{"class":139},"GROUP BY",[113,3564,3565],{"class":150}," customer_id;\n",[19,3567,3568,3569,3572],{},"What's wrong, and what are the ",[23,3570,3571],{},"two"," distinct bugs here?",[3574,3575,3576,3580,3606,3635],"details",{},[3577,3578,3579],"summary",{},"Answer",[19,3581,3582,3588,3589,3592,3593,3596,3597,3599,3600,3602,3603,1055],{},[23,3583,3584,3585,3587],{},"Bug 1 — Alias not visible in ",[110,3586,2811],{},":"," The logical evaluation order is ",[110,3590,3591],{},"FROM → WHERE → GROUP BY → HAVING → SELECT",". The alias ",[110,3594,3595],{},"total_spent"," is created in ",[110,3598,2246],{}," (step 5), but ",[110,3601,2811],{}," runs at step 2 — before the alias exists. The planner reports ",[110,3604,3605],{},"column \"total_spent\" does not exist",[19,3607,3608,3613,3614,3616,3617,3620,3621,3623,3624,3626,3627,3630,3631,1155,3633,2832],{},[23,3609,3610,3611,3587],{},"Bug 2 — Aggregate in ",[110,3612,2811],{}," Even if the alias were somehow available, ",[110,3615,2811],{}," cannot reference aggregate functions like ",[110,3618,3619],{},"SUM()",". Aggregates aren't computed until ",[110,3622,3562],{}," runs (step 3), and ",[110,3625,2811],{}," runs before grouping. The correct clause for filtering on aggregates is ",[110,3628,3629],{},"HAVING"," (which runs ",[38,3632,2818],{},[110,3634,3562],{},[99,3636,3637],{"language":101},[104,3638,3640],{"className":106,"code":3639,"language":101,"meta":108,"style":108},"-- ✅ Fix: use HAVING to filter on the aggregate (runs after GROUP BY)\nSELECT\n  customer_id,\n  SUM(amount) AS total_spent\nFROM orders\nGROUP BY customer_id\nHAVING SUM(amount) > 100;            -- reference the raw aggregate, not the alias (portability)\n",[110,3641,3642,3647,3651,3655,3665,3671,3678],{"__ignoreMap":108},[113,3643,3644],{"class":115,"line":116},[113,3645,3646],{"class":119},"-- ✅ Fix: use HAVING to filter on the aggregate (runs after GROUP BY)\n",[113,3648,3649],{"class":115,"line":123},[113,3650,3521],{"class":139},[113,3652,3653],{"class":115,"line":129},[113,3654,3526],{"class":150},[113,3656,3657,3659,3661,3663],{"class":115,"line":136},[113,3658,3531],{"class":463},[113,3660,3534],{"class":150},[113,3662,1037],{"class":139},[113,3664,3539],{"class":150},[113,3666,3667,3669],{"class":115,"line":154},[113,3668,2882],{"class":139},[113,3670,1128],{"class":150},[113,3672,3673,3675],{"class":115,"line":160},[113,3674,3562],{"class":139},[113,3676,3677],{"class":150}," customer_id\n",[113,3679,3680,3682,3685,3687,3689,3691,3694],{"class":115,"line":166},[113,3681,3629],{"class":139},[113,3683,3684],{"class":463}," SUM",[113,3686,3534],{"class":150},[113,3688,743],{"class":139},[113,3690,2896],{"class":463},[113,3692,3693],{"class":150},";            ",[113,3695,3696],{"class":119},"-- reference the raw aggregate, not the alias (portability)\n",[14,3698,3699],{"id":3577},"Summary",[19,3701,3702,3703,3705,3706,3708,3709,3711,3712,1055],{},"You now have a production-grade schema with constraints and indexes, know how to inspect the query plan with ",[110,3704,1391],{},", understand the logical evaluation order that governs alias visibility, and can navigate the ",[110,3707,1500],{}," CLI efficiently. Next: ",[110,3710,2246],{}," basics and row filtering with ",[110,3713,2811],{},[3715,3716,3717],"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 .sIsaT, html code.shiki .sIsaT{--shiki-default:#6F42C1;--shiki-github-dark:#B392F0}html pre.shiki code .ssxIu, html code.shiki .ssxIu{--shiki-default:#24292E;--shiki-github-dark:#E1E4E8}html pre.shiki code .sJ6F3, html code.shiki .sJ6F3{--shiki-default:#032F62;--shiki-github-dark:#9ECBFF}html pre.shiki code .snvgF, html code.shiki .snvgF{--shiki-default:#005CC5;--shiki-github-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .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);}",{"title":108,"searchDepth":123,"depth":123,"links":3719},[3720,3721,3724,3725,3731,3733,3734,3735,3736,3740,3741,3742,3743],{"id":16,"depth":123,"text":17},{"id":72,"depth":123,"text":73,"children":3722},[3723],{"id":96,"depth":129,"text":97},{"id":1170,"depth":123,"text":1171},{"id":1448,"depth":123,"text":1449,"children":3726},[3727,3728,3729,3730],{"id":1452,"depth":129,"text":1453},{"id":1509,"depth":129,"text":1510},{"id":1572,"depth":129,"text":1573},{"id":1675,"depth":129,"text":1196},{"id":1761,"depth":123,"text":3732},"The psql Client",{"id":2102,"depth":123,"text":2103},{"id":2273,"depth":123,"text":2274},{"id":2639,"depth":123,"text":2640},{"id":2756,"depth":123,"text":2757,"children":3737},[3738,3739],{"id":2784,"depth":129,"text":2785},{"id":3003,"depth":129,"text":3004},{"id":3218,"depth":123,"text":3219},{"id":3358,"depth":123,"text":3359},{"id":3502,"depth":123,"text":3503},{"id":3577,"depth":123,"text":3699},"md",{},"\u002Fsql\u002F01-introduction-and-setup",{"title":5,"description":108},"sql\u002F01-introduction-and-setup","GM5KUCNjblwDOsLGwqC_mJd4yg_i4Gqa4FNqNO1OhDs",1789924654229]