[{"data":1,"prerenderedAt":838},["ShallowReactive",2],{"blog-sql-formatting-best-practices":3},{"id":4,"title":5,"body":6,"category":826,"date":827,"description":828,"extension":829,"meta":830,"navigation":831,"path":832,"readingTime":80,"seo":833,"stem":834,"tool":835,"updated":836,"__hash__":837},"blog\u002Fblog\u002Fsql-formatting-best-practices.md","SQL Formatting Best Practices",{"type":7,"value":8,"toc":812},"minimark",[9,13,16,21,24,41,44,120,123,139,143,172,217,221,230,261,265,268,277,280,284,287,332,336,339,369,372,376,379,515,519,522,565,569,576,600,606,630,633,637,640,768,771,775,796,800,808],[10,11,12],"p",{},"Well-formatted SQL is the difference between a query you can debug in 30 seconds and one that takes 30 minutes. When a query fails at 2 AM, formatting is not cosmetic — it is operational. Yet SQL formatting is inconsistent across teams, with debates about uppercase keywords, indent depth, and comma placement that never resolve.",[10,14,15],{},"This guide establishes practical formatting rules with reasoning, so you can pick a style, enforce it, and move on.",[17,18,20],"h2",{"id":19},"why-format-sql","Why Format SQL?",[10,22,23],{},"Unformatted SQL is common in production:",[25,26,31],"pre",{"className":27,"code":28,"language":29,"meta":30,"style":30},"language-sql shiki shiki-themes github-light github-dark","SELECT u.id, u.name, u.email, o.total, o.created_at FROM users u JOIN orders o ON u.id = o.user_id WHERE o.status = 'completed' AND o.total > 100 ORDER BY o.created_at DESC LIMIT 20;\n","sql","",[32,33,34],"code",{"__ignoreMap":30},[35,36,39],"span",{"class":37,"line":38},"line",1,[35,40,28],{},[10,42,43],{},"The same query, formatted:",[25,45,47],{"className":27,"code":46,"language":29,"meta":30,"style":30},"SELECT\n  u.id,\n  u.name,\n  u.email,\n  o.total,\n  o.created_at\nFROM users u\nJOIN orders o ON u.id = o.user_id\nWHERE o.status = 'completed'\n  AND o.total > 100\nORDER BY o.created_at DESC\nLIMIT 20;\n",[32,48,49,54,60,66,72,78,84,90,96,102,108,114],{"__ignoreMap":30},[35,50,51],{"class":37,"line":38},[35,52,53],{},"SELECT\n",[35,55,57],{"class":37,"line":56},2,[35,58,59],{},"  u.id,\n",[35,61,63],{"class":37,"line":62},3,[35,64,65],{},"  u.name,\n",[35,67,69],{"class":37,"line":68},4,[35,70,71],{},"  u.email,\n",[35,73,75],{"class":37,"line":74},5,[35,76,77],{},"  o.total,\n",[35,79,81],{"class":37,"line":80},6,[35,82,83],{},"  o.created_at\n",[35,85,87],{"class":37,"line":86},7,[35,88,89],{},"FROM users u\n",[35,91,93],{"class":37,"line":92},8,[35,94,95],{},"JOIN orders o ON u.id = o.user_id\n",[35,97,99],{"class":37,"line":98},9,[35,100,101],{},"WHERE o.status = 'completed'\n",[35,103,105],{"class":37,"line":104},10,[35,106,107],{},"  AND o.total > 100\n",[35,109,111],{"class":37,"line":110},11,[35,112,113],{},"ORDER BY o.created_at DESC\n",[35,115,117],{"class":37,"line":116},12,[35,118,119],{},"LIMIT 20;\n",[10,121,122],{},"Formatted SQL makes it obvious:",[124,125,126,130,133,136],"ul",{},[127,128,129],"li",{},"Which columns are selected",[127,131,132],{},"Which tables are joined and how",[127,134,135],{},"What filters are applied",[127,137,138],{},"How results are ordered",[17,140,142],{"id":141},"rule-1-one-clause-per-line","Rule 1: One Clause Per Line",[10,144,145,146,149,150,149,153,149,156,149,159,149,162,149,165,149,168,171],{},"Each major clause (",[32,147,148],{},"SELECT",", ",[32,151,152],{},"FROM",[32,154,155],{},"WHERE",[32,157,158],{},"JOIN",[32,160,161],{},"ORDER BY",[32,163,164],{},"GROUP BY",[32,166,167],{},"HAVING",[32,169,170],{},"LIMIT",") starts on its own line:",[25,173,175],{"className":27,"code":174,"language":29,"meta":30,"style":30},"SELECT count(*) AS total\nFROM orders\nWHERE created_at >= '2024-01-01'\n  AND status = 'completed'\nGROUP BY customer_id\nHAVING count(*) > 5\nORDER BY total DESC\nLIMIT 10;\n",[32,176,177,182,187,192,197,202,207,212],{"__ignoreMap":30},[35,178,179],{"class":37,"line":38},[35,180,181],{},"SELECT count(*) AS total\n",[35,183,184],{"class":37,"line":56},[35,185,186],{},"FROM orders\n",[35,188,189],{"class":37,"line":62},[35,190,191],{},"WHERE created_at >= '2024-01-01'\n",[35,193,194],{"class":37,"line":68},[35,195,196],{},"  AND status = 'completed'\n",[35,198,199],{"class":37,"line":74},[35,200,201],{},"GROUP BY customer_id\n",[35,203,204],{"class":37,"line":80},[35,205,206],{},"HAVING count(*) > 5\n",[35,208,209],{"class":37,"line":86},[35,210,211],{},"ORDER BY total DESC\n",[35,213,214],{"class":37,"line":92},[35,215,216],{},"LIMIT 10;\n",[17,218,220],{"id":219},"rule-2-indent-continuation-lines","Rule 2: Indent Continuation Lines",[10,222,223,224,226,227,229],{},"Columns in ",[32,225,148],{},", conditions in ",[32,228,155],{},", and join conditions continue with consistent indentation (2 or 4 spaces):",[25,231,233],{"className":27,"code":232,"language":29,"meta":30,"style":30},"SELECT\n  u.name,\n  u.email,\n  count(o.id) AS order_count,\n  sum(o.total) AS lifetime_value\nFROM users u\n",[32,234,235,239,243,247,252,257],{"__ignoreMap":30},[35,236,237],{"class":37,"line":38},[35,238,53],{},[35,240,241],{"class":37,"line":56},[35,242,65],{},[35,244,245],{"class":37,"line":62},[35,246,71],{},[35,248,249],{"class":37,"line":68},[35,250,251],{},"  count(o.id) AS order_count,\n",[35,253,254],{"class":37,"line":74},[35,255,256],{},"  sum(o.total) AS lifetime_value\n",[35,258,259],{"class":37,"line":80},[35,260,89],{},[17,262,264],{"id":263},"rule-3-uppercase-keywords","Rule 3: Uppercase Keywords",[10,266,267],{},"SQL keywords in UPPERCASE distinguish them from identifiers:",[25,269,271],{"className":27,"code":270,"language":29,"meta":30,"style":30},"SELECT name FROM users WHERE id = 42 ORDER BY created_at DESC;\n",[32,272,273],{"__ignoreMap":30},[35,274,275],{"class":37,"line":38},[35,276,270],{},[10,278,279],{},"Not everyone agrees on this — lowercase keywords are more readable to some developers, and syntax highlighting makes case irrelevant in most editors. Pick one style for your team and enforce it.",[17,281,283],{"id":282},"rule-4-one-column-per-line-in-select","Rule 4: One Column Per Line in SELECT",[10,285,286],{},"For more than 2-3 columns, put each on its own line:",[25,288,290],{"className":27,"code":289,"language":29,"meta":30,"style":30},"SELECT\n  u.id,\n  u.name,\n  u.email,\n  u.created_at,\n  count(o.id) AS order_count\nFROM users u\nLEFT JOIN orders o ON u.id = o.user_id\nGROUP BY u.id, u.name, u.email, u.created_at;\n",[32,291,292,296,300,304,308,313,318,322,327],{"__ignoreMap":30},[35,293,294],{"class":37,"line":38},[35,295,53],{},[35,297,298],{"class":37,"line":56},[35,299,59],{},[35,301,302],{"class":37,"line":62},[35,303,65],{},[35,305,306],{"class":37,"line":68},[35,307,71],{},[35,309,310],{"class":37,"line":74},[35,311,312],{},"  u.created_at,\n",[35,314,315],{"class":37,"line":80},[35,316,317],{},"  count(o.id) AS order_count\n",[35,319,320],{"class":37,"line":86},[35,321,89],{},[35,323,324],{"class":37,"line":92},[35,325,326],{},"LEFT JOIN orders o ON u.id = o.user_id\n",[35,328,329],{"class":37,"line":98},[35,330,331],{},"GROUP BY u.id, u.name, u.email, u.created_at;\n",[17,333,335],{"id":334},"rule-5-align-joins","Rule 5: Align JOINs",[10,337,338],{},"Each JOIN gets its own line with the ON condition:",[25,340,342],{"className":27,"code":341,"language":29,"meta":30,"style":30},"FROM orders o\nJOIN users u ON u.id = o.user_id\nJOIN products p ON p.id = o.product_id\nLEFT JOIN reviews r ON r.order_id = o.id\n  AND r.deleted_at IS NULL;\n",[32,343,344,349,354,359,364],{"__ignoreMap":30},[35,345,346],{"class":37,"line":38},[35,347,348],{},"FROM orders o\n",[35,350,351],{"class":37,"line":56},[35,352,353],{},"JOIN users u ON u.id = o.user_id\n",[35,355,356],{"class":37,"line":62},[35,357,358],{},"JOIN products p ON p.id = o.product_id\n",[35,360,361],{"class":37,"line":68},[35,362,363],{},"LEFT JOIN reviews r ON r.order_id = o.id\n",[35,365,366],{"class":37,"line":74},[35,367,368],{},"  AND r.deleted_at IS NULL;\n",[10,370,371],{},"Multi-condition joins indent the additional conditions.",[17,373,375],{"id":374},"rule-6-format-ctes-clearly","Rule 6: Format CTEs Clearly",[10,377,378],{},"Common Table Expressions (CTEs) are the modern way to structure complex queries. Format each CTE as a named block:",[25,380,382],{"className":27,"code":381,"language":29,"meta":30,"style":30},"WITH monthly_revenue AS (\n  SELECT\n    date_trunc('month', created_at) AS month,\n    sum(total) AS revenue\n  FROM orders\n  WHERE status = 'completed'\n  GROUP BY 1\n),\ngrowth AS (\n  SELECT\n    month,\n    revenue,\n    lag(revenue) OVER (ORDER BY month) AS prev_revenue,\n    round(\n      (revenue - lag(revenue) OVER (ORDER BY month))\n      \u002F lag(revenue) OVER (ORDER BY month) * 100,\n      1\n    ) AS growth_pct\n  FROM monthly_revenue\n)\nSELECT *\nFROM growth\nWHERE growth_pct IS NOT NULL\nORDER BY month;\n",[32,383,384,389,394,399,404,409,414,419,424,429,433,438,443,449,455,461,467,473,479,485,491,497,503,509],{"__ignoreMap":30},[35,385,386],{"class":37,"line":38},[35,387,388],{},"WITH monthly_revenue AS (\n",[35,390,391],{"class":37,"line":56},[35,392,393],{},"  SELECT\n",[35,395,396],{"class":37,"line":62},[35,397,398],{},"    date_trunc('month', created_at) AS month,\n",[35,400,401],{"class":37,"line":68},[35,402,403],{},"    sum(total) AS revenue\n",[35,405,406],{"class":37,"line":74},[35,407,408],{},"  FROM orders\n",[35,410,411],{"class":37,"line":80},[35,412,413],{},"  WHERE status = 'completed'\n",[35,415,416],{"class":37,"line":86},[35,417,418],{},"  GROUP BY 1\n",[35,420,421],{"class":37,"line":92},[35,422,423],{},"),\n",[35,425,426],{"class":37,"line":98},[35,427,428],{},"growth AS (\n",[35,430,431],{"class":37,"line":104},[35,432,393],{},[35,434,435],{"class":37,"line":110},[35,436,437],{},"    month,\n",[35,439,440],{"class":37,"line":116},[35,441,442],{},"    revenue,\n",[35,444,446],{"class":37,"line":445},13,[35,447,448],{},"    lag(revenue) OVER (ORDER BY month) AS prev_revenue,\n",[35,450,452],{"class":37,"line":451},14,[35,453,454],{},"    round(\n",[35,456,458],{"class":37,"line":457},15,[35,459,460],{},"      (revenue - lag(revenue) OVER (ORDER BY month))\n",[35,462,464],{"class":37,"line":463},16,[35,465,466],{},"      \u002F lag(revenue) OVER (ORDER BY month) * 100,\n",[35,468,470],{"class":37,"line":469},17,[35,471,472],{},"      1\n",[35,474,476],{"class":37,"line":475},18,[35,477,478],{},"    ) AS growth_pct\n",[35,480,482],{"class":37,"line":481},19,[35,483,484],{},"  FROM monthly_revenue\n",[35,486,488],{"class":37,"line":487},20,[35,489,490],{},")\n",[35,492,494],{"class":37,"line":493},21,[35,495,496],{},"SELECT *\n",[35,498,500],{"class":37,"line":499},22,[35,501,502],{},"FROM growth\n",[35,504,506],{"class":37,"line":505},23,[35,507,508],{},"WHERE growth_pct IS NOT NULL\n",[35,510,512],{"class":37,"line":511},24,[35,513,514],{},"ORDER BY month;\n",[17,516,518],{"id":517},"rule-7-subqueries-get-full-formatting","Rule 7: Subqueries Get Full Formatting",[10,520,521],{},"Inline subqueries are formatted with the same rules, indented one level:",[25,523,525],{"className":27,"code":524,"language":29,"meta":30,"style":30},"SELECT *\nFROM users\nWHERE id IN (\n  SELECT user_id\n  FROM orders\n  WHERE total > 1000\n    AND created_at >= '2024-01-01'\n);\n",[32,526,527,531,536,541,546,550,555,560],{"__ignoreMap":30},[35,528,529],{"class":37,"line":38},[35,530,496],{},[35,532,533],{"class":37,"line":56},[35,534,535],{},"FROM users\n",[35,537,538],{"class":37,"line":62},[35,539,540],{},"WHERE id IN (\n",[35,542,543],{"class":37,"line":68},[35,544,545],{},"  SELECT user_id\n",[35,547,548],{"class":37,"line":74},[35,549,408],{},[35,551,552],{"class":37,"line":80},[35,553,554],{},"  WHERE total > 1000\n",[35,556,557],{"class":37,"line":86},[35,558,559],{},"    AND created_at >= '2024-01-01'\n",[35,561,562],{"class":37,"line":92},[35,563,564],{},");\n",[17,566,568],{"id":567},"comma-placement-leading-vs-trailing","Comma Placement: Leading vs Trailing",[10,570,571,575],{},[572,573,574],"strong",{},"Trailing commas"," (more common):",[25,577,579],{"className":27,"code":578,"language":29,"meta":30,"style":30},"SELECT\n  name,\n  email,\n  created_at\n",[32,580,581,585,590,595],{"__ignoreMap":30},[35,582,583],{"class":37,"line":38},[35,584,53],{},[35,586,587],{"class":37,"line":56},[35,588,589],{},"  name,\n",[35,591,592],{"class":37,"line":62},[35,593,594],{},"  email,\n",[35,596,597],{"class":37,"line":68},[35,598,599],{},"  created_at\n",[10,601,602,605],{},[572,603,604],{},"Leading commas"," (easier to comment out lines):",[25,607,609],{"className":27,"code":608,"language":29,"meta":30,"style":30},"SELECT\n  name\n  , email\n  , created_at\n",[32,610,611,615,620,625],{"__ignoreMap":30},[35,612,613],{"class":37,"line":38},[35,614,53],{},[35,616,617],{"class":37,"line":56},[35,618,619],{},"  name\n",[35,621,622],{"class":37,"line":62},[35,623,624],{},"  , email\n",[35,626,627],{"class":37,"line":68},[35,628,629],{},"  , created_at\n",[10,631,632],{},"Both are valid. Pick one for your team. Most formatters default to trailing commas.",[17,634,636],{"id":635},"dialect-differences","Dialect Differences",[10,638,639],{},"Different databases have different keywords, functions, and syntax:",[641,642,643,665],"table",{},[644,645,646],"thead",{},[647,648,649,653,656,659,662],"tr",{},[650,651,652],"th",{},"Feature",[650,654,655],{},"PostgreSQL",[650,657,658],{},"MySQL",[650,660,661],{},"SQL Server",[650,663,664],{},"SQLite",[666,667,668,693,722,745],"tbody",{},[647,669,670,674,679,684,689],{},[671,672,673],"td",{},"String concat",[671,675,676],{},[32,677,678],{},"||",[671,680,681],{},[32,682,683],{},"CONCAT()",[671,685,686],{},[32,687,688],{},"+",[671,690,691],{},[32,692,678],{},[647,694,695,698,707,712,717],{},[671,696,697],{},"Auto increment",[671,699,700,703,704],{},[32,701,702],{},"SERIAL"," \u002F ",[32,705,706],{},"GENERATED",[671,708,709],{},[32,710,711],{},"AUTO_INCREMENT",[671,713,714],{},[32,715,716],{},"IDENTITY",[671,718,719],{},[32,720,721],{},"AUTOINCREMENT",[647,723,724,727,732,736,741],{},[671,725,726],{},"Limit",[671,728,729],{},[32,730,731],{},"LIMIT n",[671,733,734],{},[32,735,731],{},[671,737,738],{},[32,739,740],{},"TOP n",[671,742,743],{},[32,744,731],{},[647,746,747,750,755,760,764],{},[671,748,749],{},"Boolean",[671,751,752],{},[32,753,754],{},"TRUE\u002FFALSE",[671,756,757],{},[32,758,759],{},"1\u002F0",[671,761,762],{},[32,763,759],{},[671,765,766],{},[32,767,759],{},[10,769,770],{},"A good formatter supports multiple dialects and adjusts keyword recognition accordingly.",[17,772,774],{"id":773},"enforcing-consistency","Enforcing Consistency",[776,777,778,784,790],"ol",{},[127,779,780,783],{},[572,781,782],{},"Use an automated formatter"," — manual formatting drifts. Run a formatter in CI or as a pre-commit hook.",[127,785,786,789],{},[572,787,788],{},"Document your style"," — one page in your wiki: indent size, keyword case, comma placement, CTE style.",[127,791,792,795],{},[572,793,794],{},"Format on save"," — editor plugins that format SQL on save eliminate style debates in code review.",[17,797,799],{"id":798},"format-your-sql","Format Your SQL",[10,801,802,807],{},[803,804,806],"a",{"href":805},"\u002Ftools\u002Fsql-formatter","StackCache SQL Formatter"," formats SQL for 13 dialects including PostgreSQL, MySQL, SQLite, SQL Server, Oracle, BigQuery, and Snowflake. Paste your query, choose your dialect, and get consistently formatted output — all locally in your browser.",[809,810,811],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":30,"searchDepth":56,"depth":56,"links":813},[814,815,816,817,818,819,820,821,822,823,824,825],{"id":19,"depth":56,"text":20},{"id":141,"depth":56,"text":142},{"id":219,"depth":56,"text":220},{"id":263,"depth":56,"text":264},{"id":282,"depth":56,"text":283},{"id":334,"depth":56,"text":335},{"id":374,"depth":56,"text":375},{"id":517,"depth":56,"text":518},{"id":567,"depth":56,"text":568},{"id":635,"depth":56,"text":636},{"id":773,"depth":56,"text":774},{"id":798,"depth":56,"text":799},"Guides","2026-09-08","Learn how to format SQL queries for readability. Covers indentation, capitalization, JOIN alignment, CTE formatting, and team style consistency.","md",{},true,"\u002Fblog\u002Fsql-formatting-best-practices",{"title":5,"description":828},"blog\u002Fsql-formatting-best-practices","sql-formatter",null,"4GpCUuAk4iZD-B9u3kv1znDbEbvZKAGATzyF3AFXb0g",1788868139517]