[{"data":1,"prerenderedAt":2366},["ShallowReactive",2],{"doc:\u002Fadvanced-data-transformation-and-cleaning\u002Fmoving-data-between-excel-and-databases\u002Fexport-sql-query-results-to-excel-with-python":3,"surround:\u002Fadvanced-data-transformation-and-cleaning\u002Fmoving-data-between-excel-and-databases\u002Fexport-sql-query-results-to-excel-with-python":2358},{"id":4,"title":5,"body":6,"dateModified":2327,"datePublished":2327,"description":2328,"extension":2329,"faq":2330,"meta":2339,"navigation":263,"path":2350,"seo":2351,"slug":2354,"stem":2355,"type":2356,"__hash__":2357},"docs\u002Fadvanced-data-transformation-and-cleaning\u002Fmoving-data-between-excel-and-databases\u002Fexport-sql-query-results-to-excel-with-python\u002Findex.md","Export SQL Query Results to Excel with Python",{"type":7,"value":8,"toc":2314},"minimark",[9,22,31,166,171,208,215,219,222,573,577,584,799,913,926,929,933,943,1033,1043,1047,1050,1619,1638,1642,1652,1902,1916,2015,2019,2161,2165,2172,2180,2184,2196,2200,2214,2230,2248,2261,2265,2268,2277,2280,2310],[10,11,12,13,17,18,21],"p",{},"An export from a database to a spreadsheet is the most common single job in reporting, and the two-line version — ",[14,15,16],"code",{},"pd.read_sql(sql, engine).to_excel(\"out.xlsx\")"," — is genuinely fine for a one-off. What makes it worth writing carefully is that it usually turns into a scheduled job that someone opens every Monday: at that point the date column showing ",[14,19,20],{},"45839",", the amounts running to fifteen decimal places and the columns too narrow to read all become recurring complaints.",[10,23,24,25,30],{},"This guide, part of ",[26,27,29],"a",{"href":28},"\u002Fadvanced-data-transformation-and-cleaning\u002Fmoving-data-between-excel-and-databases\u002F","Moving Data Between Excel and Databases",", builds the version worth scheduling: parameterised SQL, several result sets in one workbook, formats applied where they carry meaning, and a chunked path for results too large to hold in memory.",[32,33,41,42,41,46,41,50,41,57,41,64,41,74,41,80,41,85,41,89,41,93,41,96,41,101,41,106,41,109,41,115,41,120,41,123,41,130,41,135,41,138,41,141,41,145,41,148,41,155,41,161],"svg",{"viewBox":34,"role":35,"ariaLabelledBy":36,"xmlns":39,"style":40},"0 0 740 236","img",[37,38],"ex-flow-t","ex-flow-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:740px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[43,44,45],"title",{"id":37},"From a parameterised query to a formatted workbook",[47,48,49],"desc",{"id":38},"A query with bound parameters runs against the engine and returns a DataFrame. One ExcelWriter session writes a summary sheet and a detail sheet, then a formatting pass sets the date and currency formats, column widths and a frozen header row before the file is saved.",[51,52],"rect",{"x":53,"y":53,"width":54,"height":55,"fill":56},"0","740","236","#ffffff",[58,59,63],"text",{"x":60,"y":61,"style":62},"370","30","font-size:13px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","One connection, one writer session, one formatting pass",[51,65],{"x":66,"y":67,"width":68,"height":69,"rx":70,"fill":71,"stroke":72,"style":73},"16","54","150","76","12","#ebebfd","var(--brand,#5b5cf0)","stroke-width:2px",[58,75,79],{"x":76,"y":77,"style":78},"91","84","font-size:12px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","text() query",[58,81,84],{"x":76,"y":82,"style":83},"106","font-size:11px;fill:var(--muted,#5b6780);text-anchor:middle",":start and :end bound",[51,86],{"x":87,"y":67,"width":68,"height":69,"rx":70,"fill":88,"stroke":72,"style":73},"196","#f0f4ff",[58,90,92],{"x":91,"y":77,"style":78},"271","read_sql",[58,94,95],{"x":91,"y":82,"style":83},"DataFrame, typed",[51,97],{"x":98,"y":67,"width":68,"height":69,"rx":70,"fill":99,"stroke":100,"style":73},"376","#fdefd8","var(--gold,#b4740a)",[58,102,105],{"x":103,"y":77,"style":104},"451","font-size:12px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","ExcelWriter",[58,107,108],{"x":103,"y":82,"style":83},"Summary + Detail",[51,110],{"x":111,"y":67,"width":112,"height":69,"rx":70,"fill":113,"stroke":114,"style":73},"556","168","#d9f4f1","var(--teal,#0f9488)",[58,116,119],{"x":117,"y":77,"style":118},"640","font-size:12px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","format pass",[58,121,122],{"x":117,"y":82,"style":83},"dates · money · widths",[124,125],"line",{"x1":126,"y1":127,"x2":128,"y2":127,"stroke":129,"style":73},"170","92","190","var(--muted,#5b6780)",[131,132],"polygon",{"points":133,"fill":134},"194,92 184,87 184,97","#5b6780",[124,136],{"x1":137,"y1":127,"x2":60,"y2":127,"stroke":129,"style":73},"350",[131,139],{"points":140,"fill":134},"374,92 364,87 364,97",[124,142],{"x1":143,"y1":127,"x2":144,"y2":127,"stroke":129,"style":73},"530","550",[131,146],{"points":147,"fill":134},"554,92 544,87 544,97",[51,149],{"x":66,"y":150,"width":151,"height":152,"rx":70,"fill":153,"stroke":154},"152","708","64","#f0f2f5","var(--line,#cdd5e6)",[58,156,160],{"x":157,"y":158,"style":159},"40","178","font-size:12px;font-weight:700;fill:var(--text,#172033)","The formatting pass is not decoration",[58,162,165],{"x":157,"y":163,"style":164},"202","font-size:11.5px;fill:var(--muted,#5b6780)","Dates as serial numbers and amounts at fifteen decimal places turn a correct export into a support ticket.",[167,168,170],"h2",{"id":169},"prerequisites","Prerequisites",[172,173,178],"pre",{"className":174,"code":175,"language":176,"meta":177,"style":177},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl sqlalchemy\n# plus one driver:  psycopg[binary] | pymysql | pyodbc\n","bash","",[14,179,180,201],{"__ignoreMap":177},[181,182,184,188,192,195,198],"span",{"class":124,"line":183},1,[181,185,187],{"class":186},"sMTad","pip",[181,189,191],{"class":190},"srMev"," install",[181,193,194],{"class":190}," pandas",[181,196,197],{"class":190}," openpyxl",[181,199,200],{"class":190}," sqlalchemy\n",[181,202,204],{"class":124,"line":203},2,[181,205,207],{"class":206},"s-wDw","# plus one driver:  psycopg[binary] | pymysql | pyodbc\n",[10,209,210,211,214],{},"The examples use SQLite so they run anywhere with no server, and the only line that changes for Postgres, MySQL or SQL Server is the URL passed to ",[14,212,213],{},"create_engine",".",[167,216,218],{"id":217},"step-1-build-a-sample-database","Step 1: Build a sample database",[10,220,221],{},"So the rest of the guide runs end to end:",[172,223,227],{"className":224,"code":225,"language":226,"meta":177,"style":177},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\nfrom sqlalchemy import create_engine, text\n\nengine = create_engine(\"sqlite:\u002F\u002F\u002Fsales.db\")\n\nseed = pd.DataFrame({\n    \"order_id\": range(1, 13),\n    \"region\": [\"North\", \"South\", \"East\"] * 4,\n    \"order_date\": pd.to_datetime(\n        [\"2026-07-%02d\" % d for d in (1, 3, 5, 8, 11, 14, 17, 19, 22, 25, 28, 30)]),\n    \"amount\": [120.5, 340.0, 75.25, 410.75, 88.0, 260.4,\n               190.9, 305.6, 141.35, 520.0, 96.8, 233.2],\n})\nseed.to_sql(\"orders\", engine, if_exists=\"replace\", index=False)\n","python",[14,228,229,245,258,265,283,288,299,327,361,370,462,500,534,540],{"__ignoreMap":177},[181,230,231,235,239,242],{"class":124,"line":183},[181,232,234],{"class":233},"s-kum","import",[181,236,238],{"class":237},"skGVy"," pandas ",[181,240,241],{"class":233},"as",[181,243,244],{"class":237}," pd\n",[181,246,247,250,253,255],{"class":124,"line":203},[181,248,249],{"class":233},"from",[181,251,252],{"class":237}," sqlalchemy ",[181,254,234],{"class":233},[181,256,257],{"class":237}," create_engine, text\n",[181,259,261],{"class":124,"line":260},3,[181,262,264],{"emptyLinePlaceholder":263},true,"\n",[181,266,268,271,274,277,280],{"class":124,"line":267},4,[181,269,270],{"class":237},"engine ",[181,272,273],{"class":233},"=",[181,275,276],{"class":237}," create_engine(",[181,278,279],{"class":190},"\"sqlite:\u002F\u002F\u002Fsales.db\"",[181,281,282],{"class":237},")\n",[181,284,286],{"class":124,"line":285},5,[181,287,264],{"emptyLinePlaceholder":263},[181,289,291,294,296],{"class":124,"line":290},6,[181,292,293],{"class":237},"seed ",[181,295,273],{"class":233},[181,297,298],{"class":237}," pd.DataFrame({\n",[181,300,302,305,308,312,315,318,321,324],{"class":124,"line":301},7,[181,303,304],{"class":190},"    \"order_id\"",[181,306,307],{"class":237},": ",[181,309,311],{"class":310},"sP0c6","range",[181,313,314],{"class":237},"(",[181,316,317],{"class":310},"1",[181,319,320],{"class":237},", ",[181,322,323],{"class":310},"13",[181,325,326],{"class":237},"),\n",[181,328,330,333,336,339,341,344,346,349,352,355,358],{"class":124,"line":329},8,[181,331,332],{"class":190},"    \"region\"",[181,334,335],{"class":237},": [",[181,337,338],{"class":190},"\"North\"",[181,340,320],{"class":237},[181,342,343],{"class":190},"\"South\"",[181,345,320],{"class":237},[181,347,348],{"class":190},"\"East\"",[181,350,351],{"class":237},"] ",[181,353,354],{"class":233},"*",[181,356,357],{"class":310}," 4",[181,359,360],{"class":237},",\n",[181,362,364,367],{"class":124,"line":363},9,[181,365,366],{"class":190},"    \"order_date\"",[181,368,369],{"class":237},": pd.to_datetime(\n",[181,371,373,376,379,383,386,389,392,395,397,400,403,405,407,410,412,415,417,420,422,425,427,430,432,435,437,440,442,445,447,450,452,455,457,459],{"class":124,"line":372},10,[181,374,375],{"class":237},"        [",[181,377,378],{"class":190},"\"2026-07-",[181,380,382],{"class":381},"sSjpA","%02d",[181,384,385],{"class":190},"\"",[181,387,388],{"class":233}," %",[181,390,391],{"class":237}," d ",[181,393,394],{"class":233},"for",[181,396,391],{"class":237},[181,398,399],{"class":233},"in",[181,401,402],{"class":237}," (",[181,404,317],{"class":310},[181,406,320],{"class":237},[181,408,409],{"class":310},"3",[181,411,320],{"class":237},[181,413,414],{"class":310},"5",[181,416,320],{"class":237},[181,418,419],{"class":310},"8",[181,421,320],{"class":237},[181,423,424],{"class":310},"11",[181,426,320],{"class":237},[181,428,429],{"class":310},"14",[181,431,320],{"class":237},[181,433,434],{"class":310},"17",[181,436,320],{"class":237},[181,438,439],{"class":310},"19",[181,441,320],{"class":237},[181,443,444],{"class":310},"22",[181,446,320],{"class":237},[181,448,449],{"class":310},"25",[181,451,320],{"class":237},[181,453,454],{"class":310},"28",[181,456,320],{"class":237},[181,458,61],{"class":310},[181,460,461],{"class":237},")]),\n",[181,463,465,468,470,473,475,478,480,483,485,488,490,493,495,498],{"class":124,"line":464},11,[181,466,467],{"class":190},"    \"amount\"",[181,469,335],{"class":237},[181,471,472],{"class":310},"120.5",[181,474,320],{"class":237},[181,476,477],{"class":310},"340.0",[181,479,320],{"class":237},[181,481,482],{"class":310},"75.25",[181,484,320],{"class":237},[181,486,487],{"class":310},"410.75",[181,489,320],{"class":237},[181,491,492],{"class":310},"88.0",[181,494,320],{"class":237},[181,496,497],{"class":310},"260.4",[181,499,360],{"class":237},[181,501,503,506,508,511,513,516,518,521,523,526,528,531],{"class":124,"line":502},12,[181,504,505],{"class":310},"               190.9",[181,507,320],{"class":237},[181,509,510],{"class":310},"305.6",[181,512,320],{"class":237},[181,514,515],{"class":310},"141.35",[181,517,320],{"class":237},[181,519,520],{"class":310},"520.0",[181,522,320],{"class":237},[181,524,525],{"class":310},"96.8",[181,527,320],{"class":237},[181,529,530],{"class":310},"233.2",[181,532,533],{"class":237},"],\n",[181,535,537],{"class":124,"line":536},13,[181,538,539],{"class":237},"})\n",[181,541,543,546,549,552,556,558,561,563,566,568,571],{"class":124,"line":542},14,[181,544,545],{"class":237},"seed.to_sql(",[181,547,548],{"class":190},"\"orders\"",[181,550,551],{"class":237},", engine, ",[181,553,555],{"class":554},"sa561","if_exists",[181,557,273],{"class":233},[181,559,560],{"class":190},"\"replace\"",[181,562,320],{"class":237},[181,564,565],{"class":554},"index",[181,567,273],{"class":233},[181,569,570],{"class":310},"False",[181,572,282],{"class":237},[167,574,576],{"id":575},"step-2-query-with-bound-parameters","Step 2: Query with bound parameters",[10,578,579,580,583],{},"Never interpolate values into the SQL string. ",[14,581,582],{},"text()"," with named placeholders is both safer and faster, because the database can reuse the plan:",[172,585,587],{"className":224,"code":586,"language":226,"meta":177,"style":177},"DETAIL = text(\"\"\"\n    SELECT order_id, region, order_date, amount\n    FROM orders\n    WHERE order_date >= :start AND order_date \u003C :end\n    ORDER BY order_date, region\n\"\"\")\n\nparams = {\"start\": \"2026-07-01\", \"end\": \"2026-08-01\"}\n\nwith engine.connect() as conn:\n    detail = pd.read_sql(DETAIL, conn, params=params, parse_dates=[\"order_date\"])\n\nsummary = (detail.groupby(\"region\", as_index=False)[\"amount\"]\n                 .agg(orders=\"count\", amount=\"sum\")\n                 .sort_values(\"amount\", ascending=False))\n",[14,588,589,603,608,613,618,623,630,634,665,669,682,719,723,754,779],{"__ignoreMap":177},[181,590,591,594,597,600],{"class":124,"line":183},[181,592,593],{"class":310},"DETAIL",[181,595,596],{"class":233}," =",[181,598,599],{"class":237}," text(",[181,601,602],{"class":190},"\"\"\"\n",[181,604,605],{"class":124,"line":203},[181,606,607],{"class":190},"    SELECT order_id, region, order_date, amount\n",[181,609,610],{"class":124,"line":260},[181,611,612],{"class":190},"    FROM orders\n",[181,614,615],{"class":124,"line":267},[181,616,617],{"class":190},"    WHERE order_date >= :start AND order_date \u003C :end\n",[181,619,620],{"class":124,"line":285},[181,621,622],{"class":190},"    ORDER BY order_date, region\n",[181,624,625,628],{"class":124,"line":290},[181,626,627],{"class":190},"\"\"\"",[181,629,282],{"class":237},[181,631,632],{"class":124,"line":301},[181,633,264],{"emptyLinePlaceholder":263},[181,635,636,639,641,644,647,649,652,654,657,659,662],{"class":124,"line":329},[181,637,638],{"class":237},"params ",[181,640,273],{"class":233},[181,642,643],{"class":237}," {",[181,645,646],{"class":190},"\"start\"",[181,648,307],{"class":237},[181,650,651],{"class":190},"\"2026-07-01\"",[181,653,320],{"class":237},[181,655,656],{"class":190},"\"end\"",[181,658,307],{"class":237},[181,660,661],{"class":190},"\"2026-08-01\"",[181,663,664],{"class":237},"}\n",[181,666,667],{"class":124,"line":363},[181,668,264],{"emptyLinePlaceholder":263},[181,670,671,674,677,679],{"class":124,"line":372},[181,672,673],{"class":233},"with",[181,675,676],{"class":237}," engine.connect() ",[181,678,241],{"class":233},[181,680,681],{"class":237}," conn:\n",[181,683,684,687,689,692,694,697,700,702,705,708,710,713,716],{"class":124,"line":464},[181,685,686],{"class":237},"    detail ",[181,688,273],{"class":233},[181,690,691],{"class":237}," pd.read_sql(",[181,693,593],{"class":310},[181,695,696],{"class":237},", conn, ",[181,698,699],{"class":554},"params",[181,701,273],{"class":233},[181,703,704],{"class":237},"params, ",[181,706,707],{"class":554},"parse_dates",[181,709,273],{"class":233},[181,711,712],{"class":237},"[",[181,714,715],{"class":190},"\"order_date\"",[181,717,718],{"class":237},"])\n",[181,720,721],{"class":124,"line":502},[181,722,264],{"emptyLinePlaceholder":263},[181,724,725,728,730,733,736,738,741,743,745,748,751],{"class":124,"line":536},[181,726,727],{"class":237},"summary ",[181,729,273],{"class":233},[181,731,732],{"class":237}," (detail.groupby(",[181,734,735],{"class":190},"\"region\"",[181,737,320],{"class":237},[181,739,740],{"class":554},"as_index",[181,742,273],{"class":233},[181,744,570],{"class":310},[181,746,747],{"class":237},")[",[181,749,750],{"class":190},"\"amount\"",[181,752,753],{"class":237},"]\n",[181,755,756,759,762,764,767,769,772,774,777],{"class":124,"line":542},[181,757,758],{"class":237},"                 .agg(",[181,760,761],{"class":554},"orders",[181,763,273],{"class":233},[181,765,766],{"class":190},"\"count\"",[181,768,320],{"class":237},[181,770,771],{"class":554},"amount",[181,773,273],{"class":233},[181,775,776],{"class":190},"\"sum\"",[181,778,282],{"class":237},[181,780,782,785,787,789,792,794,796],{"class":124,"line":781},15,[181,783,784],{"class":237},"                 .sort_values(",[181,786,750],{"class":190},[181,788,320],{"class":237},[181,790,791],{"class":554},"ascending",[181,793,273],{"class":233},[181,795,570],{"class":310},[181,797,798],{"class":237},"))\n",[32,800,41,805,41,808,41,811,41,814,41,819,41,823,41,829,41,832,41,836,41,840,41,843,41,846,41,849,41,857,41,862,41,867,41,870,41,874,41,879,41,882,41,885,41,888,41,891,41,895,41,900,41,903,41,907,41,910],{"viewBox":801,"role":35,"ariaLabelledBy":802,"xmlns":39,"style":40},"0 0 740 232",[803,804],"ex-bind-t","ex-bind-d",[43,806,807],{"id":803},"A bound parameter and an interpolated string take different routes",[47,809,810],{"id":804},"With bound parameters the statement and the values travel separately: the database parses the statement once and treats every value as data, so a value containing SQL is harmless. With an f-string the value is pasted into the statement text before the database sees it, so its contents become part of the query.",[51,812],{"x":53,"y":53,"width":54,"height":813,"fill":56},"232",[58,815,818],{"x":816,"y":61,"style":817},"184","font-size:12.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","text() with :start and :end",[51,820],{"x":821,"y":822,"width":68,"height":157,"rx":419,"fill":113,"stroke":114,"style":73},"24","48",[58,824,828],{"x":825,"y":826,"style":827},"99","72","font-size:11px;fill:var(--text,#172033);text-anchor:middle","statement",[51,830],{"x":831,"y":822,"width":68,"height":157,"rx":419,"fill":71,"stroke":72,"style":73},"194",[58,833,835],{"x":834,"y":826,"style":827},"269","values",[124,837],{"x1":825,"y1":127,"x2":68,"y2":838,"stroke":129,"style":839},"122","stroke-width:1.5px",[124,841],{"x1":834,"y1":127,"x2":842,"y2":838,"stroke":129,"style":839},"218",[131,844],{"points":845,"fill":134},"153,124 141,119 143,129",[131,847],{"points":848,"fill":134},"215,124 227,119 225,129",[51,850],{"x":851,"y":852,"width":853,"height":854,"rx":419,"fill":855,"stroke":856,"style":73},"60","126","248","44","#5b5cf0","var(--brand-strong,#4338ca)",[58,858,861],{"x":816,"y":859,"style":860},"145","font-size:11px;font-weight:700;fill:#ffffff;text-anchor:middle","database — parses once, binds values",[58,863,866],{"x":816,"y":864,"style":865},"163","font-size:11px;fill:rgba(255,255,255,0.9);text-anchor:middle","a value is only ever data",[58,868,869],{"x":816,"y":87,"style":83},"the plan is cached and reused across runs",[58,871,873],{"x":111,"y":61,"style":872},"font-size:12.5px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","f\"... WHERE d >= '{value}'\"",[51,875],{"x":876,"y":822,"width":68,"height":157,"rx":419,"fill":877,"stroke":878,"style":73},"396","#fce9e9","var(--accent-ink,#be185d)",[58,880,828],{"x":881,"y":826,"style":827},"471",[51,883],{"x":884,"y":822,"width":68,"height":157,"rx":419,"fill":877,"stroke":878,"style":73},"566",[58,886,835],{"x":887,"y":826,"style":827},"641",[124,889],{"x1":887,"y1":127,"x2":890,"y2":127,"stroke":878,"style":839},"490",[131,892],{"points":893,"fill":894},"486,92 498,87 498,97","#d81b73",[58,896,899],{"x":884,"y":897,"style":898},"108","font-size:10.5px;font-weight:600;fill:var(--accent-ink,#be185d);text-anchor:middle","pasted in first",[51,901],{"x":902,"y":852,"width":853,"height":854,"rx":419,"fill":877,"stroke":878,"style":73},"432",[58,904,906],{"x":111,"y":859,"style":905},"font-size:11px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","database — one string, parsed as written",[58,908,909],{"x":111,"y":864,"style":827},"a value can change the statement",[58,911,912],{"x":111,"y":87,"style":83},"and a new plan is compiled for every run",[10,914,915,917,918,921,922,925],{},[14,916,707],{}," matters more than it looks. SQLite has no date type and returns strings; several drivers return ",[14,919,920],{},"decimal.Decimal"," for numeric columns, which pandas keeps as ",[14,923,924],{},"object",". Being explicit at the read is what stops a date column arriving in Excel as text that cannot be sorted.",[10,927,928],{},"Where the aggregation is expensive, push it into SQL instead of doing it in pandas — the database can group ten million rows without transferring them to your process, and the DataFrame you receive is the small one.",[167,930,932],{"id":931},"step-3-write-both-result-sets-into-one-workbook","Step 3: Write both result sets into one workbook",[10,934,935,936,938,939,942],{},"One ",[14,937,105],{}," session, one file, several sheets. Opening ",[14,940,941],{},"to_excel"," twice on the same path writes the file twice and keeps only the last:",[172,944,946],{"className":224,"code":945,"language":226,"meta":177,"style":177},"with pd.ExcelWriter(\"regional-july.xlsx\", engine=\"openpyxl\",\n                    datetime_format=\"yyyy-mm-dd\") as writer:\n    summary.to_excel(writer, sheet_name=\"Summary\", index=False)\n    detail.to_excel(writer, sheet_name=\"Detail\", index=False)\n",[14,947,948,970,988,1011],{"__ignoreMap":177},[181,949,950,952,955,958,960,963,965,968],{"class":124,"line":183},[181,951,673],{"class":233},[181,953,954],{"class":237}," pd.ExcelWriter(",[181,956,957],{"class":190},"\"regional-july.xlsx\"",[181,959,320],{"class":237},[181,961,962],{"class":554},"engine",[181,964,273],{"class":233},[181,966,967],{"class":190},"\"openpyxl\"",[181,969,360],{"class":237},[181,971,972,975,977,980,983,985],{"class":124,"line":203},[181,973,974],{"class":554},"                    datetime_format",[181,976,273],{"class":233},[181,978,979],{"class":190},"\"yyyy-mm-dd\"",[181,981,982],{"class":237},") ",[181,984,241],{"class":233},[181,986,987],{"class":237}," writer:\n",[181,989,990,993,996,998,1001,1003,1005,1007,1009],{"class":124,"line":260},[181,991,992],{"class":237},"    summary.to_excel(writer, ",[181,994,995],{"class":554},"sheet_name",[181,997,273],{"class":233},[181,999,1000],{"class":190},"\"Summary\"",[181,1002,320],{"class":237},[181,1004,565],{"class":554},[181,1006,273],{"class":233},[181,1008,570],{"class":310},[181,1010,282],{"class":237},[181,1012,1013,1016,1018,1020,1023,1025,1027,1029,1031],{"class":124,"line":267},[181,1014,1015],{"class":237},"    detail.to_excel(writer, ",[181,1017,995],{"class":554},[181,1019,273],{"class":233},[181,1021,1022],{"class":190},"\"Detail\"",[181,1024,320],{"class":237},[181,1026,565],{"class":554},[181,1028,273],{"class":233},[181,1030,570],{"class":310},[181,1032,282],{"class":237},[10,1034,1035,1038,1039,1042],{},[14,1036,1037],{},"index=False"," keeps pandas' row numbers out of column A, where they otherwise appear as an unnamed column and shift every heading right. ",[14,1040,1041],{},"datetime_format"," on the writer applies to every datetime column in every sheet, which is the cheapest way to stop dates rendering as five-digit serial numbers.",[167,1044,1046],{"id":1045},"step-4-format-the-sheets-so-people-can-read-them","Step 4: Format the sheets so people can read them",[10,1048,1049],{},"The formatting pass reopens the sheets through the same writer session and applies what the data means:",[172,1051,1053],{"className":224,"code":1052,"language":226,"meta":177,"style":177},"from openpyxl.styles import Alignment, Font, PatternFill\nfrom openpyxl.utils import get_column_letter\n\nHEADER_FILL = PatternFill(\"solid\", start_color=\"1F4E78\")\nHEADER_FONT = Font(bold=True, color=\"FFFFFF\")\n\n\ndef polish(ws, money_cols=(), date_cols=(), min_width=10, max_width=44):\n    for cell in ws[1]:\n        cell.fill, cell.font = HEADER_FILL, HEADER_FONT\n        cell.alignment = Alignment(horizontal=\"center\")\n\n    for col in money_cols:\n        for cell in ws[col][1:]:\n            cell.number_format = '#,##0.00'\n    for col in date_cols:\n        for cell in ws[col][1:]:\n            cell.number_format = 'yyyy-mm-dd'\n\n    for idx, column in enumerate(ws.columns, start=1):\n        longest = max((len(str(c.value)) for c in column if c.value is not None),\n                      default=0)\n        letter = get_column_letter(idx)\n        ws.column_dimensions[letter].width = min(max(longest + 2, min_width),\n                                                 max_width)\n    ws.freeze_panes = \"A2\"\n    ws.auto_filter.ref = ws.dimensions\n\n\nwith pd.ExcelWriter(\"regional-july.xlsx\", engine=\"openpyxl\",\n                    datetime_format=\"yyyy-mm-dd\") as writer:\n    summary.to_excel(writer, sheet_name=\"Summary\", index=False)\n    detail.to_excel(writer, sheet_name=\"Detail\", index=False)\n    polish(writer.sheets[\"Summary\"], money_cols=(\"C\",))\n    polish(writer.sheets[\"Detail\"], money_cols=(\"D\",), date_cols=(\"C\",))\n",[14,1054,1055,1067,1079,1083,1108,1138,1142,1146,1183,1201,1216,1236,1240,1252,1269,1279,1291,1306,1316,1321,1346,1398,1410,1421,1449,1455,1466,1477,1482,1487,1506,1521,1542,1563,1587],{"__ignoreMap":177},[181,1056,1057,1059,1062,1064],{"class":124,"line":183},[181,1058,249],{"class":233},[181,1060,1061],{"class":237}," openpyxl.styles ",[181,1063,234],{"class":233},[181,1065,1066],{"class":237}," Alignment, Font, PatternFill\n",[181,1068,1069,1071,1074,1076],{"class":124,"line":203},[181,1070,249],{"class":233},[181,1072,1073],{"class":237}," openpyxl.utils ",[181,1075,234],{"class":233},[181,1077,1078],{"class":237}," get_column_letter\n",[181,1080,1081],{"class":124,"line":260},[181,1082,264],{"emptyLinePlaceholder":263},[181,1084,1085,1088,1090,1093,1096,1098,1101,1103,1106],{"class":124,"line":267},[181,1086,1087],{"class":310},"HEADER_FILL",[181,1089,596],{"class":233},[181,1091,1092],{"class":237}," PatternFill(",[181,1094,1095],{"class":190},"\"solid\"",[181,1097,320],{"class":237},[181,1099,1100],{"class":554},"start_color",[181,1102,273],{"class":233},[181,1104,1105],{"class":190},"\"1F4E78\"",[181,1107,282],{"class":237},[181,1109,1110,1113,1115,1118,1121,1123,1126,1128,1131,1133,1136],{"class":124,"line":285},[181,1111,1112],{"class":310},"HEADER_FONT",[181,1114,596],{"class":233},[181,1116,1117],{"class":237}," Font(",[181,1119,1120],{"class":554},"bold",[181,1122,273],{"class":233},[181,1124,1125],{"class":310},"True",[181,1127,320],{"class":237},[181,1129,1130],{"class":554},"color",[181,1132,273],{"class":233},[181,1134,1135],{"class":190},"\"FFFFFF\"",[181,1137,282],{"class":237},[181,1139,1140],{"class":124,"line":290},[181,1141,264],{"emptyLinePlaceholder":263},[181,1143,1144],{"class":124,"line":301},[181,1145,264],{"emptyLinePlaceholder":263},[181,1147,1148,1151,1155,1158,1160,1163,1165,1168,1170,1173,1176,1178,1180],{"class":124,"line":329},[181,1149,1150],{"class":233},"def",[181,1152,1154],{"class":1153},"s_Opv"," polish",[181,1156,1157],{"class":237},"(ws, money_cols",[181,1159,273],{"class":233},[181,1161,1162],{"class":237},"(), date_cols",[181,1164,273],{"class":233},[181,1166,1167],{"class":237},"(), min_width",[181,1169,273],{"class":233},[181,1171,1172],{"class":310},"10",[181,1174,1175],{"class":237},", max_width",[181,1177,273],{"class":233},[181,1179,854],{"class":310},[181,1181,1182],{"class":237},"):\n",[181,1184,1185,1188,1191,1193,1196,1198],{"class":124,"line":363},[181,1186,1187],{"class":233},"    for",[181,1189,1190],{"class":237}," cell ",[181,1192,399],{"class":233},[181,1194,1195],{"class":237}," ws[",[181,1197,317],{"class":310},[181,1199,1200],{"class":237},"]:\n",[181,1202,1203,1206,1208,1211,1213],{"class":124,"line":372},[181,1204,1205],{"class":237},"        cell.fill, cell.font ",[181,1207,273],{"class":233},[181,1209,1210],{"class":310}," HEADER_FILL",[181,1212,320],{"class":237},[181,1214,1215],{"class":310},"HEADER_FONT\n",[181,1217,1218,1221,1223,1226,1229,1231,1234],{"class":124,"line":464},[181,1219,1220],{"class":237},"        cell.alignment ",[181,1222,273],{"class":233},[181,1224,1225],{"class":237}," Alignment(",[181,1227,1228],{"class":554},"horizontal",[181,1230,273],{"class":233},[181,1232,1233],{"class":190},"\"center\"",[181,1235,282],{"class":237},[181,1237,1238],{"class":124,"line":502},[181,1239,264],{"emptyLinePlaceholder":263},[181,1241,1242,1244,1247,1249],{"class":124,"line":536},[181,1243,1187],{"class":233},[181,1245,1246],{"class":237}," col ",[181,1248,399],{"class":233},[181,1250,1251],{"class":237}," money_cols:\n",[181,1253,1254,1257,1259,1261,1264,1266],{"class":124,"line":542},[181,1255,1256],{"class":233},"        for",[181,1258,1190],{"class":237},[181,1260,399],{"class":233},[181,1262,1263],{"class":237}," ws[col][",[181,1265,317],{"class":310},[181,1267,1268],{"class":237},":]:\n",[181,1270,1271,1274,1276],{"class":124,"line":781},[181,1272,1273],{"class":237},"            cell.number_format ",[181,1275,273],{"class":233},[181,1277,1278],{"class":190}," '#,##0.00'\n",[181,1280,1282,1284,1286,1288],{"class":124,"line":1281},16,[181,1283,1187],{"class":233},[181,1285,1246],{"class":237},[181,1287,399],{"class":233},[181,1289,1290],{"class":237}," date_cols:\n",[181,1292,1294,1296,1298,1300,1302,1304],{"class":124,"line":1293},17,[181,1295,1256],{"class":233},[181,1297,1190],{"class":237},[181,1299,399],{"class":233},[181,1301,1263],{"class":237},[181,1303,317],{"class":310},[181,1305,1268],{"class":237},[181,1307,1309,1311,1313],{"class":124,"line":1308},18,[181,1310,1273],{"class":237},[181,1312,273],{"class":233},[181,1314,1315],{"class":190}," 'yyyy-mm-dd'\n",[181,1317,1319],{"class":124,"line":1318},19,[181,1320,264],{"emptyLinePlaceholder":263},[181,1322,1324,1326,1329,1331,1334,1337,1340,1342,1344],{"class":124,"line":1323},20,[181,1325,1187],{"class":233},[181,1327,1328],{"class":237}," idx, column ",[181,1330,399],{"class":233},[181,1332,1333],{"class":310}," enumerate",[181,1335,1336],{"class":237},"(ws.columns, ",[181,1338,1339],{"class":554},"start",[181,1341,273],{"class":233},[181,1343,317],{"class":310},[181,1345,1182],{"class":237},[181,1347,1349,1352,1354,1357,1360,1363,1365,1368,1371,1373,1376,1378,1381,1384,1387,1390,1393,1396],{"class":124,"line":1348},21,[181,1350,1351],{"class":237},"        longest ",[181,1353,273],{"class":233},[181,1355,1356],{"class":310}," max",[181,1358,1359],{"class":237},"((",[181,1361,1362],{"class":310},"len",[181,1364,314],{"class":237},[181,1366,1367],{"class":310},"str",[181,1369,1370],{"class":237},"(c.value)) ",[181,1372,394],{"class":233},[181,1374,1375],{"class":237}," c ",[181,1377,399],{"class":233},[181,1379,1380],{"class":237}," column ",[181,1382,1383],{"class":233},"if",[181,1385,1386],{"class":237}," c.value ",[181,1388,1389],{"class":233},"is",[181,1391,1392],{"class":233}," not",[181,1394,1395],{"class":310}," None",[181,1397,326],{"class":237},[181,1399,1401,1404,1406,1408],{"class":124,"line":1400},22,[181,1402,1403],{"class":554},"                      default",[181,1405,273],{"class":233},[181,1407,53],{"class":310},[181,1409,282],{"class":237},[181,1411,1413,1416,1418],{"class":124,"line":1412},23,[181,1414,1415],{"class":237},"        letter ",[181,1417,273],{"class":233},[181,1419,1420],{"class":237}," get_column_letter(idx)\n",[181,1422,1424,1427,1429,1432,1434,1437,1440,1443,1446],{"class":124,"line":1423},24,[181,1425,1426],{"class":237},"        ws.column_dimensions[letter].width ",[181,1428,273],{"class":233},[181,1430,1431],{"class":310}," min",[181,1433,314],{"class":237},[181,1435,1436],{"class":310},"max",[181,1438,1439],{"class":237},"(longest ",[181,1441,1442],{"class":233},"+",[181,1444,1445],{"class":310}," 2",[181,1447,1448],{"class":237},", min_width),\n",[181,1450,1452],{"class":124,"line":1451},25,[181,1453,1454],{"class":237},"                                                 max_width)\n",[181,1456,1458,1461,1463],{"class":124,"line":1457},26,[181,1459,1460],{"class":237},"    ws.freeze_panes ",[181,1462,273],{"class":233},[181,1464,1465],{"class":190}," \"A2\"\n",[181,1467,1469,1472,1474],{"class":124,"line":1468},27,[181,1470,1471],{"class":237},"    ws.auto_filter.ref ",[181,1473,273],{"class":233},[181,1475,1476],{"class":237}," ws.dimensions\n",[181,1478,1480],{"class":124,"line":1479},28,[181,1481,264],{"emptyLinePlaceholder":263},[181,1483,1485],{"class":124,"line":1484},29,[181,1486,264],{"emptyLinePlaceholder":263},[181,1488,1490,1492,1494,1496,1498,1500,1502,1504],{"class":124,"line":1489},30,[181,1491,673],{"class":233},[181,1493,954],{"class":237},[181,1495,957],{"class":190},[181,1497,320],{"class":237},[181,1499,962],{"class":554},[181,1501,273],{"class":233},[181,1503,967],{"class":190},[181,1505,360],{"class":237},[181,1507,1509,1511,1513,1515,1517,1519],{"class":124,"line":1508},31,[181,1510,974],{"class":554},[181,1512,273],{"class":233},[181,1514,979],{"class":190},[181,1516,982],{"class":237},[181,1518,241],{"class":233},[181,1520,987],{"class":237},[181,1522,1524,1526,1528,1530,1532,1534,1536,1538,1540],{"class":124,"line":1523},32,[181,1525,992],{"class":237},[181,1527,995],{"class":554},[181,1529,273],{"class":233},[181,1531,1000],{"class":190},[181,1533,320],{"class":237},[181,1535,565],{"class":554},[181,1537,273],{"class":233},[181,1539,570],{"class":310},[181,1541,282],{"class":237},[181,1543,1545,1547,1549,1551,1553,1555,1557,1559,1561],{"class":124,"line":1544},33,[181,1546,1015],{"class":237},[181,1548,995],{"class":554},[181,1550,273],{"class":233},[181,1552,1022],{"class":190},[181,1554,320],{"class":237},[181,1556,565],{"class":554},[181,1558,273],{"class":233},[181,1560,570],{"class":310},[181,1562,282],{"class":237},[181,1564,1566,1569,1571,1574,1577,1579,1581,1584],{"class":124,"line":1565},34,[181,1567,1568],{"class":237},"    polish(writer.sheets[",[181,1570,1000],{"class":190},[181,1572,1573],{"class":237},"], ",[181,1575,1576],{"class":554},"money_cols",[181,1578,273],{"class":233},[181,1580,314],{"class":237},[181,1582,1583],{"class":190},"\"C\"",[181,1585,1586],{"class":237},",))\n",[181,1588,1590,1592,1594,1596,1598,1600,1602,1605,1608,1611,1613,1615,1617],{"class":124,"line":1589},35,[181,1591,1568],{"class":237},[181,1593,1022],{"class":190},[181,1595,1573],{"class":237},[181,1597,1576],{"class":554},[181,1599,273],{"class":233},[181,1601,314],{"class":237},[181,1603,1604],{"class":190},"\"D\"",[181,1606,1607],{"class":237},",), ",[181,1609,1610],{"class":554},"date_cols",[181,1612,273],{"class":233},[181,1614,314],{"class":237},[181,1616,1583],{"class":190},[181,1618,1586],{"class":237},[10,1620,1621,1624,1625,1628,1629,1633,1634,214],{},[14,1622,1623],{},"writer.sheets"," gives you the openpyxl worksheet for a sheet pandas has already written, so the data write and the styling happen in one pass and one save. There is no ",[14,1626,1627],{},"autofit"," in the file format — the width calculation above measures the longest string in the column, which is why the cap exists: one long free-text comment would otherwise push a column off the screen. The techniques here are covered further in ",[26,1630,1632],{"href":1631},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002F","Styling Excel Cells with openpyxl"," and ",[26,1635,1637],{"href":1636},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fapplying-number-and-date-formats-in-excel\u002F","Applying Number and Date Formats in Excel",[167,1639,1641],{"id":1640},"step-5-handle-a-result-set-too-large-to-hold","Step 5: Handle a result set too large to hold",[10,1643,1644,1645,1648,1649,1651],{},"Past a few hundred thousand rows, reading the whole result into a DataFrame before writing anything is what runs the job out of memory. ",[14,1646,1647],{},"chunksize"," turns ",[14,1650,92],{}," into an iterator, and openpyxl's write-only mode streams rows straight to the file:",[172,1653,1655],{"className":224,"code":1654,"language":226,"meta":177,"style":177},"from openpyxl import Workbook\n\nwb = Workbook(write_only=True)\nws = wb.create_sheet(\"Detail\")\n\nfirst = True\nrows_written = 0\nwith engine.connect().execution_options(stream_results=True) as conn:\n    for chunk in pd.read_sql(DETAIL, conn, params=params, chunksize=50_000):\n        if first:\n            ws.append(list(chunk.columns))\n            first = False\n        for row in chunk.itertuples(index=False, name=None):\n            ws.append(row)\n        rows_written += len(chunk)\n\nwb.save(\"detail-large.xlsx\")\nprint(f\"wrote {rows_written:,} rows\")\n",[14,1656,1657,1669,1673,1692,1706,1710,1720,1730,1750,1780,1788,1799,1809,1839,1844,1858,1862,1872],{"__ignoreMap":177},[181,1658,1659,1661,1664,1666],{"class":124,"line":183},[181,1660,249],{"class":233},[181,1662,1663],{"class":237}," openpyxl ",[181,1665,234],{"class":233},[181,1667,1668],{"class":237}," Workbook\n",[181,1670,1671],{"class":124,"line":203},[181,1672,264],{"emptyLinePlaceholder":263},[181,1674,1675,1678,1680,1683,1686,1688,1690],{"class":124,"line":260},[181,1676,1677],{"class":237},"wb ",[181,1679,273],{"class":233},[181,1681,1682],{"class":237}," Workbook(",[181,1684,1685],{"class":554},"write_only",[181,1687,273],{"class":233},[181,1689,1125],{"class":310},[181,1691,282],{"class":237},[181,1693,1694,1697,1699,1702,1704],{"class":124,"line":267},[181,1695,1696],{"class":237},"ws ",[181,1698,273],{"class":233},[181,1700,1701],{"class":237}," wb.create_sheet(",[181,1703,1022],{"class":190},[181,1705,282],{"class":237},[181,1707,1708],{"class":124,"line":285},[181,1709,264],{"emptyLinePlaceholder":263},[181,1711,1712,1715,1717],{"class":124,"line":290},[181,1713,1714],{"class":237},"first ",[181,1716,273],{"class":233},[181,1718,1719],{"class":310}," True\n",[181,1721,1722,1725,1727],{"class":124,"line":301},[181,1723,1724],{"class":237},"rows_written ",[181,1726,273],{"class":233},[181,1728,1729],{"class":310}," 0\n",[181,1731,1732,1734,1737,1740,1742,1744,1746,1748],{"class":124,"line":329},[181,1733,673],{"class":233},[181,1735,1736],{"class":237}," engine.connect().execution_options(",[181,1738,1739],{"class":554},"stream_results",[181,1741,273],{"class":233},[181,1743,1125],{"class":310},[181,1745,982],{"class":237},[181,1747,241],{"class":233},[181,1749,681],{"class":237},[181,1751,1752,1754,1757,1759,1761,1763,1765,1767,1769,1771,1773,1775,1778],{"class":124,"line":363},[181,1753,1187],{"class":233},[181,1755,1756],{"class":237}," chunk ",[181,1758,399],{"class":233},[181,1760,691],{"class":237},[181,1762,593],{"class":310},[181,1764,696],{"class":237},[181,1766,699],{"class":554},[181,1768,273],{"class":233},[181,1770,704],{"class":237},[181,1772,1647],{"class":554},[181,1774,273],{"class":233},[181,1776,1777],{"class":310},"50_000",[181,1779,1182],{"class":237},[181,1781,1782,1785],{"class":124,"line":372},[181,1783,1784],{"class":233},"        if",[181,1786,1787],{"class":237}," first:\n",[181,1789,1790,1793,1796],{"class":124,"line":464},[181,1791,1792],{"class":237},"            ws.append(",[181,1794,1795],{"class":310},"list",[181,1797,1798],{"class":237},"(chunk.columns))\n",[181,1800,1801,1804,1806],{"class":124,"line":502},[181,1802,1803],{"class":237},"            first ",[181,1805,273],{"class":233},[181,1807,1808],{"class":310}," False\n",[181,1810,1811,1813,1816,1818,1821,1823,1825,1827,1829,1832,1834,1837],{"class":124,"line":536},[181,1812,1256],{"class":233},[181,1814,1815],{"class":237}," row ",[181,1817,399],{"class":233},[181,1819,1820],{"class":237}," chunk.itertuples(",[181,1822,565],{"class":554},[181,1824,273],{"class":233},[181,1826,570],{"class":310},[181,1828,320],{"class":237},[181,1830,1831],{"class":554},"name",[181,1833,273],{"class":233},[181,1835,1836],{"class":310},"None",[181,1838,1182],{"class":237},[181,1840,1841],{"class":124,"line":542},[181,1842,1843],{"class":237},"            ws.append(row)\n",[181,1845,1846,1849,1852,1855],{"class":124,"line":781},[181,1847,1848],{"class":237},"        rows_written ",[181,1850,1851],{"class":233},"+=",[181,1853,1854],{"class":310}," len",[181,1856,1857],{"class":237},"(chunk)\n",[181,1859,1860],{"class":124,"line":1281},[181,1861,264],{"emptyLinePlaceholder":263},[181,1863,1864,1867,1870],{"class":124,"line":1293},[181,1865,1866],{"class":237},"wb.save(",[181,1868,1869],{"class":190},"\"detail-large.xlsx\"",[181,1871,282],{"class":237},[181,1873,1874,1877,1879,1882,1885,1888,1891,1894,1897,1900],{"class":124,"line":1308},[181,1875,1876],{"class":310},"print",[181,1878,314],{"class":237},[181,1880,1881],{"class":233},"f",[181,1883,1884],{"class":190},"\"wrote ",[181,1886,1887],{"class":381},"{",[181,1889,1890],{"class":237},"rows_written",[181,1892,1893],{"class":233},":,",[181,1895,1896],{"class":381},"}",[181,1898,1899],{"class":190}," rows\"",[181,1901,282],{"class":237},[10,1903,1904,1907,1908,1910,1911,1915],{},[14,1905,1906],{},"stream_results=True"," tells the driver to fetch server-side rather than buffering the whole result client-side, which is the half people forget — without it, ",[14,1909,1647],{}," limits how much pandas materialises but not how much the driver already pulled. Write-only mode cannot revisit a cell, so any formatting has to be applied as each row is appended. ",[26,1912,1914],{"href":1913},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python\u002Fwrite-large-dataframes-to-excel-with-write-only-mode\u002F","Write Large DataFrames to Excel with Write-Only Mode"," covers that trade-off.",[32,1917,41,1922,41,1925,41,1928,41,1931,41,1935,41,1940,41,1944,41,1947,41,1951,41,1954,41,1958,41,1963,41,1967,41,1970,41,1972,41,1976,41,1978,41,1981,41,1983,41,1986,41,1991,41,1995,41,1998,41,2004,41,2008,41,2012],{"viewBox":1918,"role":35,"ariaLabelledBy":1919,"xmlns":39,"style":40},"0 0 740 244",[1920,1921],"ex-mem-t","ex-mem-d",[43,1923,1924],{"id":1920},"Reading a whole result set versus streaming it in chunks",[47,1926,1927],{"id":1921},"Without chunking, the driver buffers the entire result, pandas materialises it as one DataFrame and openpyxl builds every cell in memory, so peak memory is roughly three copies of the data. With server-side streaming, a chunk size and write-only mode, only one chunk exists at a time and memory stays flat regardless of how many rows are exported.",[51,1929],{"x":53,"y":53,"width":54,"height":1930,"fill":56},"244",[58,1932,1934],{"x":816,"y":1933,"style":872},"32","read it all",[51,1936],{"x":821,"y":822,"width":1937,"height":1938,"rx":1939,"fill":877,"stroke":878,"style":73},"320","34","6",[58,1941,1943],{"x":816,"y":1942,"style":827},"70","driver buffer — every row",[51,1945],{"x":821,"y":1946,"width":1937,"height":1938,"rx":1939,"fill":877,"stroke":878,"style":73},"90",[58,1948,1950],{"x":816,"y":1949,"style":827},"112","one DataFrame — every row again",[51,1952],{"x":821,"y":1953,"width":1937,"height":1938,"rx":1939,"fill":877,"stroke":878,"style":73},"132",[58,1955,1957],{"x":816,"y":1956,"style":827},"154","openpyxl cells — a third copy",[58,1959,1962],{"x":816,"y":1960,"style":1961},"192","font-size:11.5px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","peak memory grows with the export",[58,1964,1966],{"x":816,"y":1965,"style":83},"214","two million rows is where jobs start dying",[58,1968,1969],{"x":111,"y":1933,"style":817},"stream in chunks",[51,1971],{"x":876,"y":822,"width":1946,"height":1938,"rx":1939,"fill":113,"stroke":114,"style":73},[58,1973,1975],{"x":1974,"y":1942,"style":827},"441","chunk 1",[51,1977],{"x":876,"y":1946,"width":1946,"height":1938,"rx":1939,"fill":113,"stroke":114,"style":73},[58,1979,1980],{"x":1974,"y":1949,"style":827},"chunk 2",[51,1982],{"x":876,"y":1953,"width":1946,"height":1938,"rx":1939,"fill":113,"stroke":114,"style":73},[58,1984,1985],{"x":1974,"y":1956,"style":827},"chunk 3",[124,1987],{"x1":1988,"y1":1989,"x2":1990,"y2":1989,"stroke":114,"style":73},"492","107","546",[131,1992],{"points":1993,"fill":1994},"550,107 540,102 540,112","#0f766e",[51,1996],{"x":111,"y":826,"width":1997,"height":1942,"rx":70,"fill":855,"stroke":856,"style":73},"164",[58,1999,2003],{"x":2000,"y":2001,"style":2002},"638","102","font-size:12px;font-weight:700;fill:#ffffff;text-anchor:middle","write-only sheet",[58,2005,2007],{"x":2000,"y":2006,"style":865},"124","rows appended, then freed",[58,2009,2011],{"x":111,"y":1960,"style":2010},"font-size:11.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","peak memory stays flat",[58,2013,2014],{"x":111,"y":1965,"style":83},"one chunk in flight, whatever the row count",[167,2016,2018],{"id":2017},"common-pitfalls-and-gotchas","Common pitfalls and gotchas",[2020,2021,2022,2038],"table",{},[2023,2024,2025],"thead",{},[2026,2027,2028,2032,2035],"tr",{},[2029,2030,2031],"th",{},"Symptom",[2029,2033,2034],{},"Cause",[2029,2036,2037],{},"Fix",[2039,2040,2041,2057,2075,2091,2109,2124,2139,2150],"tbody",{},[2026,2042,2043,2049,2052],{},[2044,2045,2046],"td",{},[14,2047,2048],{},"UserWarning: pandas only supports SQLAlchemy connectable",[2044,2050,2051],{},"A raw DBAPI connection was passed",[2044,2053,2054,2055],{},"Wrap it with ",[14,2056,213],{},[2026,2058,2059,2064,2067],{},[2044,2060,2061,2062],{},"Dates show as ",[14,2063,20],{},[2044,2065,2066],{},"The column reached Excel as a number",[2044,2068,2069,2071,2072,2074],{},[14,2070,707],{}," at the read, ",[14,2073,1041],{}," on the writer",[2026,2076,2077,2080,2086],{},[2044,2078,2079],{},"An unnamed first column of row numbers",[2044,2081,2082,2085],{},[14,2083,2084],{},"index=True"," (the default)",[2044,2087,2088,2089],{},"Pass ",[14,2090,1037],{},[2026,2092,2093,2096,2101],{},[2044,2094,2095],{},"Only the last sheet survives",[2044,2097,2098,2100],{},[14,2099,941],{}," called on the path repeatedly",[2044,2102,935,2103,2105,2106,2108],{},[14,2104,105],{}," context, several ",[14,2107,941],{}," calls",[2026,2110,2111,2114,2117],{},[2044,2112,2113],{},"Amounts show 15 decimal places",[2044,2115,2116],{},"Driver returned floats with no format applied",[2044,2118,2119,2120,2123],{},"Set ",[14,2121,2122],{},"number_format"," on the column",[2026,2125,2126,2129,2132],{},[2044,2127,2128],{},"The job hangs on a large query",[2044,2130,2131],{},"Client-side buffering",[2044,2133,2134,2136,2137],{},[14,2135,1906],{}," plus ",[14,2138,1647],{},[2026,2140,2141,2144,2147],{},[2044,2142,2143],{},"Password visible in a traceback",[2044,2145,2146],{},"URL built inline in the call",[2044,2148,2149],{},"Read it from an environment variable",[2026,2151,2152,2155,2158],{},[2044,2153,2154],{},"Report is stale but nobody noticed",[2044,2156,2157],{},"No generated-at stamp",[2044,2159,2160],{},"Write the timestamp into the summary sheet",[167,2162,2164],{"id":2163},"performance-and-scale-notes","Performance and scale notes",[10,2166,2167,2168,2171],{},"The three costs are the query, the transfer and the write, and they respond to different fixes. A slow query is a database problem — index the filter columns and aggregate server-side. A slow transfer is usually too many columns: ",[14,2169,2170],{},"SELECT *"," on a wide table moves fields nobody will read. A slow write is openpyxl building cell objects, which write-only mode avoids.",[10,2173,2174,2175,2179],{},"As a rough guide, a formatted 50,000-row export takes a few seconds and a couple of hundred megabytes; 500,000 rows through the streaming path stays flat on memory but produces a file that takes Excel a noticeable time to open. Past that, ask whether the detail sheet is being read at all — a summary workbook plus a CSV for the detail is nearly always the better delivery. ",[26,2176,2178],{"href":2177},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python\u002Fconvert-excel-to-csv-with-python\u002F","Convert Excel to CSV with Python"," covers that split.",[167,2181,2183],{"id":2182},"conclusion","Conclusion",[10,2185,2186,2187,2189,2190,2192,2193,2195],{},"Query with bound parameters, read with explicit types, write every result set through one ",[14,2188,105],{}," session, and finish with a formatting pass that fixes dates, money and column widths. Where the result is large, stream it: ",[14,2191,1906],{},", a ",[14,2194,1647],{},", and openpyxl's write-only mode keep memory flat regardless of row count. That is the difference between an export that works once and one that can be scheduled and forgotten.",[167,2197,2199],{"id":2198},"frequently-asked-questions","Frequently asked questions",[10,2201,2202,2206,2207,2210,2211,2213],{},[2203,2204,2205],"strong",{},"Why does pandas warn about a DBAPI2 connection?","\npandas only supports SQLAlchemy connectables and SQLite connections officially. Passing a raw psycopg or pyodbc connection still works for reading but emits a ",[14,2208,2209],{},"UserWarning","; wrap it in ",[14,2212,213],{}," and the warning goes away.",[10,2215,2216,2219,2220,2223,2224,2226,2227,2229],{},[2203,2217,2218],{},"How do I export several queries into one workbook?","\nOpen a single ",[14,2221,2222],{},"pd.ExcelWriter"," as a context manager and call ",[14,2225,941],{}," once per DataFrame with a different ",[14,2228,995],{},". Opening the file repeatedly overwrites it each time.",[10,2231,2232,2235,2236,2238,2239,2241,2242,2245,2246,214],{},[2203,2233,2234],{},"My dates arrive as 45839 in Excel — why?","\nThe column reached Excel as a number rather than a datetime. Set ",[14,2237,1041],{}," on the ",[14,2240,105],{},", and make sure the column's dtype is ",[14,2243,2244],{},"datetime64"," before writing rather than ",[14,2247,924],{},[10,2249,2250,2253,2254,2256,2257,2260],{},[2203,2251,2252],{},"The export takes minutes and eats memory. What should I change?","\nRead in chunks with ",[14,2255,1647],{}," and write with a streaming engine, and push the aggregation into SQL. A ",[14,2258,2259],{},"GROUP BY"," in the database is almost always faster than the same operation over a DataFrame you had to transfer first.",[167,2262,2264],{"id":2263},"related","Related",[10,2266,2267],{},"Up to the parent guide:",[2269,2270,2271],"ul",{},[2272,2273,2274,2276],"li",{},[26,2275,29],{"href":28}," — connections, type mapping and the return leg.",[10,2278,2279],{},"Related guides:",[2269,2281,2282,2289,2296,2303],{},[2272,2283,2284,2288],{},[26,2285,2287],{"href":2286},"\u002Fadvanced-data-transformation-and-cleaning\u002Fmoving-data-between-excel-and-databases\u002Fload-an-excel-file-into-a-sql-database-with-pandas\u002F","Load an Excel File into a SQL Database with pandas"," — the same boundary in the other direction.",[2272,2290,2291,2295],{},[26,2292,2294],{"href":2293},"\u002Fadvanced-data-transformation-and-cleaning\u002Fmoving-data-between-excel-and-databases\u002Frefresh-an-excel-report-from-a-database-on-a-schedule\u002F","Refresh an Excel Report from a Database on a Schedule"," — running this export unattended.",[2272,2297,2298,2302],{},[26,2299,2301],{"href":2300},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fwrite-multiple-dataframes-to-one-excel-file\u002F","Write Multiple DataFrames to One Excel File"," — the multi-sheet writer pattern in depth.",[2272,2304,2305,2309],{},[26,2306,2308],{"href":2307},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python\u002Fread-large-excel-file-in-chunks-with-pandas\u002F","Read Large Excel Files in Chunks with pandas"," — the same chunking idea applied to reading spreadsheets.",[2311,2312,2313],"style",{},"html pre.shiki code .sMTad, html code.shiki .sMTad{--shiki-default:#6F42C1;--shiki-dark:#FFB757}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}",{"title":177,"searchDepth":203,"depth":203,"links":2315},[2316,2317,2318,2319,2320,2321,2322,2323,2324,2325,2326],{"id":169,"depth":203,"text":170},{"id":217,"depth":203,"text":218},{"id":575,"depth":203,"text":576},{"id":931,"depth":203,"text":932},{"id":1045,"depth":203,"text":1046},{"id":1640,"depth":203,"text":1641},{"id":2017,"depth":203,"text":2018},{"id":2163,"depth":203,"text":2164},{"id":2182,"depth":203,"text":2183},{"id":2198,"depth":203,"text":2199},{"id":2263,"depth":203,"text":2264},"2026-08-10","Run a parameterised query with SQLAlchemy and write the result to a formatted workbook: one sheet per result set, correct date and money formats, sized columns, and chunked reads for big results.","md",[2331,2333,2335,2337],{"q":2205,"a":2332},"pandas only supports SQLAlchemy connectables and SQLite connections officially. Passing a raw psycopg or pyodbc connection still works for reading but emits a UserWarning; wrap it in create_engine and the warning goes away.",{"q":2218,"a":2334},"Open a single pd.ExcelWriter as a context manager and call to_excel once per DataFrame with a different sheet_name. Opening the file repeatedly overwrites it each time.",{"q":2234,"a":2336},"The column reached Excel as a number rather than a datetime. Set datetime_format on the ExcelWriter, and make sure the column's dtype is datetime64 before writing rather than object.",{"q":2252,"a":2338},"Read in chunks with chunksize and write with a streaming engine, and push the aggregation into SQL. A GROUP BY in the database is almost always faster than the same operation over a DataFrame you had to transfer first.",{"breadcrumb":2340},[2341,2344,2347,2348],{"name":2342,"item":2343},"Home","\u002F",{"name":2345,"item":2346},"Advanced Data Transformation and Cleaning","\u002Fadvanced-data-transformation-and-cleaning\u002F",{"name":29,"item":28},{"name":5,"item":2349},"\u002Fadvanced-data-transformation-and-cleaning\u002Fmoving-data-between-excel-and-databases\u002Fexport-sql-query-results-to-excel-with-python\u002F","\u002Fadvanced-data-transformation-and-cleaning\u002Fmoving-data-between-excel-and-databases\u002Fexport-sql-query-results-to-excel-with-python",{"title":2352,"description":2353},"Export SQL Query Results to Excel (Python)","Use SQLAlchemy and pandas to export a SQL query to .xlsx: bound parameters, multi-sheet writers, number and date formats, column widths, and chunked reads for large result sets.","export-sql-query-results-to-excel-with-python","advanced-data-transformation-and-cleaning\u002Fmoving-data-between-excel-and-databases\u002Fexport-sql-query-results-to-excel-with-python\u002Findex","how-to","NPgR-nNO1C8MM07r2Y7WWTmCi202sQ7K7Z4OrNr_9Nc",[2359,2362],{"title":29,"path":2360,"stem":2361,"children":-1},"\u002Fadvanced-data-transformation-and-cleaning\u002Fmoving-data-between-excel-and-databases","advanced-data-transformation-and-cleaning\u002Fmoving-data-between-excel-and-databases\u002Findex",{"title":2363,"path":2364,"stem":2365,"children":-1},"Fetch API Data into Excel with Python requests","\u002Fadvanced-data-transformation-and-cleaning\u002Fmoving-data-between-excel-and-databases\u002Ffetch-api-data-into-excel-with-python-requests","advanced-data-transformation-and-cleaning\u002Fmoving-data-between-excel-and-databases\u002Ffetch-api-data-into-excel-with-python-requests\u002Findex",1786800027161]