Loading the chapter…
%23%20%2F%2F%2F%20script%0A%23%20requires-python%20%3D%20%22%3E%3D3.12%2C%3C3.15%22%0A%23%20dependencies%20%3D%20%5B%0A%23%20%20%20%20%20%22marimo%5Bsql%5D%22%2C%0A%23%20%20%20%20%20%22duckdb%22%2C%0A%23%20%5D%0A%23%20%2F%2F%2F%0A%22%22%22SQL%20Handbook%2C%20Chapter%20D%3A%20Building%20and%20Loading.%0A%22%22%22%0A%0Aimport%20marimo%0A%0A__generated_with%20%3D%20%220.25.2%22%0Aapp%20%3D%20marimo.App(width%3D%22medium%22%2C%20sql_output%3D%22polars%22)%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20import%20tempfile%0A%20%20%20%20from%20pathlib%20import%20Path%0A%0A%20%20%20%20import%20duckdb%0A%20%20%20%20import%20marimo%20as%20mo%0A%0A%20%20%20%20return%20Path%2C%20duckdb%2C%20mo%2C%20tempfile%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%20Building%20and%20Loading%0A%0A%20%20%20%20Chapter%20D%20of%20the%20SQL%20Handbook.%20Five%20sections%2C%20each%20one%20concept.%0A%0A%20%20%20%20Parts%20B%20and%20C%20ask%20a%20database%20questions.%20This%20chapter%20covers%20where%20the%20tables%20came%0A%20%20%20%20from%2C%20how%20rows%20get%20into%20them%2C%20and%20how%20you%20run%20any%20of%20it%20from%20a%20notebook.%0A%0A%20%20%20%20Section%2027%20is%20the%20mechanics%20of%20running%20a%20query%20at%20all.%20If%20you%20have%20not%20run%20one%20yet%2C%0A%20%20%20%20read%20it%20first%20and%20come%20back%20here.%0A%0A%20%20%20%20Two%20databases%20are%20open%20below.%20The%20course%20warehouse%2C%20Northwind%2C%20is%20read%20and%20never%0A%20%20%20%20written%20to.%20Everything%20this%20chapter%20builds%20goes%20into%20a%20second%2C%20empty%20database%20that%0A%20%20%20%20lives%20in%20memory%20and%20is%20gone%20when%20the%20notebook%20closes.%20Nothing%20on%20disk%20changes.%0A%0A%20%20%20%20To%20run%20the%20code%20yourself%2C%20download%20the%20%60.py%60%20into%20your%20project%20and%20open%20it%20with%0A%20%20%20%20%60uv%20run%20marimo%20edit%60.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Opening%20the%20databases%0A%0A%20%20%20%20The%20first%20cell%20loads%20three%20Northwind%20tables%20from%20parquet%20files.%20The%20second%20opens%20an%0A%20%20%20%20empty%20database%20with%20nothing%20in%20it.%20How%20both%20of%20those%20lines%20work%20is%20%C2%A726%20and%20%C2%A727.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(duckdb%2C%20mo)%3A%0A%20%20%20%20%23%20Look%20for%20a%20data%2Fnorthwind%20folder%20here%20and%20in%20every%20folder%20above%2C%20so%20the%0A%20%20%20%20%23%20chapter%20works%20wherever%20you%20save%20it.%20If%20there%20is%20none%2C%20read%20the%20copy%20on%20the%0A%20%20%20%20%23%20course%20site%20instead.%0A%20%20%20%20_here%20%3D%20mo.notebook_dir()%0A%20%20%20%20_source%20%3D%20next(%0A%20%20%20%20%20%20%20%20(p%20%2F%20%22data%22%20%2F%20%22northwind%22%20for%20p%20in%20%5B_here%2C%20*_here.parents%5D%0A%20%20%20%20%20%20%20%20%20if%20(p%20%2F%20%22data%22%20%2F%20%22northwind%22).is_dir())%2C%0A%20%20%20%20%20%20%20%20%22https%3A%2F%2Foim7510.github.io%2Fdata%2Fnorthwind%22%2C%0A%20%20%20%20)%0A%0A%20%20%20%20con%20%3D%20duckdb.connect()%0A%20%20%20%20for%20_table%20in%20(%22customers%22%2C%20%22orders%22%2C%20%22products%22)%3A%0A%20%20%20%20%20%20%20%20con.execute(%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22%22%22CREATE%20TABLE%20%7B_table%7D%20AS%20SELECT%20*%20FROM%20'%7B_source%7D%2F%7B_table%7D.parquet'%22%22%22%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20return%20(con%2C)%0A%0A%0A%40app.cell%0Adef%20_(duckdb)%3A%0A%20%20%20%20db%20%3D%20duckdb.connect()%0A%20%20%20%20return%20(db%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20---%0A%20%20%20%20%23%23%2024.%20DDL%3A%20%60CREATE%20TABLE%60%2C%20types%2C%20constraints%0A%0A%20%20%20%20A%20table%20does%20not%20appear%20on%20its%20own.%20Somebody%20writes%20down%20its%20columns%2C%20gives%20each%0A%20%20%20%20column%20a%20type%2C%20and%20adds%20the%20rules%20a%20row%20has%20to%20satisfy.%20From%20then%20on%20the%20database%0A%20%20%20%20refuses%20every%20row%20that%20breaks%20one%20of%20them%2C%20which%20is%20what%20makes%20a%20column%20of%20prices%0A%20%20%20%20something%20you%20can%20add%20up%20without%20looking%20at%20it%20first.%20%60CREATE%20TABLE%60%20belongs%20to%20SQL's%0A%20%20%20%20**data%20definition%20language**%2C%20or%20DDL.%0A%0A%20%20%20%20%F0%9F%93%96%20**Used%20in%3A**%20Building%20and%20querying%20your%20database%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20The%20shape%20of%20a%20%60CREATE%20TABLE%60%0A%0A%20%20%20%20A%20small%20consultancy%20bills%20clients%20for%20projects.%20Here%20is%20its%20%60project%60%20table.%20One%20line%0A%20%20%20%20per%20column%2C%20with%20the%20type%20after%20the%20name%20and%20the%20rules%20after%20the%20type.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(db)%3A%0A%20%20%20%20db.execute(%22%22%22%0A%20%20%20%20%20%20%20%20CREATE%20OR%20REPLACE%20TABLE%20project%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20code%20%20%20%20VARCHAR%20%20%20%20%20%20%20PRIMARY%20KEY%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20name%20%20%20%20VARCHAR%20%20%20%20%20%20%20NOT%20NULL%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20budget%20%20DECIMAL(10%2C2)%20NOT%20NULL%20CHECK%20(budget%20%3E%200)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20started%20DATE%20%20%20%20%20%20%20%20%20%20NOT%20NULL%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20status%20%20VARCHAR%20%20%20%20%20%20%20NOT%20NULL%20DEFAULT%20'active'%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20CHECK%20(status%20IN%20('active'%2C%20'closed'))%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%22%22%22)%0A%20%20%20%20project_table%20%3D%20db.sql(%22DESCRIBE%20project%22)%0A%20%20%20%20project_table%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%60DESCRIBE%60%20reads%20the%20statement%20back.%20%60null%60%20says%20%60NO%60%20on%20every%20column%20that%20was%0A%20%20%20%20declared%20%60NOT%20NULL%60%2C%20%60key%60%20says%20%60PRI%60%20on%20the%20primary%20key%2C%20and%20%60default%60%20carries%20the%0A%20%20%20%20value%20the%20database%20supplies%20when%20a%20row%20leaves%20%60status%60%20out.%0A%0A%20%20%20%20**The%20trap.**%20Running%20a%20plain%20%60CREATE%20TABLE%60%20twice%20is%20an%20error%3A%0A%0A%20%20%20%20%60%60%60%0A%20%20%20%20Catalog%20Error%3A%20Table%20with%20name%20%22project%22%20already%20exists!%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20%60CREATE%20OR%20REPLACE%20TABLE%60%20re-runs%20cleanly%2C%20which%20is%20why%20every%20statement%20in%20this%0A%20%20%20%20chapter%20uses%20it.%20There%20is%20one%20case%20it%20refuses%2C%20and%20that%20case%20is%20two%20knowledge%20points%0A%20%20%20%20below.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20add%20a%20%60client%20VARCHAR%20NOT%20NULL%60%20column%20to%20the%20statement%2C%20run%20the%20cell%0A%20%20%20%20again%2C%20and%20read%20%60DESCRIBE%20project%60%20to%20confirm%20the%20column%20arrived%20and%20is%20required.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Choosing%20a%20type%0A%0A%20%20%20%20A%20lot%20of%20the%20SQL%20you%20will%20meet%20outside%20this%20course%20was%20written%20for%20SQLite%2C%20which%0A%20%20%20%20offers%20four%20types%20and%20leans%20on%20%60TEXT%60%20for%20everything%20awkward.%20DuckDB%20has%20real%20types%2C%20and%20the%0A%20%20%20%20translation%20is%20worth%20having%20in%20one%20place.%0A%0A%20%20%20%20%7C%20what%20you%20will%20see%20%7C%20what%20to%20write%20here%20%7C%20for%20%7C%0A%20%20%20%20%7C---%7C---%7C---%7C%0A%20%20%20%20%7C%20%60INTEGER%60%20%7C%20%60INTEGER%60%20%7C%20counts%2C%20quantities%2C%20ids%20%7C%0A%20%20%20%20%7C%20%60TEXT%60%20%7C%20%60VARCHAR%60%20%7C%20names%2C%20codes%2C%20anything%20typed%20%7C%0A%20%20%20%20%7C%20%60REAL%60%20%7C%20%60DECIMAL(10%2C2)%60%20%7C%20money%20%7C%0A%20%20%20%20%7C%20%60REAL%60%20%7C%20%60DOUBLE%60%20%7C%20measurements%2C%20ratios%2C%20anything%20continuous%20%7C%0A%20%20%20%20%7C%20%60TEXT%60%20holding%20%60'2026-01-12'%60%20%7C%20%60DATE%60%20%7C%20a%20day%20%7C%0A%20%20%20%20%7C%20%60TEXT%60%20holding%20a%20timestamp%20%7C%20%60TIMESTAMP%60%20%7C%20a%20day%20and%20a%20time%20%7C%0A%20%20%20%20%7C%20%60BLOB%60%20%7C%20leave%20it%20out%20%7C%20files%20belong%20beside%20the%20database%20%7C%0A%0A%20%20%20%20%60DECIMAL(10%2C2)%60%20means%2010%20digits%20with%20two%20of%20them%20after%20the%20point%2C%20so%20it%20holds%20up%20to%0A%20%20%20%2099%2C999%2C999.99%20and%20stores%20cents%20exactly.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(db)%3A%0A%20%20%20%20project_types%20%3D%20db.sql(%22%22%22%0A%20%20%20%20%20%20%20%20SELECT%20SUM(CAST(0.10%20AS%20REAL))%20%20%20%20%20%20%20%20%20%20AS%20ten_dimes_as_real%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20SUM(CAST(0.10%20AS%20DECIMAL(10%2C2)))%20AS%20ten_dimes_as_decimal%0A%20%20%20%20%20%20%20%20FROM%20range(10)%0A%20%20%20%20%22%22%22)%0A%20%20%20%20project_types%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20**The%20trap.**%20%60budget%60%20is%20a%20%60DECIMAL%60%20because%20of%20this.%20%60REAL%60%20is%20binary%20floating%0A%20%20%20%20point%2C%20and%20one%20tenth%20has%20no%20exact%20binary%20form%2C%20so%2010%20dimes%20come%20to%20a%20dollar%20and%20a%0A%20%20%20%20remainder.%20Plenty%20of%20SQLite%20material%20says%20to%20use%20%60REAL%60%20for%20money.%20A%0A%20%20%20%20course%20whose%20every%20deliverable%20carries%20a%20section%20called%20*How%20I%20know%20these%20numbers%20are%0A%20%20%20%20right*%20stores%20dollars%20in%20a%20type%20that%20can%20hold%20them.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20change%20%60range(10)%60%20to%20%60range(1000)%60%20and%20read%20both%20numbers%20again.%20Then%0A%20%20%20%20run%20%60SELECT%20CAST(0.1%20AS%20REAL)%20%2B%20CAST(0.2%20AS%20REAL)%60%20on%20its%20own.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20%60NOT%20NULL%60%2C%20%60DEFAULT%60%20and%20%60CHECK%60%0A%0A%20%20%20%20Three%20rules%20can%20sit%20on%20a%20column%2C%20and%20all%20three%20are%20enforced%20at%20the%20moment%20a%20row%0A%20%20%20%20arrives.%0A%0A%20%20%20%20-%20**%60NOT%20NULL%60**%20refuses%20a%20row%20that%20leaves%20the%20column%20empty.%0A%20%20%20%20-%20**%60DEFAULT%60**%20supplies%20a%20value%20when%20the%20row%20leaves%20the%20column%20out.%20It%20makes%20the%0A%20%20%20%20%20%20column%20optional%20to%20write%20and%20still%20never%20empty.%0A%20%20%20%20-%20**%60CHECK%60**%20refuses%20a%20row%20whose%20value%20fails%20a%20condition%20you%20write%20yourself.%20It%20takes%0A%20%20%20%20%20%20any%20expression%20that%20comes%20out%20true%20or%20false%2C%20so%20%60budget%20%3E%200%60%20and%0A%20%20%20%20%20%20%60status%20IN%20('active'%2C%20'closed')%60%20are%20both%20fine.%0A%0A%20%20%20%20Here%20is%20what%20each%20refusal%20looks%20like%20against%20the%20%60project%60%20table%20above.%0A%0A%20%20%20%20%60%60%60sql%0A%20%20%20%20INSERT%20INTO%20project%20(code%2C%20name%2C%20budget%2C%20started)%0A%20%20%20%20VALUES%20('AURORA'%2C%20'Aurora%20rollout'%2C%20-1%2C%20DATE%20'2026-01-12')%3B%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20%60%60%60%0A%20%20%20%20Constraint%20Error%3A%20CHECK%20constraint%20failed%20on%20table%20project%20with%20expression%20CHECK((budget%20%3E%200))%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20%60%60%60sql%0A%20%20%20%20INSERT%20INTO%20project%20(code%2C%20budget%2C%20started)%0A%20%20%20%20VALUES%20('AURORA'%2C%2048000.00%2C%20DATE%20'2026-01-12')%3B%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20%60%60%60%0A%20%20%20%20Constraint%20Error%3A%20NOT%20NULL%20constraint%20failed%3A%20project.name%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20%60%60%60sql%0A%20%20%20%20INSERT%20INTO%20project%20(code%2C%20name%2C%20budget%2C%20started%2C%20status)%0A%20%20%20%20VALUES%20('AURORA'%2C%20'Aurora%20rollout'%2C%2048000.00%2C%20DATE%20'2026-01-12'%2C%20'paused')%3B%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20%60%60%60%0A%20%20%20%20Constraint%20Error%3A%20CHECK%20constraint%20failed%20on%20table%20project%20with%20expression%20CHECK((status%20IN%20('active'%2C%20'closed')))%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20A%20%60CHECK%60%20is%20the%20cheapest%20place%20to%20put%20a%20rule%20you%20would%20otherwise%20write%20into%20every%0A%20%20%20%20query%20that%20reads%20the%20table%2C%20and%20the%20message%20names%20the%20expression%20that%20failed.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20reproduce%20all%20three.%20Then%20add%0A%20%20%20%20%60CHECK%20(started%20%3E%3D%20DATE%20'2020-01-01')%60%20to%20the%20statement%20and%20find%20out%20what%20happens%20to%20a%0A%20%20%20%20row%20whose%20%60started%60%20is%20in%202019.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20A%20compound%20key%0A%0A%20%20%20%20Chapter%20A%20%C2%A72%20called%20this%20a%20compound%20key%20and%20Chapter%20A%20%C2%A73%20said%20an%20intersection%20table%0A%20%20%20%20needs%20one.%20This%20is%20the%20syntax.%20A%20key%20spanning%20two%20columns%20cannot%20go%20on%20either%20column%2C%0A%20%20%20%20so%20it%20goes%20on%20its%20own%20line%20at%20the%20end%20of%20the%20statement.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(db)%3A%0A%20%20%20%20db.execute(%22%22%22%0A%20%20%20%20%20%20%20%20CREATE%20OR%20REPLACE%20TABLE%20ord_line%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20order_id%20%20%20%20%20INTEGER%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20product_code%20VARCHAR%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20quantity%20%20%20%20%20INTEGER%20NOT%20NULL%20CHECK%20(quantity%20%3E%200)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20PRIMARY%20KEY%20(order_id%2C%20product_code)%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%22%22%22)%0A%20%20%20%20db.execute(%22INSERT%20INTO%20ord_line%20VALUES%20(1%2C%20'CHAI'%2C%2012)%2C%20(1%2C%20'KONBU'%2C%204)%22)%0A%20%20%20%20keys_table%20%3D%20db.sql(%22DESCRIBE%20ord_line%22)%0A%20%20%20%20keys_table%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%60key%60%20says%20%60PRI%60%20on%20both%20columns.%20One%20order%20line%20is%20one%20product%20on%20one%20order%2C%20so%20the%0A%20%20%20%20pair%20has%20to%20be%20unique%20and%20neither%20half%20is%20unique%20on%20its%20own.%20A%20second%20line%20for%20%60CHAI%60%0A%20%20%20%20on%20order%201%20is%20refused%3A%0A%0A%20%20%20%20%60%60%60sql%0A%20%20%20%20INSERT%20INTO%20ord_line%20VALUES%20(1%2C%20'CHAI'%2C%205)%3B%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20%60%60%60%0A%20%20%20%20Constraint%20Error%3A%20Duplicate%20key%20%22order_id%3A%201%2C%20product_code%3A%20CHAI%22%20violates%20primary%20key%20constraint.%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20In%20a%20full%20design%20both%20columns%20would%20also%20be%20foreign%20keys%2C%20one%20into%20the%20orders%20table%0A%20%20%20%20and%20one%20into%20products.%20The%20next%20knowledge%20point%20is%20that%20syntax.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20insert%20%60(2%2C%20'CHAI'%2C%203)%60%20and%20confirm%20it%20is%20accepted.%20Then%20state%20in%20one%0A%20%20%20%20sentence%20what%20changes%20if%20the%20primary%20key%20is%20%60PRIMARY%20KEY%20(order_id)%60%20alone.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Declaring%20a%20foreign%20key%0A%0A%20%20%20%20Chapter%20A%20%C2%A74%20showed%20two%20%60Constraint%20Error%60%20messages%20from%20a%20%60customer%60%20and%20%60ord%60%20pair%0A%20%20%20%20and%20said%20the%20syntax%20was%20here.%20It%20is%20%60REFERENCES%60%2C%20and%20it%20goes%20on%20the%20column%20that%20holds%0A%20%20%20%20the%20other%20table's%20key.%0A%0A%20%20%20%20The%20two%20tables%20are%20created%20in%20one%20cell%20and%20dropped%20child%20first%2C%20for%20a%20reason%20the%0A%20%20%20%20output%20below%20explains.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(db)%3A%0A%20%20%20%20db.execute(%22DROP%20TABLE%20IF%20EXISTS%20ord%22)%0A%20%20%20%20db.execute(%22DROP%20TABLE%20IF%20EXISTS%20customer%22)%0A%20%20%20%20db.execute(%22%22%22%0A%20%20%20%20%20%20%20%20CREATE%20TABLE%20customer%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20id%20%20%20%20%20%20VARCHAR%20PRIMARY%20KEY%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20company%20VARCHAR%20NOT%20NULL%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%22%22%22)%0A%20%20%20%20db.execute(%22%22%22%0A%20%20%20%20%20%20%20%20CREATE%20TABLE%20ord%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20id%20%20%20%20%20%20%20%20%20%20INTEGER%20PRIMARY%20KEY%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20customer_id%20VARCHAR%20NOT%20NULL%20REFERENCES%20customer(id)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20ordered_on%20%20DATE%20%20%20%20NOT%20NULL%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%22%22%22)%0A%20%20%20%20db.execute(%22INSERT%20INTO%20customer%20VALUES%20('VINET'%2C%20'Vins%20et%20alcools%20Chevalier')%22)%0A%20%20%20%20db.execute(%22INSERT%20INTO%20ord%20VALUES%20(10248%2C%20'VINET'%2C%20DATE%20'2016-07-04')%22)%0A%20%20%20%20fk_table%20%3D%20db.sql(%22%22%22%0A%20%20%20%20%20%20%20%20SELECT%20table_name%2C%20constraint_type%2C%20constraint_text%0A%20%20%20%20%20%20%20%20FROM%20duckdb_constraints()%0A%20%20%20%20%20%20%20%20WHERE%20table_name%20IN%20('customer'%2C%20'ord')%0A%20%20%20%20%20%20%20%20ORDER%20BY%20table_name%2C%20constraint_type%0A%20%20%20%20%22%22%22)%0A%20%20%20%20fk_table%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%60duckdb_constraints()%60%20lists%20what%20the%20database%20is%20now%20enforcing.%20Both%20messages%20in%0A%20%20%20%20Chapter%20A%20%C2%A74%20come%20from%20these%20two%20rows%2C%20and%20%60ON%20DELETE%20CASCADE%60%20is%20the%20parser%20error%0A%20%20%20%20quoted%20there.%0A%0A%20%20%20%20**The%20trap.**%20%60CREATE%20OR%20REPLACE%20TABLE%20customer%60%20is%20refused%20while%20%60ord%60%20points%20at%20it%2C%0A%20%20%20%20which%20is%20why%20those%20two%20%60DROP%60%20lines%20are%20at%20the%20top%20of%20the%20cell%3A%0A%0A%20%20%20%20%60%60%60%0A%20%20%20%20Dependency%20Error%3A%20Cannot%20drop%20entry%20%22customer%22%20because%20there%20are%20entries%20that%20depend%20on%20it.%0A%20%20%20%20table%20%22ord%22%20depends%20on%20table%20%22customer%22.%0A%20%20%20%20Use%20DROP...CASCADE%20to%20drop%20all%20dependents.%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20The%20suggestion%20in%20the%20last%20line%20does%20not%20help.%20%60DROP%20...%20CASCADE%60%20in%20DuckDB%20reaches%0A%20%20%20%20views%2C%20and%20a%20foreign-key%20child%20stays%20where%20it%20is.%20A%20parent%20goes%20away%20after%20its%20children%2C%20and%0A%20%20%20%20%C2%A726%20shows%20the%20same%20order%20running%20forwards%20for%20a%20load.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20add%20%60CREATE%20OR%20REPLACE%20TABLE%20customer%20(id%20VARCHAR%20PRIMARY%20KEY)%60%20as%20its%0A%20%20%20%20own%20cell%20and%20read%20the%20error.%20Then%20delete%20the%20%60DROP%20TABLE%20IF%20EXISTS%20ord%60%20line%20from%20the%0A%20%20%20%20cell%20above%20and%20run%20it%20again.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20There%20is%20no%20%60AUTOINCREMENT%60%0A%0A%20%20%20%20Most%20tables%20want%20an%20id%20nobody%20has%20to%20think%20about.%20SQLite%20spells%20that%0A%20%20%20%20%60INTEGER%20PRIMARY%20KEY%20AUTOINCREMENT%60%2C%20which%20is%20the%20line%20an%20agent%20will%20usually%20reach%0A%20%20%20%20for%20and%20which%20DuckDB%20rejects%3A%0A%0A%20%20%20%20%60%60%60sql%0A%20%20%20%20CREATE%20OR%20REPLACE%20TABLE%20expense%20(id%20INTEGER%20PRIMARY%20KEY%20AUTOINCREMENT)%3B%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20%60%60%60%0A%20%20%20%20Parser%20Error%3A%20syntax%20error%20at%20or%20near%20%22AUTOINCREMENT%22%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20DuckDB%20splits%20the%20idea%20in%20two.%20A%20**sequence**%20is%20an%20object%20that%20hands%20out%20the%20next%0A%20%20%20%20number%20each%20time%20it%20is%20asked%2C%20and%20a%20column%20gets%20one%20by%20naming%20it%20in%20its%20%60DEFAULT%60.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(db)%3A%0A%20%20%20%20db.execute(%22DROP%20TABLE%20IF%20EXISTS%20expense%22)%0A%20%20%20%20db.execute(%22CREATE%20OR%20REPLACE%20SEQUENCE%20seq_expense_id%20START%201%22)%0A%20%20%20%20db.execute(%22%22%22%0A%20%20%20%20%20%20%20%20CREATE%20OR%20REPLACE%20TABLE%20expense%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20id%20%20%20%20%20%20%20INTEGER%20%20%20%20%20%20%20PRIMARY%20KEY%20DEFAULT%20nextval('seq_expense_id')%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20code%20%20%20%20%20VARCHAR%20%20%20%20%20%20%20NOT%20NULL%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20spent_on%20DATE%20%20%20%20%20%20%20%20%20%20NOT%20NULL%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20amount%20%20%20DECIMAL(10%2C2)%20NOT%20NULL%20CHECK%20(amount%20%3E%200)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20category%20VARCHAR%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%22%22%22)%0A%20%20%20%20id_table%20%3D%20db.sql(%22%22%22%0A%20%20%20%20%20%20%20%20SELECT%20column_name%2C%20column_type%2C%20%22default%22%20FROM%20(DESCRIBE%20expense)%0A%20%20%20%20%22%22%22)%0A%20%20%20%20id_table%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20The%20%60default%60%20column%20holds%20%60nextval('seq_expense_id')%60.%20Any%20row%20that%20leaves%20%60id%60%20out%0A%20%20%20%20gets%20the%20next%20number%2C%20which%20%C2%A725%20uses.%0A%0A%20%20%20%20The%20other%20option%20is%20to%20supply%20the%20ids%20yourself%2C%20and%20it%20is%20the%20right%20one%20more%20often%0A%20%20%20%20than%20it%20sounds.%20Northwind's%20%60CustomerID%60%20is%20%60VINET%60%2C%20a%20code%20a%20person%20chose%2C%20and%0A%20%20%20%20Chapter%20A%20%C2%A72%20is%20about%20when%20that%20beats%20a%20number.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20run%20%60SELECT%20nextval('seq_expense_id')%60%20three%20times%20and%20watch%20the%20value%0A%20%20%20%20move.%20Then%20run%20%60CREATE%20OR%20REPLACE%20SEQUENCE%20seq_expense_id%20START%201%60%20and%20read%20the%20error.%0A%20%20%20%20Which%20table%20is%20keeping%20the%20sequence%20in%20place%3F%20Rerun%20the%20cell%20above%20to%20start%20the%0A%20%20%20%20sequence%20from%201%20again.%0A%0A%20%20%20%20%23%23%23%20%E2%9C%94%20Check%20your%20understanding%0A%0A%20%20%20%201.%20%60budget%60%20is%20%60DECIMAL(10%2C2)%60%20and%20%60NOT%20NULL%60%20and%20carries%20a%20%60CHECK%60.%20Which%20of%20those%0A%20%20%20%20%20%20%20three%20would%20stop%20a%20row%20that%20says%20the%20budget%20is%20zero%2C%20and%20which%20would%20stop%20a%20row%0A%20%20%20%20%20%20%20that%20says%20nothing%20about%20the%20budget%20at%20all%3F%0A%20%20%20%202.%20A%20colleague's%20schema%20stores%20every%20date%20as%20%60TEXT%60.%20Name%20one%20query%20that%20gives%20a%20wrong%0A%20%20%20%20%20%20%20answer%20because%20of%20it.%0A%20%20%20%203.%20%60status%60%20has%20both%20a%20%60DEFAULT%60%20and%20a%20%60CHECK%60.%20What%20does%20each%20one%20do%20that%20the%20other%0A%20%20%20%20%20%20%20does%20not%3F%0A%20%20%20%204.%20%60ord_line%60%20has%20a%20primary%20key%20over%20two%20columns.%20What%20would%20go%20wrong%20if%20it%20were%0A%20%20%20%20%20%20%20declared%20over%20%60order_id%60%20alone%2C%20and%20what%20would%20go%20wrong%20over%20%60product_code%60%20alone%3F%0A%20%20%20%205.%20An%20agent%20hands%20you%20a%20%60CREATE%20TABLE%60%20with%20%60AUTOINCREMENT%60%20and%20%60budget%20REAL%60.%20State%0A%20%20%20%20%20%20%20what%20happens%20to%20each%20when%20you%20run%20it%20here.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20---%0A%20%20%20%20%23%23%2025.%20DML%3A%20%60INSERT%60%2C%20%60UPDATE%60%2C%20%60DELETE%60%0A%0A%20%20%20%20Three%20statements%20change%20rows.%20%60INSERT%60%20adds%20them%2C%20%60UPDATE%60%20changes%20ones%20that%20are%0A%20%20%20%20already%20there%2C%20and%20%60DELETE%60%20removes%20them.%20All%20three%20are%20plain%20ANSI%20SQL%20and%20take%20a%0A%20%20%20%20table%20name%2C%20so%20they%20move%20between%20engines%20with%20no%20edits.%20Together%20they%20are%20SQL's%0A%20%20%20%20**data%20manipulation%20language**%2C%20or%20DML.%0A%0A%20%20%20%20Everything%20below%20runs%20against%20the%20%60project%60%20and%20%60expense%60%20tables%20from%20%C2%A724.%20The%0A%20%20%20%20warehouse%20is%20read-only%20in%20this%20book%2C%20and%20it%20stays%20that%20way%20here.%0A%0A%20%20%20%20%F0%9F%93%96%20**Used%20in%3A**%20Building%20and%20querying%20your%20database%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20%60INSERT%60%20names%20its%20columns%0A%0A%20%20%20%20An%20%60INSERT%60%20gives%20a%20table%20name%2C%20a%20list%20of%20columns%2C%20and%20one%20%60VALUES%60%20group%20per%20row.%20The%0A%20%20%20%20first%20row%20below%20leaves%20%60status%60%20out%20and%20takes%20the%20%60DEFAULT%60.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(db)%3A%0A%20%20%20%20db.execute(%22%22%22%0A%20%20%20%20%20%20%20%20INSERT%20INTO%20project%20(code%2C%20name%2C%20budget%2C%20started)%20VALUES%0A%20%20%20%20%20%20%20%20%20%20%20%20('AURORA'%2C%20'Aurora%20rollout'%2C%2048000.00%2C%20DATE%20'2026-01-12')%0A%20%20%20%20%22%22%22)%0A%20%20%20%20db.execute(%22%22%22%0A%20%20%20%20%20%20%20%20INSERT%20INTO%20project%20(code%2C%20name%2C%20budget%2C%20started%2C%20status)%20VALUES%0A%20%20%20%20%20%20%20%20%20%20%20%20('BEACON'%2C%20'Beacon%20audit'%2C%20%20%20%2015250.50%2C%20DATE%20'2026-02-03'%2C%20'active')%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20('CEDAR'%2C%20%20'Cedar%20migration'%2C%20%207500.00%2C%20DATE%20'2025-11-30'%2C%20'active')%0A%20%20%20%20%22%22%22)%0A%20%20%20%20project_rows%20%3D%20db.sql(%22SELECT%20*%20FROM%20project%20ORDER%20BY%20code%22)%0A%20%20%20%20project_rows%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Three%20rows%20went%20in%2C%20and%20%60AURORA%60%20came%20out%20%60active%60%20without%20anybody%20saying%20so.%0A%0A%20%20%20%20**The%20trap.**%20The%20column%20list%20is%20optional.%20%60INSERT%20INTO%20project%20VALUES%20(...)%60%20matches%0A%20%20%20%20values%20to%20columns%20by%20position%2C%20so%20it%20works%20until%20somebody%20adds%20a%20column%20to%20the%20table%2C%0A%20%20%20%20at%20which%20point%20every%20one%20of%20those%20statements%20is%20loading%20values%20into%20the%20wrong%20columns%0A%20%20%20%20or%20failing.%20Name%20the%20columns.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20insert%20a%20fourth%20project%20with%20a%20%60code%60%20that%20already%20exists%20and%20read%20the%0A%20%20%20%20message.%20Then%20insert%20one%20whose%20%60started%60%20is%20a%20string%2C%20%60'2026-04-01'%60%2C%20and%20see%20whether%0A%20%20%20%20DuckDB%20accepts%20it.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20The%20columns%20you%20leave%20out%0A%0A%20%20%20%20%60expense%60%20has%20a%20sequence%20on%20its%20%60id%60%2C%20so%20the%20%60INSERT%60%20omits%20it%20and%20the%20database%20fills%0A%20%20%20%20it%20in.%20%60category%60%20is%20the%20one%20column%20with%20no%20%60NOT%20NULL%60%2C%20so%20leaving%20it%20out%20gives%20a%0A%20%20%20%20%60NULL%60%2C%20which%20Part%20B%20%C2%A79%20is%20about.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(db)%3A%0A%20%20%20%20db.execute(%22%22%22%0A%20%20%20%20%20%20%20%20INSERT%20INTO%20expense%20(code%2C%20spent_on%2C%20amount%2C%20category)%20VALUES%0A%20%20%20%20%20%20%20%20%20%20%20%20('AURORA'%2C%20DATE%20'2026-01-20'%2C%201250.00%2C%20'travel')%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20('AURORA'%2C%20DATE%20'2026-02-11'%2C%20%20340.75%2C%20'software')%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20('BEACON'%2C%20DATE%20'2026-02-14'%2C%20%20%2089.99%2C%20'software')%0A%20%20%20%20%22%22%22)%0A%20%20%20%20db.execute(%22%22%22%0A%20%20%20%20%20%20%20%20INSERT%20INTO%20expense%20(code%2C%20spent_on%2C%20amount)%20VALUES%0A%20%20%20%20%20%20%20%20%20%20%20%20('CEDAR'%2C%20DATE%20'2025-12-02'%2C%202100.00)%0A%20%20%20%20%22%22%22)%0A%20%20%20%20expense_rows%20%3D%20db.sql(%22SELECT%20*%20FROM%20expense%20ORDER%20BY%20id%22)%0A%20%20%20%20expense_rows%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20There%20are%20four%20rows%20with%20ids%201%20to%204%2C%20none%20of%20which%20was%20typed%2C%20and%20one%20%60NULL%60%0A%20%20%20%20category.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20insert%20a%20fifth%20expense%20and%20predict%20its%20id%20before%20you%20run%20it.%20Then%0A%20%20%20%20insert%20one%20with%20%60amount%60%20set%20to%20%600%60%20and%20read%20which%20constraint%20stops%20it.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20%60UPDATE%60%20changes%20the%20rows%20its%20%60WHERE%60%20names%0A%0A%20%20%20%20%60UPDATE%60%20takes%20a%20table%2C%20a%20%60SET%60%20clause%20naming%20the%20columns%20to%20change%2C%20and%20a%20%60WHERE%60%0A%20%20%20%20naming%20the%20rows.%20The%20%60WHERE%60%20is%20optional%20to%20write%20and%20never%20optional%20to%20think%20about%2C%0A%20%20%20%20because%20leaving%20it%20off%20changes%20every%20row%20in%20the%20table%20and%20reports%20success.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(db)%3A%0A%20%20%20%20db.execute(%22UPDATE%20project%20SET%20status%20%3D%20'closed'%20WHERE%20code%20%3D%20'CEDAR'%22)%0A%20%20%20%20update_rows%20%3D%20db.sql(%22SELECT%20code%2C%20name%2C%20status%20FROM%20project%20ORDER%20BY%20code%22)%0A%20%20%20%20update_rows%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20One%20row%20moved.%20DuckDB%20reports%20how%20many%20in%20the%20editor's%20output%20area%2C%20and%20the%20way%20to%0A%20%20%20%20know%20for%20certain%20is%20to%20read%20the%20table%20back%2C%20which%20is%20what%20the%20cell%20above%20does.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20run%20%60UPDATE%20project%20SET%20budget%20%3D%20budget%20*%201.1%20WHERE%20status%20%3D%20'active'%60%0A%20%20%20%20and%20read%20the%20two%20budgets%20before%20and%20after.%20Write%20down%20which%20rows%20you%20expected%20to%20move%0A%20%20%20%20before%20you%20run%20it.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20%60DELETE%60%20removes%20whole%20rows%0A%0A%20%20%20%20%60DELETE%20FROM%60%20takes%20a%20table%20and%20a%20%60WHERE%60.%20There%20is%20no%20column%20list%2C%20because%20a%20delete%0A%20%20%20%20removes%20the%20entire%20row.%20%60DELETE%20FROM%20project%60%20with%20no%20%60WHERE%60%20empties%20the%20table.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(db)%3A%0A%20%20%20%20db.execute(%22DELETE%20FROM%20project%20WHERE%20code%20%3D%20'BEACON'%22)%0A%20%20%20%20delete_rows%20%3D%20db.sql(%22SELECT%20code%2C%20name%2C%20status%20FROM%20project%20ORDER%20BY%20code%22)%0A%20%20%20%20delete_rows%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Two%20rows%20are%20left.%20Nothing%20stopped%20that%20delete%2C%20because%20no%20other%20table%20declares%20a%20foreign%0A%20%20%20%20key%20into%20%60project%60.%20Where%20one%20does%2C%20the%20delete%20is%20refused%2C%20and%20Chapter%20A%20%C2%A74%20has%20the%0A%20%20%20%20message%20and%20the%20three%20designs%20that%20answer%20for%20it.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20delete%20%60VINET%60%20from%20the%20%60customer%60%20table%20built%20in%20%C2%A724%2C%20which%20does%20have%20a%0A%20%20%20%20child%20row%20pointing%20at%20it%2C%20and%20read%20the%20message.%20Then%20delete%20the%20%60ord%60%20row%20first%20and%0A%20%20%20%20try%20again.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Transactions%2C%20%60COMMIT%60%20and%20%60ROLLBACK%60%0A%0A%20%20%20%20Sooner%20or%20later%20somebody%20runs%20an%20%60UPDATE%60%20without%20its%20%60WHERE%60.%20Wrap%20a%20destructive%0A%20%20%20%20statement%20in%20a%20transaction%20and%20there%20is%20a%20way%20back%3A%20%60BEGIN%20TRANSACTION%60%2C%20run%20it%2C%20look%0A%20%20%20%20at%20the%20result%2C%20then%20%60COMMIT%60%20to%20keep%20it%20or%20%60ROLLBACK%60%20to%20put%20everything%20back%20the%20way%0A%20%20%20%20it%20was.%0A%0A%20%20%20%20The%20cell%20below%20does%20exactly%20that%2C%20and%20prints%20the%20count%20from%20inside%20the%20transaction%0A%20%20%20%20beside%20the%20count%20after%20the%20rollback.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(db)%3A%0A%20%20%20%20db.execute(%22BEGIN%20TRANSACTION%22)%0A%20%20%20%20db.execute(%22UPDATE%20project%20SET%20status%20%3D%20'closed'%22)%0A%20%20%20%20_inside%20%3D%20db.sql(%22SELECT%20count(*)%20FROM%20project%20WHERE%20status%20%3D%20'closed'%22).fetchall()%5B0%5D%5B0%5D%0A%20%20%20%20db.execute(%22ROLLBACK%22)%0A%20%20%20%20_after%20%3D%20db.sql(%22SELECT%20count(*)%20FROM%20project%20WHERE%20status%20%3D%20'closed'%22).fetchall()%5B0%5D%5B0%5D%0A%20%20%20%20rollback_counts%20%3D%20db.sql(f%22%22%22%0A%20%20%20%20%20%20%20%20SELECT%20%7B_inside%7D%20AS%20closed_inside_the_transaction%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7B_after%7D%20%20AS%20closed_after_the_rollback%0A%20%20%20%20%22%22%22)%0A%20%20%20%20rollback_counts%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Inside%20the%20transaction%20both%20projects%20were%20closed.%20After%20the%20%60ROLLBACK%60%20one%20is%2C%20which%0A%20%20%20%20is%20where%20%C2%A725%20left%20the%20table.%0A%0A%20%20%20%20Two%20things%20make%20this%20usable.%20The%20statement%20runs%20for%20real%0A%20%20%20%20inside%20the%20transaction%2C%20so%20the%20count%20you%20read%20is%20the%20count%20you%20would%20be%20committing.%0A%20%20%20%20And%20nothing%20outside%20the%20transaction%20sees%20any%20of%20it%20until%20the%20%60COMMIT%60.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20run%20the%20same%20three%20statements%20with%20%60COMMIT%60%20in%20place%20of%20%60ROLLBACK%60%2C%0A%20%20%20%20then%20put%20the%20table%20back%20with%20an%20%60UPDATE%60%20of%20your%20own.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%3E%20**Advanced**%0A%0A%20%20%20%20%23%23%23%20Statements%20that%20belong%20together%0A%0A%20%20%20%20A%20transaction%20is%20also%20how%20you%20tell%20the%20database%20that%20several%20statements%20are%20one%0A%20%20%20%20change.%20Finance%20moves%2010%2C000%20of%20budget%20from%20Training%20to%20Sales.%20An%20%60UPDATE%60%20adds%20it%20to%0A%20%20%20%20Sales%2C%20and%20a%20second%20one%20takes%20it%20from%20Training.%20Training%20holds%207%2C500%2C%20so%20the%20second%20one%0A%20%20%20%20breaks%20a%20%60CHECK%60.%0A%0A%20%20%20%20Without%20a%20%60BEGIN%20TRANSACTION%60%2C%20DuckDB%20keeps%20each%20statement%20the%20moment%20it%20succeeds.%0A%20%20%20%20This%20is%20called%20**autocommit**%2C%20and%20most%20databases%20do%20the%20same%20by%20default.%20The%20cell%0A%20%20%20%20below%20runs%20the%20move%20both%20ways%2C%20each%20time%20on%20a%20fresh%20copy%20of%20the%20two%20departments%2C%20and%0A%20%20%20%20reads%20the%20total%20afterwards.%20The%20departments%20start%20with%2055%2C500%20between%20them.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(db%2C%20duckdb)%3A%0A%20%20%20%20def%20transfer_funds(amount%2C%20in_one_transaction)%3A%0A%20%20%20%20%20%20%20%20%23%20a%20fresh%20copy%20of%20the%20two%20departments%20on%20every%20call%0A%20%20%20%20%20%20%20%20db.execute(%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20CREATE%20OR%20REPLACE%20TABLE%20department%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20name%20%20%20%20VARCHAR%20%20%20%20%20%20%20PRIMARY%20KEY%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20balance%20DECIMAL(10%2C2)%20NOT%20NULL%20CHECK%20(balance%20%3E%3D%200)%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%22%22%22)%0A%20%20%20%20%20%20%20%20db.execute(%22INSERT%20INTO%20department%20VALUES%20('Sales'%2C%2048000.00)%2C%20('Training'%2C%207500.00)%22)%0A%20%20%20%20%20%20%20%20if%20in_one_transaction%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20db.execute(%22BEGIN%20TRANSACTION%22)%0A%20%20%20%20%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20db.execute(%22UPDATE%20department%20SET%20balance%20%3D%20balance%20%2B%20%3F%20WHERE%20name%20%3D%20'Sales'%22%2C%20%5Bamount%5D)%0A%20%20%20%20%20%20%20%20%20%20%20%20db.execute(%22UPDATE%20department%20SET%20balance%20%3D%20balance%20-%20%3F%20WHERE%20name%20%3D%20'Training'%22%2C%20%5Bamount%5D)%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20in_one_transaction%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20db.execute(%22COMMIT%22)%0A%20%20%20%20%20%20%20%20except%20duckdb.ConstraintException%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20in_one_transaction%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20db.execute(%22ROLLBACK%22)%0A%20%20%20%20%20%20%20%20return%20db.sql(%22SELECT%20sum(balance)%20FROM%20department%22).fetchall()%5B0%5D%5B0%5D%0A%0A%20%20%20%20_alone%20%3D%20transfer_funds(10000%2C%20in_one_transaction%3DFalse)%0A%20%20%20%20_together%20%3D%20transfer_funds(10000%2C%20in_one_transaction%3DTrue)%0A%20%20%20%20transfer_totals%20%3D%20db.sql(f%22%22%22%0A%20%20%20%20%20%20%20%20SELECT%20%7B_alone%7D%20%20%20%20AS%20total_without_a_transaction%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7B_together%7D%20AS%20total_inside_one_transaction%0A%20%20%20%20%22%22%22)%0A%20%20%20%20transfer_totals%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Without%20a%20transaction%20the%20total%20came%20out%20at%2065%2C500%2C%20which%20is%2010%2C000%20more%20than%20the%0A%20%20%20%20departments%20had.%20Sales%20received%20the%20money%2C%20the%20%60CHECK%60%20refused%20the%20second%20%60UPDATE%60%2C%0A%20%20%20%20and%20the%20first%20had%20already%20been%20kept.%20Inside%20one%20transaction%20the%20%60ROLLBACK%60%20undid%20both%0A%20%20%20%20statements%2C%20and%20the%20total%20stayed%20at%2055%2C500.%0A%0A%20%20%20%20The%20database%20has%20no%20way%20to%20know%20that%20two%20%60UPDATE%60s%20are%20one%20move%20until%20you%20put%20them%20in%0A%20%20%20%20one%20transaction.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20change%20both%20%6010000%60s%20to%20%605000%60%20and%20predict%20both%20totals%20before%20you%20run%0A%20%20%20%20it.%20Then%20state%20whether%20the%20transaction%20made%20any%20difference%20that%20time.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Databases%20name%20what%20a%20transaction%20promises%20with%20the%20letters%20**ACID**%3A%0A%0A%20%20%20%20-%20**Atomicity.**%20The%20statements%20in%20a%20transaction%20are%20kept%20together%20or%20not%20at%20all.%0A%20%20%20%20%20%20The%20transfer%20above%20shows%20it.%0A%20%20%20%20-%20**Consistency.**%20Every%20rule%20the%20database%20was%20told%20about%2C%20such%20as%20a%20%60CHECK%60%20or%20a%0A%20%20%20%20%20%20foreign%20key%2C%20still%20holds%20when%20the%20transaction%20ends.%20A%20business%20rule%20that%20lives%0A%20%20%20%20%20%20only%20in%20your%20code%20is%20yours%20to%20check.%0A%20%20%20%20-%20**Isolation.**%20Transactions%20running%20at%20the%20same%20time%20do%20not%20see%20each%20other's%0A%20%20%20%20%20%20uncommitted%20changes.%20How%20strict%20that%20separation%20is%20depends%20on%20the%20database%20and%20on%0A%20%20%20%20%20%20a%20setting%20called%20the%20isolation%20level.%0A%20%20%20%20-%20**Durability.**%20Once%20a%20%60COMMIT%60%20completes%2C%20the%20change%20survives%20a%20crash.%20That%20needs%0A%20%20%20%20%20%20a%20database%20kept%20in%20a%20file%2C%20and%20the%20%60db%60%20in%20this%20chapter%20lives%20in%20memory%2C%20so%20it%20is%0A%20%20%20%20%20%20gone%20when%20the%20notebook%20closes.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20%E2%9C%94%20Check%20your%20understanding%0A%0A%20%20%20%201.%20%60INSERT%20INTO%20project%20VALUES%20('DELTA'%2C%20'Delta%20review'%2C%20900.00%2C%20DATE%20'2026-04-01'%2C%20'active')%60%0A%20%20%20%20%20%20%20runs%20today.%20Somebody%20adds%20a%20%60client%60%20column%20to%20%60project%60%20next%20week.%20What%20happens%20to%0A%20%20%20%20%20%20%20that%20statement%2C%20and%20when%20do%20you%20find%20out%3F%0A%20%20%20%202.%20An%20%60UPDATE%60%20reports%20that%20it%20changed%2047%20rows%20and%20you%20expected%203.%20What%20do%20you%20do%0A%20%20%20%20%20%20%20before%20running%20anything%20else%3F%0A%20%20%20%203.%20You%20want%20to%20remove%20one%20column's%20values%20from%20every%20row%20without%20removing%20the%20rows.%0A%20%20%20%20%20%20%20Which%20of%20the%20three%20statements%20is%20that%2C%20and%20how%20would%20you%20write%20it%3F%0A%20%20%20%204.%20A%20colleague%20says%20a%20transaction%20is%20an%20undo%20button.%20Name%20one%20thing%20it%20undoes%20and%20one%0A%20%20%20%20%20%20%20thing%20it%20does%20not.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20---%0A%20%20%20%20%23%23%2026.%20Loading%20a%20CSV%20into%20DuckDB%0A%0A%20%20%20%20A%20CSV%20is%20the%20file%20data%20arrives%20in.%20Getting%20the%20rows%20into%20a%20table%20takes%20one%20line.%20Two%0A%20%20%20%20things%20decide%20whether%20the%20numbers%20are%20right%20afterwards%3A%20the%20type%20each%20column%20ended%20up%0A%20%20%20%20with%2C%20and%20whether%20the%20count%20matches%20the%20file.%0A%0A%20%20%20%20Two%20CSV%20files%20are%20written%20to%20a%20temporary%20folder%20below%20and%20read%20from%20there.%20Nothing%0A%20%20%20%20in%20the%20%60data%60%20folder%20is%20touched.%0A%0A%20%20%20%20%F0%9F%93%96%20**Used%20in%3A**%20Building%20and%20querying%20your%20database%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Reading%20a%20CSV%20without%20loading%20it%0A%0A%20%20%20%20%60read_csv_auto%60%20reads%20a%20file%20and%20hands%20back%20rows.%20No%20table%20is%20created%20and%20nothing%20is%0A%20%20%20%20stored%2C%20so%20this%20is%20the%20way%20to%20look%20at%20a%20file%20before%20deciding%20what%20to%20do%20with%20it.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(Path%2C%20tempfile)%3A%0A%20%20%20%20csv_dir%20%3D%20Path(tempfile.mkdtemp())%0A%20%20%20%20(csv_dir%20%2F%20%22project.csv%22).write_text(%0A%20%20%20%20%20%20%20%20%22code%2Cname%2Cbudget%2Cstarted%2Cstatus%5Cn%22%0A%20%20%20%20%20%20%20%20%22AURORA%2CAurora%20rollout%2C48000.00%2C2026-01-12%2Cactive%5Cn%22%0A%20%20%20%20%20%20%20%20%22BEACON%2CBeacon%20audit%2C15250.50%2C2026-02-03%2Cactive%5Cn%22%0A%20%20%20%20%20%20%20%20%22CEDAR%2CCedar%20migration%2C7500.00%2C2025-11-30%2Cclosed%5Cn%22%0A%20%20%20%20)%0A%20%20%20%20(csv_dir%20%2F%20%22expense.csv%22).write_text(%0A%20%20%20%20%20%20%20%20%22project_code%2Cspent_on%2Camount%2Ccategory%5Cn%22%0A%20%20%20%20%20%20%20%20%22AURORA%2C2026-01-20%2C1250.00%2Ctravel%5Cn%22%0A%20%20%20%20%20%20%20%20%22AURORA%2C2026-02-11%2C340.75%2Csoftware%5Cn%22%0A%20%20%20%20%20%20%20%20%22BEACON%2C2026-02-14%2C89.99%2Csoftware%5Cn%22%0A%20%20%20%20%20%20%20%20%22CEDAR%2C2025-12-02%2C2100.00%2Ccontractor%5Cn%22%0A%20%20%20%20)%0A%20%20%20%20csv_dir%0A%20%20%20%20return%20(csv_dir%2C)%0A%0A%0A%40app.cell%0Adef%20_(csv_dir%2C%20db)%3A%0A%20%20%20%20db.sql(f%22SELECT%20*%20FROM%20read_csv_auto('%7B(csv_dir%20%2F%20'project.csv').as_posix()%7D')%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20The%20header%20row%20became%20column%20names%20and%20every%20row%20below%20it%20became%20a%20row.%20%60FROM%0A%20%20%20%20'project.csv'%60%20on%20its%20own%20does%20the%20same%20thing%2C%20because%20DuckDB%20recognizes%20the%20extension%0A%20%20%20%20and%20calls%20the%20reader%20for%20you.%20That%20is%20also%20how%20the%20warehouse%20is%20opened%20at%20the%20top%20of%0A%20%20%20%20this%20notebook%2C%20with%20%60.parquet%60%20in%20place%20of%20%60.csv%60.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20run%20%60DESCRIBE%20SELECT%20*%20FROM%20read_csv_auto('project.csv')%60%20against%20the%0A%20%20%20%20path%20above%20and%20write%20down%20the%20type%20it%20chose%20for%20each%20of%20the%20five%20columns.%20Keep%20the%0A%20%20%20%20list%3B%20the%20next%20knowledge%20point%20is%20about%20one%20of%20them.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Making%20it%20a%20table%0A%0A%20%20%20%20The%20difference%20between%20the%20two%20is%20who%20decides%20the%20column%20types.%0A%0A%20%20%20%20%60CREATE%20TABLE%20...%20AS%20SELECT%60%20takes%20whatever%20the%20reader%20guessed.%20It%20is%20one%20line%2C%20and%0A%20%20%20%20the%20guess%20is%20often%20right.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(csv_dir%2C%20db)%3A%0A%20%20%20%20db.execute(f%22%22%22%0A%20%20%20%20%20%20%20%20CREATE%20OR%20REPLACE%20TABLE%20project_guessed%20AS%0A%20%20%20%20%20%20%20%20SELECT%20*%20FROM%20read_csv_auto('%7B(csv_dir%20%2F%20'project.csv').as_posix()%7D')%0A%20%20%20%20%22%22%22)%0A%20%20%20%20guessed_types%20%3D%20db.sql(%22DESCRIBE%20project_guessed%22)%0A%20%20%20%20guessed_types%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%60budget%60%20came%20out%20%60DOUBLE%60%2C%20which%20is%20%C2%A724's%20%60REAL%60%20problem%20under%20another%20name.%20The%0A%20%20%20%20reader%20saw%20digits%20and%20a%20decimal%20point%20and%20had%20no%20way%20to%20know%20they%20were%20dollars.%0A%0A%20%20%20%20%60COPY%20...%20FROM%60%20is%20the%20other%20way.%20You%20write%20the%20%60CREATE%20TABLE%60%20first%2C%20with%20the%20types%0A%20%20%20%20and%20the%20constraints%20you%20want%2C%20and%20%60COPY%60%20fills%20it.%20The%20file%20has%20to%20fit%20the%20table%20you%0A%20%20%20%20declared.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(csv_dir%2C%20db)%3A%0A%20%20%20%20db.execute(%22DROP%20TABLE%20IF%20EXISTS%20expense_loaded%22)%0A%20%20%20%20db.execute(%22DROP%20TABLE%20IF%20EXISTS%20project_loaded%22)%0A%20%20%20%20db.execute(%22CREATE%20OR%20REPLACE%20SEQUENCE%20seq_loaded_id%20START%201%22)%0A%20%20%20%20db.execute(%22%22%22%0A%20%20%20%20%20%20%20%20CREATE%20TABLE%20project_loaded%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20code%20%20%20%20VARCHAR%20%20%20%20%20%20%20PRIMARY%20KEY%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20name%20%20%20%20VARCHAR%20%20%20%20%20%20%20NOT%20NULL%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20budget%20%20DECIMAL(10%2C2)%20NOT%20NULL%20CHECK%20(budget%20%3E%200)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20started%20DATE%20%20%20%20%20%20%20%20%20%20NOT%20NULL%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20status%20%20VARCHAR%20%20%20%20%20%20%20NOT%20NULL%20DEFAULT%20'active'%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%22%22%22)%0A%20%20%20%20db.execute(%22%22%22%0A%20%20%20%20%20%20%20%20CREATE%20TABLE%20expense_loaded%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20id%20%20%20%20%20%20%20%20%20%20%20INTEGER%20%20%20%20%20%20%20PRIMARY%20KEY%20DEFAULT%20nextval('seq_loaded_id')%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20project_code%20VARCHAR%20%20%20%20%20%20%20NOT%20NULL%20REFERENCES%20project_loaded(code)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20spent_on%20%20%20%20%20DATE%20%20%20%20%20%20%20%20%20%20NOT%20NULL%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20amount%20%20%20%20%20%20%20DECIMAL(10%2C2)%20NOT%20NULL%20CHECK%20(amount%20%3E%200)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20category%20%20%20%20%20VARCHAR%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%22%22%22)%0A%20%20%20%20db.execute(%0A%20%20%20%20%20%20%20%20f%22COPY%20project_loaded%20FROM%20'%7B(csv_dir%20%2F%20'project.csv').as_posix()%7D'%20(HEADER)%22%0A%20%20%20%20)%0A%20%20%20%20db.execute(%0A%20%20%20%20%20%20%20%20%22COPY%20expense_loaded%20(project_code%2C%20spent_on%2C%20amount%2C%20category)%20%22%0A%20%20%20%20%20%20%20%20f%22FROM%20'%7B(csv_dir%20%2F%20'expense.csv').as_posix()%7D'%20(HEADER)%22%0A%20%20%20%20)%0A%20%20%20%20loaded_tables%20%3D%20db.sql(%22%22%22%0A%20%20%20%20%20%20%20%20SELECT%20(SELECT%20SUM(budget)%20FROM%20project_guessed)%20AS%20sum_from_the_guess%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20(SELECT%20SUM(budget)%20FROM%20project_loaded)%20%20AS%20sum_from_the_declaration%0A%20%20%20%20%22%22%22)%0A%20%20%20%20loaded_tables%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20The%20same%20three%20budgets%20add%20to%20%6070750.5%60%20from%20the%20guessed%20table%20and%20%6070750.50%60%20from%20the%0A%20%20%20%20declared%20one.%20On%20three%20rows%20the%20gap%20is%20cosmetic.%20On%20a%20quarter%20of%20invoices%20it%20is%20the%0A%20%20%20%20difference%20between%20a%20total%20that%20reconciles%20and%20one%20that%20is%20off%20by%20a%20few%20cents%20with%20no%0A%20%20%20%20explanation.%0A%0A%20%20%20%20Two%20details%20in%20that%20cell%20are%20worth%20naming.%20%60expense.csv%60%20has%20no%20%60id%60%20column%2C%20so%20the%0A%20%20%20%20%60COPY%60%20lists%20the%20four%20columns%20it%20does%20supply%20and%20the%20sequence%20fills%20in%20the%20fifth.%0A%20%20%20%20And%20%60project_loaded%60%20is%20filled%20before%20%60expense_loaded%60%2C%20which%20the%20next%20knowledge%20point%0A%20%20%20%20is%20about.%0A%0A%20%20%20%20%23%23%23%20In%20Excel%20terms%0A%0A%20%20%20%20Opening%20a%20CSV%20in%20Excel%20is%20%60CREATE%20TABLE%20...%20AS%20SELECT%60.%20Excel%20guesses%20the%20type%20of%0A%20%20%20%20every%20column%2C%20tells%20you%20nothing%2C%20and%20is%20why%20a%20column%20of%20product%20codes%20sometimes%20turns%0A%20%20%20%20into%20dates.%20%60COPY%20...%20FROM%60%20is%20the%20version%20where%20you%20specify%20what%20each%20column%20is%20first%20and%0A%20%20%20%20the%20file%20has%20to%20agree.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20add%20a%20row%20to%20%60project.csv%60%20whose%20budget%20is%20%60-5%60%20and%20run%20the%20%60COPY%60%0A%20%20%20%20again.%20Then%20run%20the%20%60CREATE%20TABLE%20...%20AS%20SELECT%60%20on%20the%20same%20file%20and%20compare%20what%0A%20%20%20%20each%20one%20did%20with%20that%20row.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Parents%20before%20children%0A%0A%20%20%20%20%60expense_loaded.project_code%60%20is%20a%20foreign%20key%20into%20%60project_loaded.code%60.%20Load%20the%0A%20%20%20%20expenses%20first%20and%20every%20row%20is%20an%20orphan%2C%20so%20the%20database%20refuses%20the%20whole%20file%3A%0A%0A%20%20%20%20%60%60%60sql%0A%20%20%20%20COPY%20expense_loaded%20(project_code%2C%20spent_on%2C%20amount%2C%20category)%0A%20%20%20%20FROM%20'expense.csv'%20(HEADER)%3B%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20%60%60%60%0A%20%20%20%20Constraint%20Error%3A%20Violates%20foreign%20key%20constraint%20because%20key%20%22code%3A%20AURORA%22%20does%20not%20exist%20in%20the%20referenced%20table%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20So%20a%20load%20runs%20down%20the%20diagram%3A%20every%20table%20with%20no%20foreign%20keys%20first%2C%20then%20the%0A%20%20%20%20tables%20that%20point%20at%20those%2C%20and%20the%20intersection%20tables%20last.%20A%20drop%20runs%20the%20other%0A%20%20%20%20way%2C%20which%20is%20why%20%C2%A724's%20foreign-key%20cell%20drops%20%60ord%60%20before%20%60customer%60.%0A%0A%20%20%20%20An%20ERD%20read%20top%20to%20bottom%20is%20the%20loading%20order.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20take%20the%20ERD%20you%20drew%20for%20Chapter%20A's%20chapter%20challenge%20and%20write%20out%0A%20%20%20%20the%20order%20its%20tables%20have%20to%20be%20loaded%20in.%20Name%20any%20table%20whose%20position%20you%20cannot%0A%20%20%20%20decide%20from%20the%20diagram%20alone.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Count%20the%20rows%20and%20compare%0A%0A%20%20%20%20A%20load%20that%20raises%20no%20error%20is%20not%20a%20load%20that%20worked.%20The%20check%20is%20one%20query%20that%0A%20%20%20%20counts%20the%20rows%20in%20the%20file%20and%20the%20rows%20in%20the%20table.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(csv_dir%2C%20db)%3A%0A%20%20%20%20load_counts%20%3D%20db.sql(f%22%22%22%0A%20%20%20%20%20%20%20%20SELECT%20(SELECT%20count(*)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20FROM%20read_csv_auto('%7B(csv_dir%20%2F%20'project.csv').as_posix()%7D'))%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20AS%20project_rows_in_the_file%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20(SELECT%20count(*)%20FROM%20project_loaded)%20%20AS%20project_rows_in_the_table%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20(SELECT%20count(*)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20FROM%20read_csv_auto('%7B(csv_dir%20%2F%20'expense.csv').as_posix()%7D'))%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20AS%20expense_rows_in_the_file%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20(SELECT%20count(*)%20FROM%20expense_loaded)%20%20AS%20expense_rows_in_the_table%0A%20%20%20%20%22%22%22)%0A%20%20%20%20load_counts%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20The%20counts%20match%2C%20three%20and%20three%20for%20projects%20and%20four%20and%20four%20for%20expenses.%20Run%0A%20%20%20%20this%20after%20every%20load%2C%20on%20every%20table%2C%20and%20write%20the%20numbers%20down.%0A%0A%20%20%20%20The%20same%20move%20catches%20a%20join%20that%20duplicated%20rows%2C%20which%20is%20Part%20C%20%C2%A719%2C%20and%20it%20is%20what%0A%20%20%20%20goes%20in%20the%20*How%20I%20know%20these%20numbers%20are%20right*%20section%20every%20deliverable%20in%20this%0A%20%20%20%20course%20carries.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20delete%20the%20header%20line%20from%20%60project.csv%60%2C%20load%20it%20again%20with%0A%20%20%20%20%60(HEADER)%60%20still%20set%2C%20and%20see%20what%20the%20two%20counts%20say.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20When%20a%20load%20goes%20wrong%0A%0A%20%20%20%20Most%20of%20these%20stop%20the%20load%20and%20name%20themselves.%20The%20last%20is%20the%20one%20to%20watch%2C%0A%20%20%20%20because%20it%20loads%20cleanly.%0A%0A%20%20%20%20**A%20foreign%20key%20with%20no%20parent.**%20The%20message%20and%20the%20fix%20are%20two%20knowledge%20points%0A%20%20%20%20above.%0A%0A%20%20%20%20**A%20column%20count%20that%20does%20not%20match.**%20%60COPY%20expense_loaded%20FROM%20'expense.csv'%60%20with%0A%20%20%20%20no%20column%20list%20gives%20a%20table%20of%20five%20columns%20a%20file%20of%20four%3A%0A%0A%20%20%20%20%60%60%60%0A%20%20%20%20Invalid%20Input%20Error%3A%20Error%20when%20sniffing%20file%20%22expense.csv%22.%0A%20%20%20%20...%0A%20%20%20%20*%20Columns%20are%20set%20as%3A%20%22columns%20%3D%20%7B%20'id'%20%3A%20'INTEGER'%2C%20'project_code'%20%3A%20'VARCHAR'%2C%20...%20%7D%22%2C%0A%20%20%20%20%20%20and%20they%20contain%3A%205%20columns.%20It%20does%20not%20match%20the%20number%20of%20columns%20found%20by%20the%0A%20%20%20%20%20%20sniffer%3A%204.%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20The%20fix%20is%20the%20column%20list%2C%20%60COPY%20expense_loaded%20(project_code%2C%20spent_on%2C%20amount%2C%0A%20%20%20%20category)%20FROM%20...%60.%0A%0A%20%20%20%20**A%20file%20that%20is%20not%20UTF-8.**%20Anything%20exported%20from%20an%20older%20Windows%20tool%20may%20be%0A%20%20%20%20Latin-1%2C%20and%20one%20accented%20character%20is%20enough%3A%0A%0A%20%20%20%20%60%60%60%0A%20%20%20%20Invalid%20Input%20Error%3A%20CSV%20Error%20on%20Line%3A%202%0A%20%20%20%20Invalid%20unicode%20(byte%20sequence%20mismatch)%20detected.%20This%20file%20is%20not%20utf-8%20encoded.%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20The%20fix%20is%20%60read_csv_auto('file.csv'%2C%20encoding%3D'latin-1')%60%2C%20and%20the%20better%20fix%20is%20to%0A%20%20%20%20ask%20whoever%20produced%20the%20file%20to%20export%20UTF-8.%0A%0A%20%20%20%20**A%20date%20in%20the%20other%20order.**%20This%20one%20is%20below.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(csv_dir%2C%20db)%3A%0A%20%20%20%20(csv_dir%20%2F%20%22us-dates.csv%22).write_text(%0A%20%20%20%20%20%20%20%20%22code%2Cstarted%5CnAURORA%2C03%2F04%2F2026%5CnBEACON%2C07%2F11%2F2026%5Cn%22%0A%20%20%20%20)%0A%20%20%20%20_path%20%3D%20(csv_dir%20%2F%20%22us-dates.csv%22).as_posix()%0A%20%20%20%20us_dates%20%3D%20db.sql(f%22%22%22%0A%20%20%20%20%20%20%20%20SELECT%20a.code%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20a.started%20AS%20read_with_the_default%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20b.started%20AS%20read_as_month_day%0A%20%20%20%20%20%20%20%20FROM%20read_csv_auto('%7B_path%7D')%20a%0A%20%20%20%20%20%20%20%20JOIN%20read_csv_auto('%7B_path%7D'%2C%20dateformat%20%3D%20'%25m%2F%25d%2F%25Y')%20b%20USING%20(code)%0A%20%20%20%20%20%20%20%20ORDER%20BY%20a.code%0A%20%20%20%20%22%22%22)%0A%20%20%20%20us_dates%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20That%20file%20came%20off%20a%20US%20machine%2C%20where%20%6003%2F04%2F2026%60%20is%20the%20fourth%20of%20March.%20DuckDB%0A%20%20%20%20read%20it%20as%20the%20third%20of%20April.%20There%20was%20no%20error%20and%20no%20warning%2C%20the%20column%20came%20out%0A%20%20%20%20as%20a%20%60DATE%60%2C%20and%20every%20date%20in%20it%20is%20wrong.%0A%0A%20%20%20%20Nothing%20in%20the%20file%20says%20which%20order%20it%20is%20in%2C%20so%20the%20reader%20picked%20day%20before%20month%0A%20%20%20%20and%20both%20rows%20happened%20to%20be%20readable%20that%20way.%20A%20row%20with%20%6004%2F13%2F2026%60%20in%20it%20would%0A%20%20%20%20have%20forced%20the%20other%20reading%2C%20and%20a%20file%20with%20no%20day%20above%2012%20gives%20the%20reader%20nothing%0A%20%20%20%20to%20go%20on.%0A%0A%20%20%20%20The%20fix%20is%20to%20specify%20the%20order%3A%20%60read_csv_auto('file.csv'%2C%20dateformat%20%3D%20'%25m%2F%25d%2F%25Y')%60.%20The%20habit%0A%20%20%20%20is%20to%20read%20five%20dates%20you%20can%20check%20against%20something%20else%20before%20believing%20the%0A%20%20%20%20column.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20add%20a%20row%20with%20%6004%2F13%2F2026%60%20to%20that%20file%20and%20read%20it%20again%20with%20the%0A%20%20%20%20default.%20Then%20take%20a%20real%20export%20from%20a%20system%20you%20use%2C%20look%20at%20one%20date%20column%2C%20and%0A%20%20%20%20state%20which%20order%20it%20is%20in%20and%20how%20you%20know.%0A%0A%20%20%20%20%23%23%23%20%E2%9C%94%20Check%20your%20understanding%0A%0A%20%20%20%201.%20%60CREATE%20TABLE%20...%20AS%20SELECT%60%20and%20%60COPY%20...%20FROM%60%20both%20end%20with%20a%20table%20full%20of%0A%20%20%20%20%20%20%20rows.%20What%20does%20each%20one%20decide%20that%20the%20other%20does%20not%3F%0A%20%20%20%202.%20A%20load%20of%2040%2C000%20rows%20raises%20no%20error.%20Name%20the%20one%20query%20you%20run%20next%20and%20state%20what%0A%20%20%20%20%20%20%20two%20numbers%20you%20are%20comparing.%0A%20%20%20%203.%20You%20have%20four%20CSV%20files%20and%20an%20ERD.%20How%20do%20you%20work%20out%20the%20order%20to%20load%20them%20in%3F%0A%20%20%20%204.%20A%20date%20column%20loaded%20without%20an%20error.%20Give%20two%20reasons%20that%20is%20not%20enough.%0A%20%20%20%205.%20A%20colleague's%20file%20will%20not%20load%20because%20of%20an%20encoding%20error.%20Name%20the%20fix%20you%20can%0A%20%20%20%20%20%20%20apply%20yourself%20and%20the%20fix%20you%20would%20rather%20have.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20---%0A%20%20%20%20%23%23%2027.%20Running%20SQL%20from%20a%20notebook%0A%0A%20%20%20%20Every%20query%20in%20this%20book%20runs%20from%20a%20notebook%20cell.%20This%20section%20covers%20what%20you%20type%2C%0A%20%20%20%20what%20comes%20back%2C%20and%20how%20a%20value%20that%20lives%20in%20Python%20reaches%20a%20query.%0A%0A%20%20%20%20%F0%9F%93%96%20**Used%20in%3A**%20every%20session%20that%20runs%20a%20query%2C%20from%20*From%20a%20CSV%20file%20to%20a%20dataframe*%20to%20the%20end%20of%20term.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20There%20is%20no%20server%0A%0A%20%20%20%20DuckDB%20is%20a%20Python%20library.%20%60import%20duckdb%60%20is%20the%20whole%20installation%2C%20and%0A%20%20%20%20%60duckdb.connect()%60%20with%20no%20argument%20opens%20a%20database%20that%20lives%20in%20memory%20for%20as%20long%0A%20%20%20%20as%20the%20notebook%20is%20running.%20There%20is%20no%20host%20name%2C%20no%20port%2C%20no%20user%20name%2C%20no%20password%0A%20%20%20%20and%20nothing%20to%20be%20granted%20access%20to.%0A%0A%20%20%20%20The%20course%20warehouse%20is%2013%20parquet%20files%20sitting%20in%20this%20repository.%20Opening%20it%20is%20one%0A%20%20%20%20%60CREATE%20TABLE%20...%20AS%20SELECT%20*%20FROM%20'file.parquet'%60%20per%20file%2C%20and%20%C2%A726's%20first%20knowledge%0A%20%20%20%20point%20is%20that%20statement.%20The%20first%20cell%20of%20this%20notebook%20runs%20it%20for%20the%20three%20tables%0A%20%20%20%20this%20chapter%20reads.%0A%0A%20%20%20%20If%20you%20have%20used%20a%20database%20at%20work%2C%20the%20arrangement%20here%20is%20a%20different%20one.%20A%0A%20%20%20%20database%20there%20is%20a%20machine%20somebody%20else%20runs.%20Here%20the%20engine%20is%20a%20library%20inside%0A%20%20%20%20your%20own%20process%2C%20an%20**in-process**%20or%20**embedded**%20database%2C%20and%20the%20data%20is%20a%20set%20of%0A%20%20%20%20files%20you%20already%20have.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(con)%3A%0A%20%20%20%20con.sql(%22SELECT%20database_name%2C%20path%20FROM%20duckdb_databases()%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%60memory%60%20is%20where%20the%20three%20Northwind%20tables%20were%20loaded%2C%20and%20its%20%60path%60%20is%20%60NULL%60%0A%20%20%20%20because%20no%20file%20is%20behind%20it.%20A%20database%20at%20that%20level%20is%20a%20**catalog**%2C%20a%20catalog%0A%20%20%20%20holds%20a%20**schema**%2C%20and%20a%20schema%20holds%20the%20tables%2C%20so%20%60customers%60%20written%20out%20in%20full%0A%20%20%20%20is%20%60memory.main.customers%60.%20Give%20%60duckdb.connect()%60%20a%20filename%20and%20that%20column%20fills%0A%20%20%20%20in%2C%20and%20the%20tables%20are%20still%20there%20tomorrow.%0A%0A%20%20%20%20%23%23%23%20In%20Excel%20terms%0A%0A%20%20%20%20Opening%20a%20workbook%20needs%20no%20login%20either.%20A%20spreadsheet%20is%20a%20file%20and%20an%20application%2C%0A%20%20%20%20and%20this%20is%20a%20file%20and%20a%20library.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20run%20%60duckdb.connect(%22%2Ftmp%2Fscratch.duckdb%22)%60%20in%20a%20cell%2C%20create%20one%20table%0A%20%20%20%20in%20it%2C%20then%20open%20a%20second%20connection%20to%20the%20same%20path%20and%20read%20the%20table%20back.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(%22%22%22%0A%20%20%20%20%23%23%23%20A%20%60mo.sql%60%20cell%0A%0A%20%20%20%20marimo%20has%20a%20SQL%20cell.%20Add%20one%20in%20the%20editor%20and%20the%20file%20gets%20this%3A%0A%0A%20%20%20%20%60%60%60python%0A%20%20%20%20french_customers%20%3D%20mo.sql(%0A%20%20%20%20%20%20%20%20%22%5C%22%22%0A%20%20%20%20%20%20%20%20SELECT%20%22CustomerID%22%2C%20%22CompanyName%22%2C%20%22City%22%0A%20%20%20%20%20%20%20%20FROM%20customers%0A%20%20%20%20%20%20%20%20WHERE%20%22Country%22%20%3D%20'France'%0A%20%20%20%20%20%20%20%20%22%5C%22%22%2C%0A%20%20%20%20%20%20%20%20engine%3Dcon%2C%0A%20%20%20%20)%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20On%20screen%20that%20is%20a%20query%20box%20with%20syntax%20coloring%2C%20a%20picker%20for%20which%20database%20to%20run%0A%20%20%20%20against%2C%20and%20a%20field%20naming%20the%20Python%20variable%20the%20result%20is%20bound%20to.%0A%20%20%20%20%60french_customers%60%20is%20an%20ordinary%20Python%20name%20holding%20a%20polars%20DataFrame%2C%20and%20every%0A%20%20%20%20cell%20downstream%20can%20use%20it.%0A%0A%20%20%20%20Three%20things%20about%20it%20are%20worth%20knowing.%20It%20runs%20only%20inside%20marimo%2C%20so%20the%20same%20cell%0A%20%20%20%20raises%20in%20a%20plain%20Python%20script%20and%20in%20Jupyter.%20What%20kind%20of%20frame%20it%20hands%20back%20is%20set%0A%20%20%20%20at%20the%20top%20of%20the%20notebook%20file%3A%20every%20notebook%20in%20this%20course%20sets%0A%20%20%20%20%60sql_output%3D%22polars%22%60%2C%20and%20a%20notebook%20that%20sets%20%60sql_output%3D%22pandas%22%60%20hands%20back%20pandas%0A%20%20%20%20from%20the%20same%20query.%20And%20the%20only%20route%20a%20Python%20value%20has%20into%20the%20query%20is%20an%0A%20%20%20%20f-string%2C%20which%20the%20last%20knowledge%20point%20in%20this%20section%20is%20about.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20%60con.sql%60%20runs%20anywhere%0A%0A%20%20%20%20%60con.sql(%22...%22)%60%20is%20the%20same%20query%20without%20the%20cell%20UI.%20It%20runs%20in%20a%20marimo%20cell%2C%20in%20a%0A%20%20%20%20plain%20script%20and%20in%20Jupyter%2C%20so%20it%20is%20what%20every%20runnable%20cell%20in%20this%20book%20uses.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(con)%3A%0A%20%20%20%20france%20%3D%20con.sql(%22%22%22%0A%20%20%20%20%20%20%20%20SELECT%20%22CustomerID%22%2C%20%22CompanyName%22%2C%20%22City%22%0A%20%20%20%20%20%20%20%20FROM%20customers%0A%20%20%20%20%20%20%20%20WHERE%20%22Country%22%20%3D%20'France'%0A%20%20%20%20%20%20%20%20ORDER%20BY%20%22CustomerID%22%0A%20%20%20%20%22%22%22)%0A%20%20%20%20france%0A%20%20%20%20return%20(france%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20There%20are%2011%20French%20customers.%20The%20last%20line%20of%20a%20cell%20is%20what%20marimo%20displays%2C%20so%0A%20%20%20%20naming%0A%20%20%20%20%60france%60%20on%20its%20own%20is%20what%20put%20the%20table%20on%20the%20page.%20Assigning%20it%20and%20stopping%20there%0A%20%20%20%20would%20have%20run%20the%20query%20and%20shown%20nothing.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20change%20%60'France'%60%20to%20%60'Brazil'%60%20and%20count%20the%20rows.%20Then%20delete%20the%0A%20%20%20%20final%20%60france%60%20line%20and%20run%20the%20cell%20again.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20What%20comes%20back%20is%20a%20Python%20object%0A%0A%20%20%20%20%60con.sql%60%20hands%20back%20a%20**DuckDB%20relation%20object**%2C%20which%20is%20a%20description%20of%20a%20query%0A%20%20%20%20that%20has%20not%20necessarily%20run%20yet.%20It%20carries%20the%20column%20names%20and%20types%2C%20and%20it%20turns%0A%20%20%20%20into%20rows%20when%20something%20asks%20for%20them.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(con%2C%20france)%3A%0A%20%20%20%20con.sql(f%22%22%22%0A%20%20%20%20%20%20%20%20SELECT%20'%7Btype(france).__name__%7D'%20AS%20python_type%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7Bfrance.shape%5B0%5D%7D%20%20%20%20%20%20%20%20%20AS%20rows%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7Bfrance.shape%5B1%5D%7D%20%20%20%20%20%20%20%20%20AS%20columns%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20'%7Bfrance.columns%5B1%5D%7D'%20%20%20%20%20AS%20second_column%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Because%20it%20is%20a%20Python%20variable%2C%20the%20next%20query%20can%20name%20it%20as%20though%20it%20were%20a%20table.%0A%20%20%20%20DuckDB%20looks%20in%20the%20calling%20cell%20for%20any%20name%20it%20does%20not%20recognize.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(con%2C%20france)%3A%0A%20%20%20%20france%20%20%23%20the%20query%20below%20finds%20this%20name%20in%20the%20cell%0A%20%20%20%20con.sql(%22%22%22%0A%20%20%20%20%20%20%20%20SELECT%20%22City%22%2C%20count(*)%20AS%20customers%0A%20%20%20%20%20%20%20%20FROM%20france%0A%20%20%20%20%20%20%20%20GROUP%20BY%20%22City%22%0A%20%20%20%20%20%20%20%20ORDER%20BY%20customers%20DESC%2C%20%22City%22%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20More%20ways%20out%20of%20a%20relation%2C%20for%20when%20you%20want%20plain%20Python%20instead%20of%20a%20table%20on%20the%0A%20%20%20%20page%3A%0A%0A%20%20%20%20%7C%20%7C%20gives%20you%20%7C%0A%20%20%20%20%7C---%7C---%7C%0A%20%20%20%20%7C%20%60.fetchall()%60%20%7C%20a%20list%20of%20tuples%2C%20one%20per%20row%2C%20so%20%60.fetchall()%5B0%5D%5B0%5D%60%20is%20a%20single%20number%20%7C%0A%20%20%20%20%7C%20%60.pl()%60%20%7C%20a%20polars%20DataFrame%20%7C%0A%20%20%20%20%7C%20%60.df()%60%20%7C%20a%20pandas%20DataFrame%20%7C%0A%0A%20%20%20%20**Older%20code%20calls%20%60.fetchone()%60%20on%20a%20relation**%2C%20and%20so%20does%20much%20of%20what%20an%20agent%0A%20%20%20%20writes.%20DuckDB%202.0%20removed%20it%2C%20and%20the%20call%20stops%20with%20an%20%60AttributeError%60.%0A%20%20%20%20%60.fetchall()%5B0%5D%5B0%5D%60%20gives%20the%20same%20number%20in%20every%20version.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(con%2C%20france%2C%20mo)%3A%0A%20%20%20%20france%20%20%23%20the%20query%20below%20finds%20this%20name%20in%20the%20cell%0A%20%20%20%20n_french%20%3D%20con.sql(%22SELECT%20count(*)%20FROM%20france%22).fetchall()%5B0%5D%5B0%5D%0A%20%20%20%20first_row%20%3D%20con.sql(%22SELECT%20*%20FROM%20france%20LIMIT%201%22).fetchall()%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20f%22%60fetchall()%5B0%5D%5B0%5D%60%20gives%20a%20plain%20Python%20%60%7Btype(n_french).__name__%7D%60%20worth%20%22%0A%20%20%20%20%20%20%20%20f%22**%7Bn_french%7D**%2C%20and%20%60fetchall()%60%20gives%20%60%7Bfirst_row%7D%60.%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20hold%20%60SELECT%20*%20FROM%20orders%20WHERE%20%22ShippedDate%22%20IS%20NULL%60%20in%20a%20variable%2C%0A%20%20%20%20then%20write%20a%20second%20query%20that%20names%20that%20variable%20and%20counts%20the%20rows%20by%0A%20%20%20%20%60%22CustomerID%22%60.%20Then%20get%20the%20same%20count%20out%20with%20%60.fetchall()%5B0%5D%5B0%5D%60.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Parameterized%20queries%20and%20the%20%60%3F%60%20placeholder%0A%0A%20%20%20%20A%20query%20is%20a%20string%2C%20so%20the%20obvious%20way%20to%20put%20a%20Python%20value%20into%20one%20is%20an%20f-string.%0A%20%20%20%20It%20works%20until%20the%20value%20contains%20an%20apostrophe.%0A%0A%20%20%20%20%60%60%60python%0A%20%20%20%20company%20%3D%20%22La%20corne%20d'abondance%22%0A%20%20%20%20con.sql(f'''SELECT%20%22CustomerID%22%20FROM%20customers%20WHERE%20%22CompanyName%22%20%3D%20'%7Bcompany%7D'%20''')%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20%60%60%60%0A%20%20%20%20Parser%20Error%3A%20syntax%20error%20at%20or%20near%20%22abondance%22%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20The%20apostrophe%20in%20the%20name%20closed%20the%20string%20early%2C%20and%20the%20parser%20tried%20to%20read%0A%20%20%20%20%60abondance%60%20as%20SQL.%20The%20next%20cell%20counts%20how%20many%20names%20in%20this%20warehouse%20would%20do%0A%20%20%20%20the%20same.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(con)%3A%0A%20%20%20%20con.sql(%22%22%22%0A%20%20%20%20%20%20%20%20SELECT%20(SELECT%20count(*)%20FROM%20customers%20WHERE%20%22CompanyName%22%20LIKE%20'%25''%25')%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20AS%20customers_with_an_apostrophe%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20(SELECT%20count(*)%20FROM%20products%20%20WHERE%20%22ProductName%22%20LIKE%20'%25''%25')%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20AS%20products_with_an_apostrophe%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Six%20customers%20and%20nine%20products%20carry%20one%2C%20among%20them%20%60Bon%20app'%60%2C%20%60Chef%20Anton's%20Cajun%0A%20%20%20%20Seasoning%60%2C%20%60Grandma's%20Boysenberry%20Spread%60%20and%20%60Sirop%20d'%C3%A9rable%60.%20Any%20filter%20built%20by%20f-string%0A%20%20%20%20breaks%20on%20all%2015%20of%20them.%0A%0A%20%20%20%20The%20fix%20is%20a%20**placeholder**%2C%20which%20makes%20this%20a%20**parameterized%20query**%20and%20the%20value%0A%20%20%20%20a%20**bind%20parameter**.%20Write%20%60%3F%60%20where%20the%20value%20goes%2C%20hand%20the%20value%20to%20%60con.execute%60%0A%20%20%20%20as%20a%20list%2C%20and%20the%20value%20travels%20beside%20the%20query%20instead%20of%20inside%20it.%0A%20%20%20%20The%20parser%20never%20sees%20it%2C%20so%20an%20apostrophe%20is%20just%20a%20character.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(con%2C%20mo)%3A%0A%20%20%20%20company%20%3D%20%22La%20corne%20d'abondance%22%0A%20%20%20%20_rows%20%3D%20con.execute(%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20SELECT%20%22CustomerID%22%2C%20%22CompanyName%22%2C%20%22Country%22%0A%20%20%20%20%20%20%20%20FROM%20customers%0A%20%20%20%20%20%20%20%20WHERE%20%22CompanyName%22%20%3D%20%3F%0A%20%20%20%20%20%20%20%20%22%22%22%2C%0A%20%20%20%20%20%20%20%20%5Bcompany%5D%2C%0A%20%20%20%20).fetchall()%0A%20%20%20%20mo.md(f%22The%20placeholder%20form%20returns%20%60%7B_rows%7D%60.%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20**The%20trap.**%20%60mo.sql%60%20does%20not%20take%20placeholders.%20The%20same%20%60%3F%60%20in%20a%20SQL%20cell%20gives%3A%0A%0A%20%20%20%20%60%60%60%0A%20%20%20%20Binder%20Error%3A%20Unexpected%20prepared%20parameter.%20This%20type%20of%20statement%20can't%20be%20prepared!%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20So%20a%20query%20whose%20value%20came%20out%20of%20data%20goes%20through%20%60con.execute%60.%20A%20%60mo.sql%60%20cell%20is%0A%20%20%20%20for%20a%20query%20you%20wrote%20by%20hand.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20run%20the%20f-string%20version%20and%20the%20placeholder%20version%20on%20%60Bon%20app'%60%20and%0A%20%20%20%20read%20both%20outcomes.%20Then%20set%20%60company%60%20to%20%60Alfreds%20Futterkiste%60%20and%20confirm%20the%0A%20%20%20%20f-string%20version%20works.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Input%20from%20somebody%20else%0A%0A%20%20%20%20A%20notebook%20that%20takes%20a%20value%20from%20whoever%20uses%20it%2C%20through%20a%20text%20box%20or%20a%20dropdown%2C%0A%20%20%20%20puts%20that%20value%20into%20a%20query.%20Built%20with%20an%20f-string%2C%20whatever%20was%20typed%20becomes%20part%0A%20%20%20%20of%20the%20SQL%2C%20so%20SQL%20typed%20into%20the%20box%20runs.%20This%20is%20called%20**SQL%20injection**.%0A%0A%20%20%20%20The%20cell%20below%20counts%20one%20customer's%20orders%20twice%2C%20once%20through%20an%20f-string%20and%20once%0A%20%20%20%20through%20a%20placeholder.%20It%20does%20that%20for%20a%20real%20customer%20ID%20and%20for%20an%20input%20written%20to%0A%20%20%20%20break%20out%20of%20the%20quotes.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(con%2C%20mo)%3A%0A%20%20%20%20def%20count_orders(customer_id)%3A%0A%20%20%20%20%20%20%20%20%23%20the%20f-string%20writes%20whatever%20was%20typed%20into%20the%20SQL%20itself%0A%20%20%20%20%20%20%20%20return%20con.sql(%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22%22%22SELECT%20count(*)%20FROM%20orders%20WHERE%20%22CustomerID%22%20%3D%20'%7Bcustomer_id%7D'%22%22%22%0A%20%20%20%20%20%20%20%20).fetchall()%5B0%5D%5B0%5D%0A%0A%20%20%20%20def%20count_orders_safely(customer_id)%3A%0A%20%20%20%20%20%20%20%20return%20con.execute(%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22SELECT%20count(*)%20FROM%20orders%20WHERE%20%22CustomerID%22%20%3D%20%3F%22%22%22%2C%20%5Bcustomer_id%5D%0A%20%20%20%20%20%20%20%20).fetchall()%5B0%5D%5B0%5D%0A%0A%20%20%20%20_rows%20%3D%20%5B%0A%20%20%20%20%20%20%20%20f%22%7C%20%60%7B_typed%7D%60%20%7C%20%7Bcount_orders(_typed)%3A%2C%7D%20%7C%20%7Bcount_orders_safely(_typed)%3A%2C%7D%20%7C%22%0A%20%20%20%20%20%20%20%20for%20_typed%20in%20%5B%22ALFKI%22%2C%20%22x'%20OR%20'1'%3D'1%22%5D%0A%20%20%20%20%5D%0A%20%20%20%20mo.md(%22%7C%20typed%20%7C%20f-string%20%7C%20placeholder%20%7C%5Cn%7C---%7C---%7C---%7C%5Cn%22%20%2B%20%22%5Cn%22.join(_rows))%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%60ALFKI%60%20has%20163%20orders%2C%20and%20both%20versions%20find%20them.%20The%20second%20input%20closes%20the%20quote%0A%20%20%20%20early%2C%20and%20%60OR%20'1'%3D'1'%60%20is%20true%20on%20every%20row%2C%20so%20the%20f-string%20version%20counts%20all%2016%2C282%0A%20%20%20%20orders%20in%20the%20warehouse.%20The%20placeholder%20version%20looks%20for%20a%20customer%20whose%20ID%20is%20that%0A%20%20%20%20whole%20string%2C%20and%20there%20is%20none.%0A%0A%20%20%20%20The%20classic%20case%20is%20a%20login%20form.%20A%20username%20ending%20in%20%60'--%60%20turns%20the%20rest%20of%20the%0A%20%20%20%20query%2C%20the%20password%20check%20included%2C%20into%20a%20comment.%20Any%20value%20that%20comes%20from%20outside%0A%20%20%20%20your%20code%20goes%20into%20a%20query%20through%20%60%3F%60.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20add%20%60ALFKI'%20--%60%20as%20a%20third%20input%20and%20predict%20both%20counts%20before%20you%20run%0A%20%20%20%20it.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20%E2%9C%94%20Check%20your%20understanding%0A%0A%20%20%20%201.%20Name%20one%20thing%20a%20%60mo.sql%60%20cell%20gives%20you%20that%20%60con.sql%60%20does%20not%2C%20and%20one%20thing%0A%20%20%20%20%20%20%20%60con.sql%60%20gives%20you%20that%20%60mo.sql%60%20does%20not.%0A%20%20%20%202.%20%60con.sql%60%20returned%20something%20and%20the%20cell%20showed%20nothing.%20Name%20two%20different%0A%20%20%20%20%20%20%20reasons.%0A%20%20%20%203.%20Your%20f-string%20query%20has%20worked%20on%2087%20customer%20names.%20What%20do%20you%20know%20about%20the%0A%20%20%20%20%20%20%20other%206%3F%0A%20%20%20%204.%20Somebody%20asks%20where%20the%20database%20server%20is.%20What%20do%20you%20tell%20them%3F%0A%20%20%20%205.%20You%20need%20the%20count%20as%20a%20number%20to%20put%20in%20a%20sentence.%20Which%20method%20gets%20it%20out%20of%0A%20%20%20%20%20%20%20the%20relation%3F%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20---%0A%20%20%20%20%23%23%2028.%20Views%0A%0A%20%20%20%20A%20**view**%20is%20a%20query%20with%20a%20name.%20Selecting%20from%20the%20view%20runs%20the%20query%20underneath%0A%20%20%20%20and%20hands%20back%20the%20rows.%20No%20rows%20are%20stored%2C%20so%20a%20view%20costs%20almost%20nothing%20to%20create%0A%20%20%20%20and%20is%20never%20out%20of%20date.%0A%0A%20%20%20%20%F0%9F%93%96%20**Used%20in%3A**%20Building%20and%20querying%20your%20database%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Creating%20one%0A%0A%20%20%20%20Chapter%20A's%20warehouse%20holds%2016%2C282%20orders%2C%20and%20one%20recurring%20question%20about%20them%20is%0A%20%20%20%20how%20many%20shipped%20after%20the%20date%20they%20were%20required.%20As%20a%20view%2C%20that%20question%20gets%20a%0A%20%20%20%20name%20every%20other%20query%20can%20use.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(con)%3A%0A%20%20%20%20con.execute(%22%22%22%0A%20%20%20%20%20%20%20%20CREATE%20OR%20REPLACE%20VIEW%20late_orders%20AS%0A%20%20%20%20%20%20%20%20SELECT%20%22OrderID%22%2C%20%22CustomerID%22%2C%20%22OrderDate%22%2C%20%22RequiredDate%22%2C%20%22ShippedDate%22%0A%20%20%20%20%20%20%20%20FROM%20orders%0A%20%20%20%20%20%20%20%20WHERE%20%22ShippedDate%22%20%3E%20%22RequiredDate%22%0A%20%20%20%20%22%22%22)%0A%20%20%20%20late_orders%20%3D%20con.sql(%22%22%22%0A%20%20%20%20%20%20%20%20SELECT%20count(*)%20AS%20late%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20(SELECT%20count(*)%20FROM%20orders)%20AS%20all_orders%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20round(100.0%20*%20count(*)%20%2F%20(SELECT%20count(*)%20FROM%20orders)%2C%201)%20AS%20percent%0A%20%20%20%20%20%20%20%20FROM%20late_orders%0A%20%20%20%20%22%22%22)%0A%20%20%20%20late_orders%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(con)%3A%0A%20%20%20%20con.sql(%22%22%22SELECT%20*%20FROM%20late_orders%20ORDER%20BY%20%22OrderID%22%20LIMIT%205%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20The%20view%20holds%203%2C755%20of%2016%2C282%20orders%2C%20which%20is%2023.1%20percent.%20%60late_orders%60%20now%0A%20%20%20%20behaves%20like%20a%20table%20in%20every%20query%20in%20this%20notebook%2C%20including%20a%20join%20or%20a%0A%20%20%20%20%60GROUP%20BY%60.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20write%20%60SELECT%20%22CustomerID%22%2C%20count(*)%20FROM%20late_orders%20GROUP%20BY%0A%20%20%20%20%22CustomerID%22%20ORDER%20BY%202%20DESC%20LIMIT%205%60.%20Then%20write%20the%20same%20query%20without%20the%20view%20and%0A%20%20%20%20compare%20the%20two%20statements.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20A%20view%20re-runs%20every%20time%0A%0A%20%20%20%20Nothing%20is%20cached.%20The%20query%20underneath%20runs%20again%20on%20every%20select%2C%20so%20a%20view%20over%20a%0A%20%20%20%20table%20that%20changes%20gives%20a%20different%20answer%20after%20the%20change.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(db)%3A%0A%20%20%20%20db.execute(%22%22%22%0A%20%20%20%20%20%20%20%20CREATE%20OR%20REPLACE%20VIEW%20active_projects%20AS%0A%20%20%20%20%20%20%20%20SELECT%20code%2C%20name%2C%20budget%20FROM%20project%20WHERE%20status%20%3D%20'active'%0A%20%20%20%20%22%22%22)%0A%20%20%20%20_before%20%3D%20db.sql(%22SELECT%20count(*)%20FROM%20active_projects%22).fetchall()%5B0%5D%5B0%5D%0A%20%20%20%20db.execute(%22UPDATE%20project%20SET%20status%20%3D%20'closed'%20WHERE%20code%20%3D%20'AURORA'%22)%0A%20%20%20%20_after%20%3D%20db.sql(%22SELECT%20count(*)%20FROM%20active_projects%22).fetchall()%5B0%5D%5B0%5D%0A%20%20%20%20view_reruns%20%3D%20db.sql(f%22%22%22%0A%20%20%20%20%20%20%20%20SELECT%20%7B_before%7D%20AS%20rows_before_the_update%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7B_after%7D%20%20AS%20rows_after_the_update%0A%20%20%20%20%22%22%22)%0A%20%20%20%20view_reruns%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20The%20view%20held%20one%20row%20before%20the%20%60UPDATE%60%20and%20none%20after%2C%20from%20the%20same%20three%20words%0A%20%20%20%20of%20SQL.%20That%20is%20what%20a%20view%20is%20for.%20It%20is%20also%20why%20a%20view%20over%20a%20slow%20query%20is%20still%20slow.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20set%20%60AURORA%60%20back%20to%20%60active%60%20and%20read%20the%20view%20again%20without%20touching%0A%20%20%20%20the%20%60CREATE%20VIEW%60%20statement.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20What%20the%20database%20keeps%0A%0A%20%20%20%20A%20view%20is%20stored%20as%20text.%20%60duckdb_views()%60%20shows%20what%20the%20database%20wrote%20down.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(con)%3A%0A%20%20%20%20con.sql(%22SELECT%20view_name%2C%20sql%20FROM%20duckdb_views()%20WHERE%20NOT%20internal%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Editing%20a%20view%20means%20replacing%20that%20text%20with%20%60CREATE%20OR%20REPLACE%20VIEW%60%2C%20and%20removing%0A%20%20%20%20it%20is%20%60DROP%20VIEW%20late_orders%60.%20Neither%20touches%20a%20single%20row.%0A%0A%20%20%20%20**The%20trap.**%20A%20view%20looks%20like%20a%20table%20until%20you%20write%20to%20it%3A%0A%0A%20%20%20%20%60%60%60sql%0A%20%20%20%20INSERT%20INTO%20late_orders%20VALUES%20(99999%2C%20'VINET'%2C%20NULL%2C%20NULL%2C%20NULL)%3B%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20%60%60%60%0A%20%20%20%20Catalog%20Error%3A%20late_orders%20is%20not%20an%20table%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20Views%20in%20DuckDB%20are%20read-only.%20The%20rows%20live%20in%20the%20tables%20underneath%2C%20and%20that%20is%0A%20%20%20%20where%20an%20%60INSERT%60%20or%20an%20%60UPDATE%60%20has%20to%20go.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20create%20a%20view%20over%20%60late_orders%60%20itself%2C%20holding%20only%20the%20orders%20from%0A%20%20%20%202016%2C%20and%20confirm%20a%20view%20over%20a%20view%20works.%20Then%20drop%20%60late_orders%60%20and%20find%20out%20what%0A%20%20%20%20happens%20to%20the%20second%20one.%0A%0A%20%20%20%20%23%23%23%20%E2%9C%94%20Check%20your%20understanding%0A%0A%20%20%20%201.%20A%20view%20stores%20no%20rows.%20Name%20one%20thing%20that%20buys%20you%20and%20one%20thing%20it%20costs%20you.%0A%20%20%20%202.%20Somebody%20says%20a%20view%20will%20make%20a%20slow%20query%20fast.%20How%20do%20you%20answer%3F%0A%20%20%20%203.%20You%20have%20written%20the%20same%2012-line%20%60WHERE%60%20clause%20into%20four%20queries.%20What%20does%20a%0A%20%20%20%20%20%20%20view%20change%20about%20that%2C%20and%20what%20does%20it%20not%20change%3F%0A%20%20%20%204.%20A%20view%20is%20defined%20over%20a%20table%20and%20somebody%20drops%20the%20table.%20What%20do%20you%20expect%20to%0A%20%20%20%20%20%20%20happen%2C%20and%20when%3F%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20---%0A%20%20%20%20%23%23%20Chapter%20challenge%0A%0A%20%20%20%20Nothing%20here%20is%20due.%20Chapter%20A%20had%20you%20design%20a%20database.%20This%20is%20building%20it.%0A%0A%20%20%20%20**Take%20the%20ERD%20you%20drew%20for%20Chapter%20A's%20chapter%20challenge%20and%20make%20it%20real.**%20Write%20the%0A%20%20%20%20%60CREATE%20TABLE%60%20statements%2C%20invent%2010%20to%2030%20rows%20per%20table%20in%20a%20spreadsheet%2C%20export%20them%0A%20%20%20%20as%20CSV%2C%20and%20load%20them.%0A%0A%20%20%20%20**Done%20looks%20like**%20a%20notebook%20that%20runs%20from%20top%20to%20bottom%20on%20an%20empty%20database%20and%0A%20%20%20%20ends%20with%20a%20row-count%20check%20per%20table%20showing%20the%20file%20count%20and%20the%20table%20count%20side%0A%20%20%20%20by%20side.%20Every%20table%20has%20a%20primary%20key.%20Every%20relationship%20on%20your%20ERD%20is%20a%20declared%0A%20%20%20%20foreign%20key.%20Every%20money%20column%20is%20a%20%60DECIMAL%60%20and%20every%20date%20column%20is%20a%20%60DATE%60.%0A%0A%20%20%20%20**Five%20decisions%20are%20yours%2C%20and%20the%20notebook%20cannot%20make%20them.**%0A%0A%20%20%20%201.%20Which%20columns%20get%20a%20%60CHECK%60%2C%20and%20what%20does%20each%20one%20forbid%3F%20Write%20the%20rule%20down%20in%0A%20%20%20%20%20%20%20words%20first%2C%20then%20in%20SQL.%0A%20%20%20%202.%20Which%20ids%20do%20you%20invent%20and%20which%20do%20you%20let%20a%20sequence%20hand%20out%3F%20Chapter%20A%20%C2%A72%20is%0A%20%20%20%20%20%20%20the%20argument%3B%20this%20is%20where%20you%20commit%20to%20it.%0A%20%20%20%203.%20What%20order%20do%20the%20files%20load%20in%2C%20and%20what%20in%20the%20ERD%20told%20you%3F%0A%20%20%20%204.%20Which%20columns%20are%20%60NOT%20NULL%60%3F%20For%20each%20one%20you%20make%20optional%2C%20name%20the%20query%20that%0A%20%20%20%20%20%20%20has%20to%20handle%20the%20%60NULL%60.%0A%20%20%20%205.%20What%20does%20your%20row-count%20check%20do%20when%20the%20numbers%20disagree%3F%20Printing%20them%20is%20not%0A%20%20%20%20%20%20%20the%20same%20as%20noticing.%0A%0A%20%20%20%20**Going%20further.**%20Put%20a%20mistake%20in%20one%20of%20the%20CSV%20files%20on%20purpose%2C%20something%20that%0A%20%20%20%20loads%20without%20an%20error%2C%20such%20as%20a%20date%20in%20the%20other%20order%20or%20a%20budget%20that%20lost%20its%0A%20%20%20%20decimal%20point.%20Hand%20the%20notebook%20to%20somebody%20else%20in%20the%20course%20and%20ask%20them%20to%20find%0A%20%20%20%20it%20using%20only%20queries.%20Write%20down%20how%20long%20it%20took%20and%20which%20query%20found%20it.%20Then%20add%20a%0A%20%20%20%20view%20for%20each%20question%20your%20design%20was%20built%20to%20answer%2C%20and%20see%20whether%20the%20person%20who%0A%20%20%20%20reads%20your%20views%20can%20tell%20what%20the%20business%20does%20without%20reading%20the%20tables.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20---%0A%20%20%20%20%23%23%20%F0%9F%A4%96%20Ask%20your%20agent%0A%0A%20%20%20%20Two%20different%20uses%2C%20and%20the%20second%20one%20is%20the%20one%20this%20course%20grades.%0A%0A%20%20%20%20%23%23%23%20Ask%20it%20about%20what%20this%20chapter%20left%20out%0A%0A%20%20%20%20-%20Ask%20what%20an%20**index**%20is%2C%20why%20a%20primary%20key%20usually%20gets%20one%20without%20being%20asked%2C%20and%0A%20%20%20%20%20%20what%20having%20one%20costs%20on%20a%20table%20you%20write%20to%20often.%0A%20%20%20%20-%20Ask%20how%20to%20add%20a%20column%20to%20a%20table%20that%20already%20holds%20a%20million%20rows%2C%20what%0A%20%20%20%20%20%20%60ALTER%20TABLE%60%20can%20and%20cannot%20change%2C%20and%20what%20DuckDB%20does%20that%20SQLite%20does%20not.%0A%20%20%20%20-%20Ask%20what%20an%20**upsert**%20is%2C%20and%20show%20it%20%60INSERT%20...%20ON%20CONFLICT%20DO%20UPDATE%60.%20Ask%20when%0A%20%20%20%20%20%20you%20would%20want%20that%20instead%20of%20deleting%20and%20reloading.%0A%20%20%20%20-%20Ask%20what%20%60read_csv%60%20options%20like%20%60ignore_errors%60%2C%20%60null_padding%60%20and%20%60sample_size%60%0A%20%20%20%20%20%20do%2C%20and%20for%20each%20one%2C%20ask%20what%20it%20hides.%0A%20%20%20%20-%20Ask%20what%20a%20**materialized**%20view%20is%2C%20why%20it%20can%20be%20stale%2C%20and%20whether%20DuckDB%20has%20one.%0A%0A%20%20%20%20Test%20whatever%20comes%20back.%20Run%20it.%0A%0A%20%20%20%20%23%23%23%20Watch%20what%20it%20decides%20without%20telling%20you%0A%0A%20%20%20%20Give%20it%20this%2C%20exactly%3A%0A%0A%20%20%20%20%3E%20*%22Write%20the%20CREATE%20TABLE%20statements%20for%20a%20small%20consultancy%20that%20bills%20clients%20for%0A%20%20%20%20%3E%20projects.%20People%20book%20hours%20against%20projects%20and%20projects%20carry%20a%20budget.%22*%0A%0A%20%20%20%20Then%20run%20every%20statement%20it%20gives%20you%20against%20DuckDB%20and%20answer%20five%20questions.%0A%0A%20%20%20%201.%20**How%20many%20statements%20failed%20to%20parse%3F**%20Read%20each%20message.%0A%20%20%20%202.%20**What%20type%20did%20it%20pick%20for%20the%20budget%20and%20for%20the%20hourly%20rate%3F**%20If%20either%20one%20is%0A%20%20%20%20%20%20%20%60REAL%60%2C%20%60FLOAT%60%20or%20%60DOUBLE%60%2C%20ask%20it%20to%20add%20up%2010%20dimes.%0A%20%20%20%203.%20**Did%20it%20write%20%60AUTOINCREMENT%60%3F**%20That%20is%20the%20SQLite%20spelling%2C%20and%20it%20tells%20you%0A%20%20%20%20%20%20%20which%20engine%20it%20assumed.%20Ask%20which%20engine%20it%20was%20writing%20for%20and%20see%20whether%20the%0A%20%20%20%20%20%20%20answer%20matches%20what%20it%20wrote.%0A%20%20%20%204.%20**Did%20it%20declare%20foreign%20keys%2C%20and%20did%20it%20write%20%60ON%20DELETE%20CASCADE%60%3F**%20One%20of%20those%0A%20%20%20%20%20%20%20is%20a%20parser%20error%20here.%0A%20%20%20%205.%20**What%20did%20it%20store%20a%20date%20as%3F**%20If%20it%20is%20%60TEXT%60%2C%20ask%20it%20to%20find%20every%20project%20that%0A%20%20%20%20%20%20%20started%20in%20the%20last%20quarter.%0A%0A%20%20%20%20You%20asked%20for%20a%20schema%20and%20got%20a%20set%20of%20engine%20assumptions%2C%20and%20none%20of%20them%20was%20in%0A%20%20%20%20your%20request.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20send%20the%20same%20request%20again%20with%20one%20sentence%20added%2C%20*%22target%20DuckDB%22*%2C%0A%20%20%20%20and%20put%20the%20two%20schemas%20side%20by%20side.%20Then%20send%20it%20the%20parser%20error%20from%20the%20first%0A%20%20%20%20attempt%20and%20see%20whether%20the%20fix%20it%20offers%20changes%20anything%20else%20in%20the%20statement.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20---%0A%20%20%20%20%23%23%20Chapter%20D%20checklist%0A%0A%20%20%20%20-%20%5B%20%5D%20I%20can%20write%20a%20%60CREATE%20TABLE%60%20with%20a%20primary%20key%2C%20%60NOT%20NULL%60%2C%20%60DEFAULT%60%20and%0A%20%20%20%20%20%20%20%20%20%20%60CHECK%60%2C%20and%20state%20what%20each%20one%20refuses%0A%20%20%20%20-%20%5B%20%5D%20I%20can%20choose%20a%20type%20for%20money%2C%20for%20a%20date%20and%20for%20a%20code%2C%20and%20explain%20why%20%60REAL%60%20is%0A%20%20%20%20%20%20%20%20%20%20the%20wrong%20answer%20for%20the%20first%20one%0A%20%20%20%20-%20%5B%20%5D%20I%20can%20declare%20a%20foreign%20key%2C%20and%20I%20know%20which%20table%20has%20to%20be%20dropped%20first%0A%20%20%20%20-%20%5B%20%5D%20I%20can%20give%20a%20table%20an%20id%20without%20%60AUTOINCREMENT%60%0A%20%20%20%20-%20%5B%20%5D%20I%20can%20add%2C%20change%20and%20remove%20rows%2C%20and%20I%20write%20the%20%60WHERE%60%20before%20the%20%60SET%60%0A%20%20%20%20-%20%5B%20%5D%20I%20can%20wrap%20a%20destructive%20statement%20so%20there%20is%20a%20way%20back%0A%20%20%20%20-%20%5B%20%5D%20I%20can%20load%20a%20CSV%20two%20ways%20and%20state%20which%20one%20decided%20the%20types%0A%20%20%20%20-%20%5B%20%5D%20I%20can%20work%20out%20the%20order%20a%20set%20of%20files%20has%20to%20be%20loaded%20in%20from%20an%20ERD%0A%20%20%20%20-%20%5B%20%5D%20I%20check%20row%20counts%20after%20every%20load%2C%20and%20I%20know%20what%20I%20am%20comparing%0A%20%20%20%20-%20%5B%20%5D%20I%20can%20spot%20a%20date%20column%20that%20loaded%20cleanly%20and%20is%20wrong%0A%20%20%20%20-%20%5B%20%5D%20I%20can%20run%20a%20query%20from%20a%20notebook%2C%20hold%20the%20result%20in%20a%20variable%20and%20query%20that%0A%20%20%20%20%20%20%20%20%20%20variable%0A%20%20%20%20-%20%5B%20%5D%20I%20can%20get%20a%20Python%20value%20into%20a%20query%20without%20an%20f-string%2C%20and%20explain%20why%20that%0A%20%20%20%20%20%20%20%20%20%20matters%20on%20this%20warehouse%0A%20%20%20%20-%20%5B%20%5D%20I%20can%20create%20a%20view%20and%20explain%20what%20it%20stores%0A%0A%20%20%20%20**That%20is%20the%20last%20of%20the%20four%20content%20chapters.**%20Part%20A%20is%20what%20a%20database%20is%2C%20B%20is%0A%20%20%20%20one%20table%2C%20C%20is%20many%20tables%2C%20and%20D%20is%20building%20one.%20The%20practice%20bank%20is%20indexed%20by%0A%20%20%20%20section%20number%2C%20so%20anything%20here%20that%20needs%20more%20repetitions%20has%20more%20waiting.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20app.run()%0A
af155454f890f04bd0aab88082809450