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%20A%3A%20What%20a%20Database%20Is.%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%20duckdb%0A%20%20%20%20import%20marimo%20as%20mo%0A%20%20%20%20from%20urllib.request%20import%20urlopen%0A%0A%20%20%20%20return%20duckdb%2C%20mo%2C%20urlopen%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%20What%20a%20Database%20Is%0A%0A%20%20%20%20Chapter%20A%20of%20the%20SQL%20Handbook.%20Five%20sections%2C%20each%20one%20concept.%0A%0A%20%20%20%20There%20is%20no%20query%20language%20in%20this%20chapter.%20Part%20B%20starts%20%60SELECT%60.%20What%20is%20here%20is%0A%20%20%20%20the%20vocabulary%20that%20makes%20a%20query%20mean%20anything%3A%20what%20a%20table%20holds%2C%20what%20makes%20one%0A%20%20%20%20row%20findable%2C%20and%20how%20two%20tables%20know%20about%20each%20other.%0A%0A%20%20%20%20Every%20query%20below%20runs%20against%20the%20course%20warehouse%2C%20Northwind.%20You%20can%20read%20this%0A%20%20%20%20straight%20through%2C%20or%20open%20it%20when%20you%20need%20one%20thing.%20Every%20section%20ends%20with%0A%20%20%20%20questions%20that%20have%20no%20answers%20written%20down%2C%20because%20those%20are%20the%20ones%20asked%20out%0A%20%20%20%20loud%20in%20class.%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%20warehouse%0A%0A%20%20%20%20The%20two%20cells%20below%20load%20the%2013%20parquet%20files%20in%20%60data%2Fnorthwind%2F%60%20into%20DuckDB%20as%2013%0A%20%20%20%20tables.%20How%20that%20works%20is%20Part%20D%2C%20sections%2026%20and%2027%2C%20and%20nothing%20in%20this%20chapter%0A%20%20%20%20depends%20on%20reading%20it.%0A%0A%20%20%20%20One%20name%20is%20worth%20noticing%20now.%20The%20file%20is%20%60order-details.parquet%60%20and%20the%20table%20is%0A%20%20%20%20%60%22Order%20Details%22%60%2C%20with%20a%20space%20in%20it%2C%20so%20every%20mention%20of%20it%20needs%20double%20quotes.%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%0A%20%20%20%20_files%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%22categories%22%3A%20%22categories%22%2C%0A%20%20%20%20%20%20%20%20%22customercustomerdemo%22%3A%20%22customercustomerdemo%22%2C%0A%20%20%20%20%20%20%20%20%22customerdemographics%22%3A%20%22customerdemographics%22%2C%0A%20%20%20%20%20%20%20%20%22customers%22%3A%20%22customers%22%2C%0A%20%20%20%20%20%20%20%20%22employees%22%3A%20%22employees%22%2C%0A%20%20%20%20%20%20%20%20%22employeeterritories%22%3A%20%22employeeterritories%22%2C%0A%20%20%20%20%20%20%20%20%22Order%20Details%22%3A%20%22order-details%22%2C%0A%20%20%20%20%20%20%20%20%22orders%22%3A%20%22orders%22%2C%0A%20%20%20%20%20%20%20%20%22products%22%3A%20%22products%22%2C%0A%20%20%20%20%20%20%20%20%22regions%22%3A%20%22regions%22%2C%0A%20%20%20%20%20%20%20%20%22shippers%22%3A%20%22shippers%22%2C%0A%20%20%20%20%20%20%20%20%22suppliers%22%3A%20%22suppliers%22%2C%0A%20%20%20%20%20%20%20%20%22territories%22%3A%20%22territories%22%2C%0A%20%20%20%20%7D%0A%20%20%20%20for%20_table%2C%20_file%20in%20_files.items()%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%22%7B_table%7D%22%20AS%20SELECT%20*%20FROM%20'%7B_source%7D%2F%7B_file%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_(mo%2C%20urlopen)%3A%0A%20%20%20%20def%20erd(name)%3A%0A%20%20%20%20%20%20%20%20%22%22%22Return%20the%20named%20diagram%20from%20the%20assets%20folder%2C%20drawn%20in%20the%20page's%20text%20color.%22%22%22%0A%20%20%20%20%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_svg%20%3D%20(mo.notebook_dir()%20%2F%20%22assets%22%20%2F%20f%22%7Bname%7D.svg%22).read_text()%0A%20%20%20%20%20%20%20%20except%20FileNotFoundError%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_url%20%3D%20f%22https%3A%2F%2Foim7510.github.io%2Fhandbooks%2Fsql%2Fassets%2F%7Bname%7D.svg%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_svg%20%3D%20urlopen(_url).read().decode(%22utf-8%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20except%20Exception%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20mo.md(f%22*Diagram%20%60%7Bname%7D%60%20could%20not%20be%20loaded.*%22)%0A%20%20%20%20%20%20%20%20_style%20%3D%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%3Cstyle%3E%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%22.erd-card%7Bpadding%3A.5rem%7D%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%22.erd-card%20svg%2C.erd-card%20svg%20*%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%7Bcolor%3Ainherit!important%3Bbackground-color%3Atransparent!important%7D%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%22.erd-card%20svg%20rect%2C.erd-card%20svg%20polygon%2C.erd-card%20svg%20circle%2C%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%22.erd-card%20svg%20path%2C.erd-card%20svg%20line%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%7Bfill%3Anone!important%3Bstroke%3AcurrentColor!important%7D%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%22.erd-card%20svg%20text%2C.erd-card%20svg%20tspan%7Bfill%3AcurrentColor!important%7D%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%22.erd-card%20svg%20.edgeLabel%2C.erd-card%20svg%20.labelBkg%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%7Bbackground-color%3Avar(--background%2C%23fff)!important%7D%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%3C%2Fstyle%3E%22%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20return%20mo.Html(f'%7B_style%7D%3Cdiv%20class%3D%22erd-card%22%3E%7B_svg%7D%3C%2Fdiv%3E')%0A%0A%20%20%20%20return%20(erd%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%201.%20Tables%2C%20rows%2C%20columns%0A%0A%20%20%20%20A%20relational%20database%20keeps%20everything%20in%20tables.%20A%20table%20has%20named%20columns%20and%20some%0A%20%20%20%20number%20of%20rows%2C%20and%20that%20is%20the%20whole%20shape%20of%20it.%20The%20rest%20of%20this%20book%20is%20about%0A%20%20%20%20what%20you%20are%20allowed%20to%20claim%20about%20tables.%0A%0A%20%20%20%20%F0%9F%93%96%20**Used%20in%3A**%20Designing%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%20A%20table%20is%20columns%20and%20rows%0A%0A%20%20%20%20%60shippers%60%20is%20the%20smallest%20table%20in%20the%20warehouse%20that%20has%20anything%20in%20it.%20It%20has%0A%20%20%20%20three%20columns%20and%20three%20rows%2C%20and%20one%20row%20is%20one%20shipping%20company.%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%20*%20FROM%20shippers%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%20Three%20words%20exist%20for%20a%20row%20and%20three%20for%20a%20column%2C%20and%20you%20will%20meet%20all%20six.%0A%0A%20%20%20%20%7C%20the%20whole%20thing%20%7C%20one%20across%20%7C%20one%20down%20%7C%0A%20%20%20%20%7C---%7C---%7C---%7C%0A%20%20%20%20%7C%20table%2C%20relation%20%7C%20row%2C%20record%2C%20tuple%20%7C%20column%2C%20field%2C%20attribute%20%7C%0A%0A%20%20%20%20%60ShipperID%60%2C%20%60CompanyName%60%20and%20%60Phone%60%20are%20the%20columns.%20The%20line%20beginning%20%602%60%20is%20a%0A%20%20%20%20row%2C%20and%20it%20is%20one%20company.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20find%20another%20table%20in%20the%20warehouse%20with%20fewer%20than%2010%20rows%20and%20state%2C%0A%20%20%20%20in%20one%20sentence%2C%20what%20one%20row%20of%20it%20means.%20%60regions%60%20and%20%60categories%60%20are%20both%0A%20%20%20%20candidates.%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%20Every%20value%20in%20a%20column%20has%20its%20type%0A%0A%20%20%20%20%60DESCRIBE%60%20asks%20the%20database%20what%20a%20table%20looks%20like.%20It%20answers%20with%20one%20line%20per%0A%20%20%20%20column.%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(%22DESCRIBE%20shippers%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%60ShipperID%60%20is%20an%20%60INTEGER%60%2C%20and%20it%20is%20an%20integer%20in%20every%20row%20there%20will%20ever%20be.%0A%20%20%20%20Writing%20the%20word%20%60later%60%20into%20that%20column%20is%20refused%20at%20the%20moment%20somebody%20tries%2C%20so%0A%20%20%20%20nothing%20downstream%20has%20to%20defend%20against%20finding%20it.%0A%0A%20%20%20%20%23%23%23%20In%20Excel%20terms%0A%0A%20%20%20%20A%20spreadsheet%20column%20holds%20whatever%20each%20cell%20happens%20to%20hold%2C%20so%20one%20cell%20says%0A%20%20%20%20%601%2C204%60%2C%20the%20next%20says%20%601204%60%2C%20a%20third%20says%20%60n%2Fa%60%2C%20and%20%60SUM%60%20quietly%20skips%20two%20of%0A%20%20%20%20them.%20A%20database%20column%20has%20one%20declared%20type%20for%20all%20of%20its%20rows%2C%20which%20is%20why%20a%0A%20%20%20%20column%20of%20prices%20in%20a%20database%20can%20be%20added%20up%20without%20being%20inspected%20first.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20run%20%60DESCRIBE%20orders%60%20and%20find%20the%20three%20columns%20that%20hold%20a%20date%20and%0A%20%20%20%20a%20time.%20Then%20state%20what%20%60Freight%60%20is%20a%20number%20of.%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%20a%20row%20stands%20for%0A%0A%20%20%20%20%60employees%60%20holds%20nine%20rows.%20One%20row%20is%20one%20person%2C%20and%20the%20whole%20table%20fits%20on%20a%0A%20%20%20%20screen%2C%20which%20makes%20it%20the%20example%20this%20chapter%20keeps%20coming%20back%20to.%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%22EmployeeID%22%2C%20%22FirstName%22%2C%20%22LastName%22%2C%20%22Title%22%2C%20%22ReportsTo%22%0A%20%20%20%20%20%20%20%20FROM%20employees%0A%20%20%20%20%20%20%20%20ORDER%20BY%20%22EmployeeID%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%20The%20table%20has%2018%20columns%20and%20five%20of%20them%20are%20shown.%20A%20photograph%20and%20a%20paragraph%20of%0A%20%20%20%20notes%20are%20in%20there%20too%2C%20so%20a%20column%20can%20hold%20something%20you%20would%20never%20add%20up.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20state%20in%20one%20sentence%20what%20one%20row%20of%20%60orders%60%20is%2C%20and%20then%20what%20one%20row%0A%20%20%20%20of%20%60%22Order%20Details%22%60%20is.%20The%20second%20one%20is%20harder%20and%20%C2%A73%20is%20where%20it%20gets%20settled.%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%20order%20of%20the%20rows%20is%20not%20part%20of%20the%20table%0A%0A%20%20%20%20Nothing%20about%20a%20table%20says%20which%20row%20comes%20first.%20The%20query%20above%20ends%20with%0A%20%20%20%20%60ORDER%20BY%20%22EmployeeID%22%60%2C%20and%20that%20is%20the%20only%20reason%20the%20nine%20came%20back%20numbered%20one%0A%20%20%20%20to%20nine.%20The%20same%20query%20without%20that%20line%20may%20hand%20you%20the%20same%20rows%20in%20a%20different%0A%20%20%20%20order%20tomorrow%2C%20and%20the%20database%20is%20within%20its%20rights.%20%C2%A78%20is%20where%20you%20ask%20for%20an%0A%20%20%20%20order.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20run%20the%20employees%20query%20again%20with%20the%20%60ORDER%20BY%60%20line%20deleted%2C%20and%0A%20%20%20%20then%20with%20%60ORDER%20BY%20%22LastName%22%60.%20Same%20rows%2C%20three%20arrangements.%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%20size%20of%20every%20table%0A%0A%20%20%20%20%60duckdb_tables()%60%20is%20the%20database%20describing%20itself.%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%20table_name%2C%20estimated_size%0A%20%20%20%20%20%20%20%20FROM%20duckdb_tables()%0A%20%20%20%20%20%20%20%20ORDER%20BY%20table_name%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%20DuckDB%20calls%20that%20column%20an%20estimate%2C%20and%20for%20a%20table%20it%20has%20just%20loaded%20whole%20it%20is%0A%20%20%20%20the%20exact%20count.%20Checking%20one%20of%20them%20with%20%60count(*)%60%20takes%20a%20second%20and%20is%20the%20habit%0A%20%20%20%20to%20build.%0A%0A%20%20%20%20%60customerdemographics%60%20and%20%60customercustomerdemo%60%20hold%20zero%20rows.%20Somebody%20designed%0A%20%20%20%20them%2C%20somebody%20created%20them%2C%20and%20nobody%20ever%20put%20anything%20in%20them.%0A%0A%20%20%20%20**The%20trap.**%20A%20table%20existing%20says%20nothing%20about%20whether%20the%20thing%20it%20describes%20was%0A%20%20%20%20ever%20recorded.%20A%20query%20that%20goes%20through%20either%20of%20those%20two%20comes%20back%20with%20no%20rows%0A%20%20%20%20while%20looking%20entirely%20correct.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20confirm%20one%20of%20the%20numbers%20above%20with%20%60SELECT%20count(*)%60%20on%20that%20table.%0A%20%20%20%20Then%20take%20the%20three%20largest%20tables%20and%20state%20what%20one%20row%20of%20each%20means%2C%20in%20one%20sentence%0A%20%20%20%20each.%0A%0A%20%20%20%20%23%23%23%20%E2%9C%94%20Check%20your%20understanding%0A%0A%20%20%20%201.%20%60orders%60%20has%2016%2C282%20rows.%20What%20is%20one%20row%20of%20it%3F%0A%20%20%20%202.%20A%20colleague%20sends%20a%20spreadsheet%20where%20one%20column%20holds%20numbers%20in%20some%20rows%20and%0A%20%20%20%20%20%20%20the%20word%20%60pending%60%20in%20others.%20What%20has%20to%20happen%20before%20that%20becomes%20a%20database%0A%20%20%20%20%20%20%20column%3F%0A%20%20%20%203.%20Two%20people%20run%20the%20same%20query%20with%20no%20%60ORDER%20BY%60%20and%20compare%20the%20first%20row%20they%0A%20%20%20%20%20%20%20get.%20The%20two%20rows%20differ.%20What%20can%20you%20conclude%3F%0A%20%20%20%204.%20%60customerdemographics%60%20has%20zero%20rows.%20What%20does%20that%20tell%20you%20about%20the%20design%2C%0A%20%20%20%20%20%20%20and%20what%20does%20it%20tell%20you%20about%20the%20data%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%202.%20Primary%20keys%2C%20compound%20keys%2C%20surrogate%20keys%0A%0A%20%20%20%20A%20table%20of%2016%2C282%20rows%20is%20no%20use%20until%20you%20can%20name%20one%20of%20them.%20A%20**primary%20key**%20is%0A%20%20%20%20the%20column%2C%20or%20the%20group%20of%20columns%2C%20that%20identifies%20exactly%20one%20row%20and%20never%0A%20%20%20%20repeats.%20Every%20table%20should%20have%20one%2C%20and%20somebody%20decides%20which%20column%20it%20is.%20The%0A%20%20%20%20data%20never%20hands%20you%20the%20answer.%0A%0A%20%20%20%20%F0%9F%93%96%20**Used%20in%3A**%20Designing%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%20A%20primary%20key%20is%20unique%20and%20it%20is%20never%20missing%0A%0A%20%20%20%20%60CustomerID%60%20is%20the%20primary%20key%20of%20%60customers%60.%20Two%20counts%20show%20what%20that%20buys.%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%20count(*)%20AS%20n_rows%2C%20count(DISTINCT%20%22CustomerID%22)%20AS%20n_keys%0A%20%20%20%20%20%20%20%20FROM%20customers%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%20There%20are%2093%20rows%20and%2093%20different%20values%20in%20that%20column.%20If%20those%20two%20numbers%20ever%0A%20%20%20%20came%20apart%2C%20some%20id%20would%20appear%20twice%20or%20some%20row%20would%20have%20none%2C%20and%20either%20way%20the%0A%20%20%20%20column%20would%20not%20be%20a%20key.%20Only%20a%20key%20guarantees%20that%20%22the%20customer%20with%20id%20%60VINET%60%22%0A%20%20%20%20names%20exactly%20one%20row.%0A%0A%20%20%20%20Those%20two%20counts%20are%20the%20check%20for%20any%20column%20somebody%20claims%20is%20a%20key%2C%20and%20they%20cost%0A%20%20%20%20one%20query.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20run%20the%20same%20two%20counts%20on%20%60employees%60%20using%20%60%22EmployeeID%22%60.%20Then%20run%0A%20%20%20%20them%20on%20%60customers%60%20using%20%60%22Country%22%60%2C%20and%20explain%20in%20one%20sentence%20why%20that%20column%20could%0A%20%20%20%20never%20be%20the%20key.%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%20which%20column%20is%20the%20key%0A%0A%20%20%20%20A%20%60Student%60%20table%20has%20to%20identify%20one%20student.%20The%20candidates%20are%20the%20student%20id%0A%20%20%20%20number%2C%20the%20full%20name%2C%20the%20email%20address%2C%20the%20date%20of%20birth%2C%20the%20home%20address%2C%20the%0A%20%20%20%20Social%20Security%20number%20and%20the%20driver's%20license%20number.%20Three%20properties%20decide%20it.%0A%0A%20%20%20%20-%20**Unique.**%20Two%20students%20must%20never%20share%20the%20value%2C%20and%20a%20column%20that%20passes%20this%0A%20%20%20%20%20%20test%20is%20a%20**candidate%20key**.%20Full%20name%20fails%20on%20the%20first%20pair%20of%20Smiths%2C%20and%20it%0A%20%20%20%20%20%20fails%20quietly%2C%20because%20nothing%20about%20the%20row%20looks%20wrong.%0A%20%20%20%20-%20**Stable.**%20The%20value%20must%20never%20change.%20Email%20addresses%20and%20home%20addresses%20change%0A%20%20%20%20%20%20all%20the%20time%2C%20and%20every%20row%20pointing%20at%20the%20old%20value%20is%20then%20pointing%20at%20nothing.%0A%20%20%20%20-%20**Simple%2C%20and%20always%20there.**%20A%20date%20of%20birth%20is%20shared%20by%20dozens%20of%20people.%20Not%0A%20%20%20%20%20%20every%20student%20holds%20a%20driver's%20license.%20A%20Social%20Security%20number%20is%20a%20legal%0A%20%20%20%20%20%20liability%20you%20would%20rather%20keep%20out%20of%20the%20table%20entirely.%0A%0A%20%20%20%20The%20student%20id%20wins%20because%20the%20school%20invented%20it%20for%20this%20job%2C%20and%20a%20key%20that%0A%20%20%20%20already%20exists%20in%20the%20business%20is%20a%20**natural%20key**.%20When%20no%20such%20column%20exists%2C%20the%0A%20%20%20%20next%20two%20knowledge%20points%20are%20the%20two%20ways%20out.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20think%20of%20a%20list%20your%20own%20team%20keeps%20in%20a%20spreadsheet.%20Name%20the%20column%0A%20%20%20%20you%20would%20make%20the%20primary%20key%2C%20and%20state%20which%20of%20the%20three%20properties%20you%20are%20least%0A%20%20%20%20sure%20of.%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%20single%20column%20is%20not%20enough%0A%0A%20%20%20%20%60employeeterritories%60%20records%20which%20employee%20covers%20which%20territory.%20It%20has%2049%20rows%0A%20%20%20%20and%20two%20columns%2C%20and%20neither%20column%20on%20its%20own%20identifies%20a%20row.%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%20count(*)%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%20%20%20%20%20%20%20AS%20n_rows%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20count(DISTINCT%20%22EmployeeID%22)%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20AS%20n_employees%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20count(DISTINCT%20(%22EmployeeID%22%2C%20%22TerritoryID%22))%20%20%20AS%20n_pairs%0A%20%20%20%20%20%20%20%20FROM%20employeeterritories%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%20Nine%20employees%20are%20spread%20across%2049%20rows%2C%20so%20%60EmployeeID%60%20repeats.%20The%20pair%20never%0A%20%20%20%20repeats.%20The%20key%20of%20this%20table%20is%20the%20two%20columns%20together%2C%20written%0A%20%20%20%20%60PRIMARY%20KEY%20(EmployeeID%2C%20TerritoryID)%60%2C%20and%20that%20is%20a%20**compound%20key**%2C%20also%20called%20a%0A%20%20%20%20**composite%20key**.%0A%0A%20%20%20%20The%20same%20shape%20shows%20up%20on%20paper%2C%20where%20you%20can%20see%20it%20at%20a%20glance%3A%0A%0A%20%20%20%20%7C%20StudentID%20%7C%20CourseID%20%7C%20Grade%20%7C%0A%20%20%20%20%7C---%7C---%7C---%7C%0A%20%20%20%20%7C%20%60123456789%60%20%7C%20%60ACC7200%60%20%7C%20A%20%7C%0A%20%20%20%20%7C%20%60123456789%60%20%7C%20%60QTM7800%60%20%7C%20B%20%7C%0A%20%20%20%20%7C%20%60987654321%60%20%7C%20%60ACC7200%60%20%7C%20A-%20%7C%0A%20%20%20%20%7C%20%60987654321%60%20%7C%20%60QTM7800%60%20%7C%20B%2B%20%7C%0A%0A%20%20%20%20Each%20id%20appears%20twice.%20Each%20course%20appears%20twice.%20Each%20pairing%20appears%20once.%0A%0A%20%20%20%20**The%20trap.**%20%60TerritoryID%60%20on%20its%20own%20also%20has%2049%20distinct%20values%20in%20those%2049%20rows%2C%0A%20%20%20%20so%20a%20uniqueness%20check%20on%20that%20one%20column%20passes%20today.%20It%20passes%20because%20no%20territory%0A%20%20%20%20has%20yet%20been%20given%20to%20two%20people.%20A%20count%20over%20today's%20rows%20tells%20you%20what%20happened%3B%0A%20%20%20%20a%20primary%20key%20states%20what%20the%20database%20will%20refuse%20tomorrow.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20run%20the%20same%20three%20counts%20on%20%60%22Order%20Details%22%60%2C%20using%20%60%22OrderID%22%60%20and%0A%20%20%20%20the%20pair%20%60(%22OrderID%22%2C%20%22ProductID%22)%60.%20Predict%20all%20three%20numbers%20first.%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%20An%20invented%20key%0A%0A%20%20%20%20A%20**surrogate%20key**%20is%20a%20column%20the%20database%20made%20up.%20It%20carries%20no%20meaning%20at%20all%2C%0A%20%20%20%20and%20that%20is%20the%20advantage%2C%20because%20nothing%20that%20happens%20in%20the%20business%20can%20change%0A%20%20%20%20it.%0A%0A%20%20%20%20%7C%20EnrollmentID%20%7C%20StudentID%20%7C%20CourseID%20%7C%20Grade%20%7C%0A%20%20%20%20%7C---%7C---%7C---%7C---%7C%0A%20%20%20%20%7C%20%601%60%20%7C%20%60123456789%60%20%7C%20%60ACC7200%60%20%7C%20A%20%7C%0A%20%20%20%20%7C%20%602%60%20%7C%20%60123456789%60%20%7C%20%60QTM7800%60%20%7C%20B%20%7C%0A%20%20%20%20%7C%20%603%60%20%7C%20%60987654321%60%20%7C%20%60ACC7200%60%20%7C%20A-%20%7C%0A%20%20%20%20%7C%20%604%60%20%7C%20%60987654321%60%20%7C%20%60QTM7800%60%20%7C%20B%2B%20%7C%0A%0A%20%20%20%20%60EnrollmentID%60%20is%20a%20counter.%20%60orders.OrderID%60%20is%20the%20same%20idea%3A%2010248%20is%20not%20a%20fact%0A%20%20%20%20about%20that%20order%2C%20it%20is%20the%20number%20the%20system%20handed%20out%20when%20the%20order%20arrived.%0A%0A%20%20%20%20**The%20trap.**%20A%20surrogate%20key%20permits%20the%20same%20pairing%20twice.%20Nothing%20stops%20a%20fifth%0A%20%20%20%20row%20holding%20%60123456789%60%20and%20%60ACC7200%60%20again.%20The%20rule%20that%20a%20student%20takes%20a%20course%0A%20%20%20%20once%20has%20to%20be%20declared%20separately%2C%20as%20%60UNIQUE%20(StudentID%2C%20CourseID)%60%2C%20and%20a%20design%0A%20%20%20%20that%20adds%20%60EnrollmentID%60%20and%20forgets%20that%20line%20has%20quietly%20given%20up%20a%20guarantee%20it%0A%20%20%20%20had%20before.%0A%0A%20%20%20%20**Every%20engine%20spells%20the%20counter%20differently%2C%20and%20DuckDB%20refuses%20the%20common%0A%20%20%20%20spelling.**%20SQLite%20takes%20this%3A%0A%0A%20%20%20%20%60%60%60sql%0A%20%20%20%20CREATE%20TABLE%20enrollment%20(id%20INTEGER%20PRIMARY%20KEY%20AUTOINCREMENT)%3B%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20DuckDB%20answers%3A%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%20wants%20a%20sequence%20instead.%20%C2%A724%20is%20where%20the%20%60CREATE%20TABLE%60%20gets%20written%20and%0A%20%20%20%20where%20the%20spelling%20starts%20to%20matter.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20run%20the%20two%20counts%20from%20earlier%20on%20%60products%60%20using%20%60%22ProductName%22%60.%0A%20%20%20%20Then%20judge%20whether%20the%20product%20name%20could%20be%20the%20primary%20key%2C%20and%20what%20would%20have%20to%0A%20%20%20%20happen%20for%20your%20answer%20to%20change.%0A%0A%20%20%20%20%23%23%23%20%E2%9C%94%20Check%20your%20understanding%0A%0A%20%20%20%201.%20A%20colleague%20says%20the%20email%20address%20is%20the%20primary%20key%20of%20the%20customer%20table.%20What%0A%20%20%20%20%20%20%20breaks%20the%20first%20time%20somebody%20changes%20their%20email%3F%0A%20%20%20%202.%20%60count(*)%60%20and%20%60count(DISTINCT%20%22OrderID%22)%60%20on%20%60orders%60%20are%20both%2016%2C282.%20Does%20that%0A%20%20%20%20%20%20%20prove%20%60OrderID%60%20is%20the%20primary%20key%3F%0A%20%20%20%203.%20A%20table%20has%20a%20compound%20key%20made%20of%20three%20columns.%20How%20many%20rows%20can%20share%20the%20same%0A%20%20%20%20%20%20%20values%20in%20the%20first%20two%20of%20them%3F%0A%20%20%20%204.%20What%20does%20a%20surrogate%20key%20fail%20to%20give%20you%20that%20%60UNIQUE%20(StudentID%2C%20CourseID)%60%0A%20%20%20%20%20%20%20gives%20you%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%203.%20Relationship%20cardinality%3A%201%3A1%2C%201%3AM%2C%20M%3AN%0A%0A%20%20%20%20Tables%20are%20kept%20apart%20on%20purpose%2C%20and%20a%20**relationship**%20is%20how%20one%20table%20refers%20to%0A%20%20%20%20another.%20Every%20relationship%20is%20a%20business%20rule%20written%20down.%20There%20are%20three%20kinds%2C%0A%20%20%20%20and%20telling%20them%20apart%20is%20most%20of%20what%20database%20design%20is.%0A%0A%20%20%20%20%F0%9F%93%96%20**Used%20in%3A**%20Designing%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%20One%20to%20many%0A%0A%20%20%20%20There%20is%20one%20row%20on%20the%20left%20and%20many%20rows%20on%20the%20right%2C%20and%20each%20row%20on%20the%20right%0A%20%20%20%20belongs%20to%20exactly%20one%20row%20on%20the%20left.%20A%20customer%20places%20many%20orders.%20Each%20order%0A%20%20%20%20belongs%20to%20one%20customer.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(erd)%3A%0A%20%20%20%20erd(%22erd-one-to-many%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%20Customer%20%60VINET%60%20is%20one%20row%20in%20%60customers%60.%20Here%20is%20how%20many%20orders%20point%20back%20at%20it.%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%20count(*)%20AS%20vinet_orders%0A%20%20%20%20%20%20%20%20FROM%20orders%0A%20%20%20%20%20%20%20%20WHERE%20%22CustomerID%22%20%3D%20'VINET'%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%20Those%20158%20orders%20each%20carry%20the%20five%20characters%20%60VINET%60.%20The%20company%20name%2C%20the%0A%20%20%20%20address%20and%20the%20phone%20number%20sit%20once%20in%20%60customers%60%20and%20are%20not%20copied%20anywhere.%0A%0A%20%20%20%20This%20is%20the%20reason%20the%20two%20tables%20are%20two%20tables.%20An%20address%20copied%20onto%20158%20orders%0A%20%20%20%20is%20158%20chances%20to%20be%20wrong.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20count%20the%20orders%20belonging%20to%20customer%20%60SAVEA%60%20and%20to%20customer%0A%20%20%20%20%60ANATR%60.%20Then%20check%20that%20each%20of%20those%20ids%20appears%20exactly%20once%20in%20%60customers%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%20A%20table%20can%20point%20at%20itself%0A%0A%20%20%20%20%60employees.ReportsTo%60%20holds%20the%20%60EmployeeID%60%20of%20that%20person's%20manager%2C%20which%20puts%0A%20%20%20%20%60employees%60%20on%20both%20ends%20of%20one%20relationship%20and%20makes%20it%20a%0A%20%20%20%20**self-referencing%20relationship**.%20Nine%20rows%20fit%20on%20a%20screen%2C%20so%20this%20is%20the%20one%0A%20%20%20%20relationship%20in%20the%20warehouse%20you%20can%20check%20entirely%20by%20eye.%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%22EmployeeID%22%2C%20%22FirstName%22%2C%20%22LastName%22%2C%20%22Title%22%2C%20%22ReportsTo%22%0A%20%20%20%20%20%20%20%20FROM%20employees%0A%20%20%20%20%20%20%20%20ORDER%20BY%20%22EmployeeID%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%20Andrew%20Fuller%20is%20employee%202%20and%20his%20%60ReportsTo%60%20is%20empty%2C%20because%20the%20Vice%20President%0A%20%20%20%20reports%20to%20nobody.%20Five%20people%20point%20at%20him.%20Three%20point%20at%20Steven%20Buchanan%2C%20who%20is%0A%20%20%20%20employee%205%20and%20points%20at%20Fuller%20in%20turn.%0A%0A%20%20%20%20An%20empty%20foreign%20key%20is%20a%20statement%20in%20its%20own%20right%2C%20and%20here%20it%20says%20the%20chain%0A%20%20%20%20stops.%20%C2%A79%20is%20where%20the%20empty%20value%20gets%20its%20proper%20name.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20read%20the%20nine%20rows%20and%20write%20out%20the%20reporting%20chain%20for%20Anne%0A%20%20%20%20Dodsworth%20up%20to%20the%20top.%20How%20many%20names%20is%20it%3F%20Now%20do%20the%20same%20for%20Nancy%20Davolio.%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%20One%20to%20one%0A%0A%20%20%20%20There%20is%20at%20most%20one%20row%20on%20each%20side.%20A%20person%20holds%20one%20passport%20and%20a%20passport%0A%20%20%20%20belongs%20to%20one%20person.%20Northwind%20has%20no%20one-to-one%20relationship%20anywhere%20in%20it%2C%20which%20is%0A%20%20%20%20ordinary.%20When%20two%20tables%20line%20up%20one%20for%20one%2C%20the%20usual%20answer%20is%20a%20single%20table%0A%20%20%20%20with%20more%20columns%20in%20it.%0A%0A%20%20%20%20Two%20cases%20keep%20a%20one-to-one%20alive.%20The%20first%20is%20a%20column%20so%20large%20that%20you%20want%20it%0A%20%20%20%20out%20of%20the%20way%20of%20every%20query%2C%20such%20as%20an%20employee%20photograph.%20The%20second%20is%20a%20group%0A%20%20%20%20of%20columns%20only%20some%20people%20are%20allowed%20to%20read%2C%20such%20as%20salary%2C%20which%20is%20easier%20to%0A%20%20%20%20control%20when%20it%20sits%20in%20a%20table%20of%20its%20own.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20name%20a%20one-to-one%20pair%20from%20your%20own%20work%20and%20state%20which%20of%20those%20two%0A%20%20%20%20reasons%20keeps%20the%20two%20tables%20apart.%20If%20neither%20applies%2C%20explain%20what%20merging%20them%20would%0A%20%20%20%20cost.%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%20Many%20to%20many%0A%0A%20%20%20%20Take%20students%20and%20courses.%20A%20student%20takes%20many%20courses%20and%20a%20course%20holds%20many%0A%20%20%20%20students.%20Two%20obvious%20designs%20are%20available%20and%20both%20of%20them%20are%20wrong.%0A%0A%20%20%20%20**Bad%20solution%20A.**%20Draw%20a%20one%20to%20many%20from%20students%20to%20courses.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(erd)%3A%0A%20%20%20%20erd(%22erd-bad-a%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%20Read%20the%20line%20back%20from%20the%20course%20end.%20Each%20course%20connects%20to%20one%20student%2C%20so%20every%0A%20%20%20%20course%20in%20the%20school%20has%20exactly%20one%20person%20sitting%20in%20it.%0A%0A%20%20%20%20**Bad%20solution%20B.**%20Draw%20a%20one%20to%20many%20from%20courses%20to%20students.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(erd)%3A%0A%20%20%20%20erd(%22erd-bad-b%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%20Read%20that%20one%20back%20from%20the%20student%20end.%20Each%20student%20connects%20to%20one%20course%2C%20so%0A%20%20%20%20nobody%20takes%20two%20subjects.%0A%0A%20%20%20%20The%20two%20diagrams%20are%20mirror%20images.%20Turning%20the%20line%20around%20moves%20the%20damage%20to%20the%0A%20%20%20%20other%20side%20and%20never%20removes%20it%2C%20because%20the%20shape%20is%20what%20is%20wrong.%0A%0A%20%20%20%20**The%20answer%20is%20a%20third%20table**%2C%20holding%20one%20row%20for%20each%20pairing.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(erd)%3A%0A%20%20%20%20erd(%22erd-enrollment%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%60ENROLLMENT%60%20has%20a%20row%20for%20every%20student-and-course%20pair%20that%20exists.%20Its%20key%20is%0A%20%20%20%20%60(StudentID%2C%20CourseID)%60%2C%20the%20compound%20key%20from%20%C2%A72%2C%20or%20the%20surrogate%20%60EnrollmentID%60%0A%20%20%20%20with%20%60UNIQUE%20(StudentID%2C%20CourseID)%60%20beside%20it%2C%20which%20is%20also%20from%20%C2%A72.%20The%20one%0A%20%20%20%20many-to-many%20has%20become%20two%20one-to-many%20relationships%20pointing%20inward.%0A%0A%20%20%20%20A%20table%20in%20that%20position%20is%20an%20**intersection%20table**%2C%20also%20called%20a%20junction%20table%2C%0A%20%20%20%20and%20it%20almost%20always%20grows%20columns%20of%20its%20own.%20%60Grade%60%20belongs%20to%20the%20pairing.%20It%20is%0A%20%20%20%20no%20more%20a%20fact%20about%20the%20student%20than%20it%20is%20a%20fact%20about%20the%20course.%0A%0A%20%20%20%20The%20warehouse%20holds%20two%20of%20them.%0A%0A%20%20%20%20-%20**%60employeeterritories%60.**%20It%20holds%20nothing%20but%20the%20pair%2C%20in%2049%20rows%20of%20two%20columns%2C%0A%20%20%20%20%20%20keyed%20the%20way%20%C2%A72%20described.%0A%20%20%20%20-%20**%60%22Order%20Details%22%60.**%20It%20holds%20one%20row%20per%20product%20on%20one%20order%2C%20and%20it%20carries%0A%20%20%20%20%20%20%60UnitPrice%60%2C%20%60Quantity%60%20and%20%60Discount%60.%20Those%20three%20facts%20belong%20to%20the%20pairing%20and%0A%20%20%20%20%20%20to%20nothing%20else%2C%20because%20the%20price%20on%20the%20day%20is%20a%20fact%20about%20that%20line%20of%20that%0A%20%20%20%20%20%20order.%0A%0A%20%20%20%20Order%2010248%20has%20three%20of%20those%20rows.%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*%0A%20%20%20%20%20%20%20%20FROM%20%22Order%20Details%22%0A%20%20%20%20%20%20%20%20WHERE%20%22OrderID%22%20%3D%2010248%0A%20%20%20%20%20%20%20%20ORDER%20BY%20%22ProductID%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%20The%20three%20lines%20carry%20three%20product%20ids%20and%20no%20product%20names.%20Product%2011%20is%20Queso%0A%20%20%20%20Cabrales%2C%2042%20is%20Singaporean%20Hokkien%20Fried%20Mee%20and%2072%20is%20Mozzarella%20di%20Giovanni%2C%20and%0A%20%20%20%20turning%20those%20numbers%20into%20those%20names%20means%20reaching%20into%20%60products%60%2C%20which%20is%20a%0A%20%20%20%20join%20and%20belongs%20to%20%C2%A717.%20The%20order%20header%2C%20with%20the%20customer%20and%20the%20date%20and%20the%0A%20%20%20%2016.75%20of%20freight%2C%20is%20one%20row%20over%20in%20%60orders%60.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20name%20a%20many-to-many%20relationship%20in%20something%20you%20work%20on.%20State%20what%0A%20%20%20%20its%20intersection%20table%20would%20be%20called%2C%20and%20name%20one%20column%20it%20would%20carry%20that%0A%20%20%20%20belongs%20to%20neither%20of%20the%20two%20sides.%0A%0A%20%20%20%20%23%23%23%20%E2%9C%94%20Check%20your%20understanding%0A%0A%20%20%20%201.%20Customer%20%60VINET%60%20has%20158%20orders.%20How%20many%20customers%20does%20one%20of%20those%20orders%20have%3F%0A%20%20%20%202.%20Bad%20solution%20A%20and%20bad%20solution%20B%20are%20the%20same%20mistake.%20Name%20the%20mistake%0A%20%20%20%20%20%20%20without%20using%20the%20word%20direction.%0A%20%20%20%203.%20%60Grade%60%20sits%20on%20%60ENROLLMENT%60.%20What%20goes%20wrong%20if%20somebody%20puts%20it%20on%20%60STUDENT%60%0A%20%20%20%20%20%20%20instead%3F%0A%20%20%20%204.%20%60employees.ReportsTo%60%20is%20empty%20in%20one%20row%20out%20of%20nine.%20What%20would%20you%20suspect%20if%0A%20%20%20%20%20%20%20it%20were%20empty%20in%20four%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%204.%20Foreign%20keys%20and%20referential%20integrity%0A%0A%20%20%20%20A%20**foreign%20key**%20is%20a%20column%20holding%20another%20table's%20primary%20key.%20It%20is%20the%0A%20%20%20%20mechanism%20behind%20every%20relationship%20in%20%C2%A73.%20**Referential%20integrity**%20is%20the%20promise%0A%20%20%20%20that%20a%20foreign%20key%20always%20points%20at%20a%20row%20that%20exists.%0A%0A%20%20%20%20%F0%9F%93%96%20**Used%20in%3A**%20Designing%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%20foreign%20key%20sits%20on%20the%20many%20side%0A%0A%20%20%20%20%60orders.CustomerID%60%20is%20a%20foreign%20key.%20It%20holds%20a%20value%20taken%20from%0A%20%20%20%20%60customers.CustomerID%60%2C%20which%20is%20that%20table's%20primary%20key.%0A%0A%20%20%20%20Which%20side%20it%20lands%20on%20follows%20from%20counting%2C%20and%20there%20is%20no%20choice%20in%20it.%20One%0A%20%20%20%20customer%20has%20many%20orders%2C%20so%20a%20customer%20row%20has%20no%20single%20order%20id%20to%20hold.%20One%20order%0A%20%20%20%20has%20one%20customer%2C%20so%20an%20order%20row%20holds%20one%20customer%20id%20comfortably.%20The%20pointer%20goes%0A%20%20%20%20on%20the%20side%20that%20has%20one%20of%20the%20other%20thing.%0A%0A%20%20%20%20%23%23%23%20In%20Excel%20terms%0A%0A%20%20%20%20A%20foreign%20key%20is%20the%20column%20you%20would%20run%20%60VLOOKUP%60%20against.%20The%20difference%20is%20what%0A%20%20%20%20happens%20when%20the%20value%20is%20missing.%20%60VLOOKUP%60%20puts%20%60%23N%2FA%60%20in%20the%20cell%20and%20the%0A%20%20%20%20spreadsheet%20carries%20on%3B%20a%20database%20with%20the%20constraint%20declared%20refuses%20to%20store%20the%0A%20%20%20%20row%20at%20all.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20for%20each%20of%20these%20pairs%2C%20state%20which%20table%20holds%20the%20foreign%20key%20and%0A%20%20%20%20what%20the%20column%20is%20called%3A%20%60products%60%20and%20%60categories%60%2C%20%60orders%60%20and%20%60employees%60%2C%0A%20%20%20%20%60%22Order%20Details%22%60%20and%20%60orders%60.%20Check%20each%20answer%20with%20%60DESCRIBE%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%20What%20the%20constraint%20refuses%0A%0A%20%20%20%20Once%20a%20foreign%20key%20is%20declared%2C%20it%20blocks%20two%20things.%20Both%20messages%20below%20came%20out%20of%0A%20%20%20%20DuckDB%2C%20from%20a%20two-table%20example%20with%20%60ord.customer_id%60%20declared%20as%20a%20foreign%20key%0A%20%20%20%20into%20%60customer.id%60.%0A%0A%20%20%20%20The%20first%20is%20an%20order%20pointing%20at%20a%20customer%20who%20does%20not%20exist%3A%0A%0A%20%20%20%20%60%60%60sql%0A%20%20%20%20INSERT%20INTO%20ord%20VALUES%20(99999%2C%20'NOPE'%2C%20DATE%20'2016-07-04')%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%22id%3A%20NOPE%22%0A%20%20%20%20does%20not%20exist%20in%20the%20referenced%20table%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20The%20second%20is%20deleting%20a%20customer%20who%20still%20has%20orders%3A%0A%0A%20%20%20%20%60%60%60sql%0A%20%20%20%20DELETE%20FROM%20customer%20WHERE%20id%20%3D%20'VINET'%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%0A%20%20%20%20%22customer_id%3A%20VINET%22%20is%20still%20referenced%20by%20a%20foreign%20key%20in%20a%20different%20table%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20A%20row%20whose%20foreign%20key%20names%20a%20parent%20that%20is%20not%20there%20is%20an%20**orphan**.%20Those%20two%0A%20%20%20%20refusals%20are%20the%20only%20two%20ways%20an%20orphan%20can%20be%20created%2C%20and%20the%20constraint%20blocks%0A%20%20%20%20both.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20build%20that%20two-table%20example%20yourself%2C%20with%20%60CREATE%20TABLE%20customer%60%20and%0A%20%20%20%20%60CREATE%20TABLE%20ord%60%2C%20and%20reproduce%20both%20messages.%20%C2%A724%20has%20the%20syntax%20if%20you%20want%20it%0A%20%20%20%20now.%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%20parent%20with%20no%20children%20is%20allowed%0A%0A%20%20%20%20The%20rule%20runs%20one%20way%20only.%20Every%20order%20must%20have%20a%20customer.%20Nothing%20anywhere%20says%20a%0A%20%20%20%20customer%20must%20have%20an%20order%2C%20and%20nothing%20says%20a%20territory%20must%20have%20an%20employee.%0A%0A%20%20%20%20Four%20territories%20are%20covered%20by%20nobody.%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%22TerritoryID%22%2C%20%22TerritoryDescription%22%0A%20%20%20%20%20%20%20%20FROM%20territories%0A%20%20%20%20%20%20%20%20WHERE%20%22TerritoryID%22%20NOT%20IN%20(SELECT%20%22TerritoryID%22%20FROM%20employeeterritories)%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%20Nobody%20sells%20in%20Columbia%2C%20Bentonville%2C%20Dallas%20or%20Austin%2C%20and%20that%20is%20a%20fact%20about%20the%0A%20%20%20%20sales%20team.%20Territories%20are%20not%20the%20only%20parents%20here%20with%20no%20children.%20Through%0A%20%20%20%20%60ReportsTo%60%20from%20%C2%A73%2C%20seven%20of%20the%20nine%20employees%20have%20nobody%20reporting%20to%20them.%0A%0A%20%20%20%20**The%20trap.**%20These%20four%20are%20the%20opposite%20of%20an%20orphan.%20An%20orphan%20is%20a%20child%20with%20no%0A%20%20%20%20parent%20and%20the%20constraint%20forbids%20it.%20These%20are%20parents%20with%20no%20children%2C%20and%20no%0A%20%20%20%20constraint%20has%20an%20opinion%20about%20them.%20Reading%20one%20as%20the%20other%20turns%20a%20true%20business%0A%20%20%20%20fact%20into%20a%20bug%20report%2C%20and%20%C2%A75%20shows%20where%20an%20ERD%20says%20which%20of%20the%20two%20you%20should%0A%20%20%20%20expect.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20write%20the%20same%20check%20the%20other%20way%20around%2C%20looking%20for%20employees%20who%0A%20%20%20%20cover%20no%20territory.%20Predict%20the%20answer%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%20Design%20against%20enforcement%0A%0A%20%20%20%20Everything%20above%20describes%20what%20a%20foreign%20key%20does%20once%20it%20is%20**declared**.%20This%0A%20%20%20%20warehouse%20declares%20nothing%20at%20all.%20It%20was%20loaded%20from%20parquet%20files%2C%20and%20a%20parquet%0A%20%20%20%20file%20carries%20column%20names%20and%20column%20types%20and%20no%20constraints.%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(%22DESCRIBE%20orders%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%20%60key%60%20column%20is%20empty%20for%20all%2014%20columns%20and%20the%20%60null%60%20column%20says%20%60YES%60%20for%20all%0A%20%20%20%2014.%20There%20is%20no%20primary%20key%2C%20no%20foreign%20key%20and%20nothing%20required%20anywhere%20in%20this%0A%20%20%20%20table%2C%20and%20it%20would%20take%20a%20row%20tomorrow%20whose%20%60CustomerID%60%20is%20%60ZZZZZ%60.%0A%0A%20%20%20%20So%20the%20relationships%20in%20%C2%A73%20are%20real%2C%20and%20what%20holds%20them%20up%20is%20the%20process%20that%0A%20%20%20%20produced%20these%20files.%20The%20database%20is%20enforcing%20none%20of%20them.%20Whether%20they%20hold%20is%20a%0A%20%20%20%20question%20you%20answer%20by%20running%20a%20query.%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%0A%20%20%20%20%20%20%20%20%20%20(SELECT%20count(*)%20FROM%20orders%0A%20%20%20%20%20%20%20%20%20%20%20%20%20WHERE%20%22CustomerID%22%20NOT%20IN%20(SELECT%20%22CustomerID%22%20FROM%20customers))%0A%20%20%20%20%20%20%20%20%20%20%20%20AS%20orders_without_a_customer%2C%0A%20%20%20%20%20%20%20%20%20%20(SELECT%20count(*)%20FROM%20%22Order%20Details%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20WHERE%20%22OrderID%22%20NOT%20IN%20(SELECT%20%22OrderID%22%20FROM%20orders))%0A%20%20%20%20%20%20%20%20%20%20%20%20AS%20lines_without_an_order%2C%0A%20%20%20%20%20%20%20%20%20%20(SELECT%20count(*)%20FROM%20products%0A%20%20%20%20%20%20%20%20%20%20%20%20%20WHERE%20%22CategoryID%22%20NOT%20IN%20(SELECT%20%22CategoryID%22%20FROM%20categories))%0A%20%20%20%20%20%20%20%20%20%20%20%20AS%20products_without_a_category%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%20All%20three%20counts%20are%20zero.%20Every%20order%20names%20a%20customer%20that%20exists%2C%20every%20order%20line%0A%20%20%20%20names%20an%20order%20that%20exists%2C%20and%20every%20product%20names%20a%20category%20that%20exists.%20The%20design%0A%20%20%20%20is%20honored%20here%2C%20and%20that%20query%20is%20how%20you%20know%20it.%0A%0A%20%20%20%20**DuckDB%20checks%20a%20declared%20foreign%20key%20and%20gives%20you%20no%20way%20to%20switch%20the%20check%20off.**%0A%20%20%20%20SQLite%20does%20have%20such%20a%20switch%2C%20%60PRAGMA%20foreign_keys%60%2C%20and%20it%20is%20**off%20by%20default**%2C%0A%20%20%20%20so%20a%20SQLite%20database%20can%20accept%20an%20orphan%20and%20report%20nothing.%20In%20DuckDB%20the%20same%20line%0A%20%20%20%20is%20not%20a%20setting%20at%20all%3A%0A%0A%20%20%20%20%60%60%60sql%0A%20%20%20%20PRAGMA%20foreign_keys%20%3D%20OFF%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%20unrecognized%20configuration%20parameter%20%22foreign_keys%22%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20add%20a%20fourth%20line%20to%20the%20three-zero%20query%2C%20checking%0A%20%20%20%20%60employeeterritories.%22EmployeeID%22%60%20against%20%60employees%60.%20Predict%20the%20number%2C%20then%20run%0A%20%20%20%20it.%20Then%20change%20one%20table%20name%20to%20something%20that%20does%20not%20exist%20and%20read%20what%0A%20%20%20%20happens.%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%20happens%20on%20a%20delete%20is%20a%20design%20decision%0A%0A%20%20%20%20A%20design%20that%20declares%20foreign%20keys%20also%20has%20to%20answer%20what%20a%20delete%20does%20to%20the%20rows%0A%20%20%20%20pointing%20at%20the%20deleted%20one.%20There%20are%20three%20answers.%0A%0A%20%20%20%20%7C%20%7C%20add%20an%20order%20for%20a%20customer%20who%20does%20not%20exist%20%7C%20delete%20a%20customer%20who%20has%20orders%20%7C%0A%20%20%20%20%7C---%7C---%7C---%7C%0A%20%20%20%20%7C%20**Not%20enforced**%20%7C%20allowed%20%7C%20allowed%20%7C%0A%20%20%20%20%7C%20**Enforced%2C%20no%20cascade**%20%7C%20refused%20%7C%20refused%20%7C%0A%20%20%20%20%7C%20**Enforced%2C%20cascade**%20%7C%20refused%20%7C%20allowed%2C%20and%20the%20orders%20go%20too%20%7C%0A%0A%20%20%20%20The%20third%20row%20is%20where%20somebody%20removes%20one%20customer%20and%20158%20orders%20disappear%20behind%0A%20%20%20%20it.%20The%20design%20settled%20whether%20that%20is%20the%20intended%20behavior%20long%20before%20anybody%0A%20%20%20%20pressed%20delete.%0A%0A%20%20%20%20Which%20of%20the%20three%20you%20can%20actually%20declare%20depends%20on%20the%20engine%2C%20and%20DuckDB%20is%20the%0A%20%20%20%20strict%20one%3A%0A%0A%20%20%20%20%60%60%60sql%0A%20%20%20%20CREATE%20TABLE%20ord%20(%0A%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%20customer_id%20VARCHAR%20REFERENCES%20customer(id)%20ON%20DELETE%20CASCADE%0A%20%20%20%20)%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%20FOREIGN%20KEY%20constraints%20cannot%20use%20CASCADE%2C%20SET%20NULL%20or%20SET%20DEFAULT%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20The%20middle%20row%20is%20the%20only%20one%20DuckDB%20will%20write%20down.%20%C2%A724%20is%20where%20you%20declare%20these%0A%20%20%20%20for%20real%2C%20and%20where%20the%20engine%20you%20are%20running%20on%20starts%20to%20matter.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20take%20one%20deletion%20from%20your%20own%20work%2C%20such%20as%20removing%20a%20customer%2C%20an%0A%20%20%20%20employee%20or%20a%20product.%20List%20everything%20in%20a%20system%20you%20know%20that%20points%20at%20the%20thing%0A%20%20%20%20being%20deleted%2C%20and%20state%20which%20of%20the%20three%20rows%20above%20you%20would%20want%20for%20each.%0A%0A%20%20%20%20%23%23%23%20%E2%9C%94%20Check%20your%20understanding%0A%0A%20%20%20%201.%20%60orders.CustomerID%60%20is%20the%20foreign%20key%20and%20it%20sits%20on%20%60orders%60.%20Why%20can%20it%20not%20sit%0A%20%20%20%20%20%20%20on%20%60customers%60%20instead%3F%0A%20%20%20%202.%20Four%20territories%20have%20no%20employee.%20Is%20that%20a%20referential%20integrity%20violation%3F%0A%20%20%20%203.%20The%20parquet%20files%20declare%20no%20constraints.%20What%20does%20that%20change%20about%20the%20three%0A%20%20%20%20%20%20%20zeros%20above%2C%20and%20what%20does%20it%20not%20change%3F%0A%20%20%20%204.%20A%20design%20asks%20for%20cascade%20on%20delete.%20What%20do%20you%20check%20before%20approving%20it%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%205.%20Reading%20an%20ERD%0A%0A%20%20%20%20An%20**entity%20relationship%20diagram**%20puts%20the%20design%20of%20a%20database%20on%20one%20page%3A%20the%0A%20%20%20%20entities%2C%20their%20attributes%2C%20and%20the%20lines%20between%20them.%20Four%20of%20them%20appear%20earlier%20in%0A%20%20%20%20this%20chapter.%20This%20section%20names%20every%20mark%20on%20the%20line.%0A%0A%20%20%20%20%F0%9F%93%96%20**Used%20in%3A**%20Designing%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%20What%20is%20on%20the%20diagram%0A%0A%20%20%20%20-%20An%20**entity**%20is%20a%20thing%20you%20keep%20a%20table%20of.%20It%20is%20drawn%20as%20a%20box%20and%20it%20becomes%20a%0A%20%20%20%20%20%20table%2C%20such%20as%20%60CUSTOMERS%60%20or%20%60ORDERS%60.%0A%20%20%20%20-%20An%20**attribute**%20is%20one%20fact%20about%20that%20thing.%20It%20is%20drawn%20as%20a%20line%20inside%20the%20box%0A%20%20%20%20%20%20and%20it%20becomes%20a%20column%2C%20such%20as%20%60CompanyName%60%20or%20%60OrderDate%60.%0A%20%20%20%20-%20A%20**relationship**%20is%20a%20line%20between%20two%20boxes%20with%20a%20word%20on%20it%20that%20reads%20as%20a%0A%20%20%20%20%20%20verb%2C%20such%20as%20%60CUSTOMERS%20places%20ORDERS%60.%0A%0A%20%20%20%20An%20ERD%20is%20drawn%20before%20any%20table%20exists.%20That%20is%20why%20it%20says%20entity%20where%20a%20running%0A%20%20%20%20database%20says%20table.%20By%20the%20time%20there%20is%20a%20database%2C%20the%20two%20words%20mean%20the%20same%0A%20%20%20%20thing.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20take%20three%20sentences%20describing%20any%20business%20you%20know%20and%20mark%20every%0A%20%20%20%20noun.%20Sort%20them%20into%20things%20you%20would%20keep%20a%20table%20of%20and%20facts%20about%20those%20things.%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%20marks%20at%20each%20end%20of%20a%20line%0A%0A%20%20%20%20The%20end%20of%20a%20line%20where%20it%20touches%20a%20box%20says%20how%20many%20rows%20of%20that%20box%20take%20part.%0A%20%20%20%20Two%20symbols%20do%20that%20job.%20The%20one%20nearer%20the%20box%20is%20the%20maximum%2C%20its%20**cardinality**%2C%0A%20%20%20%20and%20the%20one%20further%20from%20the%20box%20is%20the%20minimum%2C%20its%20**optionality**.%0A%0A%20%20%20%20-%20**Two%20bars.**%20Exactly%20one.%20Written%20%60%7C%7C%60.%0A%20%20%20%20-%20**A%20circle%20and%20a%20bar.**%20Zero%20or%20one.%20Written%20%60o%7C%60.%0A%20%20%20%20-%20**A%20bar%20and%20a%20crow's%20foot.**%20One%20or%20more.%20Written%20%60%7C%7B%60.%0A%20%20%20%20-%20**A%20circle%20and%20a%20crow's%20foot.**%20Zero%20or%20more.%20Written%20%60o%7B%60.%0A%0A%20%20%20%20The%20written%20forms%20are%20Mermaid%2C%20which%20is%20the%20text%20format%20these%20diagrams%20were%20generated%0A%20%20%20%20from%20and%20the%20one%20an%20agent%20will%20hand%20you%20when%20you%20ask%20it%20for%20an%20ERD.%20A%20full%20line%20reads%0A%20%20%20%20%60CUSTOMERS%20%7C%7C--o%7B%20ORDERS%20%3A%20places%60%2C%20and%20the%20left%20end%20is%20spelled%20backwards%2C%20so%20a%20crow's%0A%20%20%20%20foot%20on%20the%20left%20is%20%60%7Do--%60.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20using%20only%20the%20four%20forms%20above%2C%20work%20out%20what%0A%20%20%20%20%60TERRITORIES%20%7C%7C--%7C%7B%20EMPLOYEE_TERRITORIES%60%20would%20be%20claiming.%20Then%20check%20whether%20the%20four%0A%20%20%20%20uncovered%20territories%20in%20%C2%A74%20agree%20with%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%20Reading%20a%20line%20both%20ways%0A%0A%20%20%20%20Read%20it%20once%20in%20each%20direction%2C%20and%20each%20time%20read%20the%20marks%20at%20the%20far%20end.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(erd)%3A%0A%20%20%20%20erd(%22erd-one-to-many%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%20Left%20to%20right%3A%20one%20customer%20places%20zero%20or%20more%20orders.%20The%20end%20touching%20%60ORDERS%60%0A%20%20%20%20carries%20a%20crow's%20foot%20for%20the%20maximum%20and%20a%20circle%20for%20the%20minimum.%0A%0A%20%20%20%20Right%20to%20left%3A%20one%20order%20belongs%20to%20exactly%20one%20customer.%20The%20end%20touching%0A%20%20%20%20%60CUSTOMERS%60%20carries%20two%20bars.%0A%0A%20%20%20%20Both%20sentences%20have%20to%20be%20true%20of%20the%20business.%20A%20design%20goes%20wrong%20when%20somebody%0A%20%20%20%20checked%20one%20direction%20and%20assumed%20the%20other.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20read%20%60EMPLOYEES%20%7C%7C--o%7B%20ORDERS%20%3A%20takes%60%20out%20loud%20in%20both%20directions%2C%20then%0A%20%20%20%20%60CATEGORIES%20%7C%7C--o%7B%20PRODUCTS%20%3A%20groups%60.%20Judge%20whether%20each%20sentence%20is%20true%20of%20a%20company%0A%20%20%20%20that%20sells%20food.%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%20whole%20warehouse%20on%20a%20page%0A%0A%20%20%20%20The%20diagram%20has%20seven%20lines%20and%20eight%20boxes%2C%20and%20it%20carries%20everything%20this%20chapter%0A%20%20%20%20has%20said%20about%20Northwind.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(erd)%3A%0A%20%20%20%20erd(%22erd-northwind%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%20These%20lines%20read%20out%20like%20this%3A%0A%0A%20%20%20%20-%20**%60CATEGORIES%20%7C%7C--o%7B%20PRODUCTS%20%3A%20groups%60.**%20A%20category%20groups%20zero%20or%20more%20products%2C%0A%20%20%20%20%20%20and%20each%20product%20belongs%20to%20exactly%20one%20category.%0A%20%20%20%20-%20**%60ORDERS%20%7C%7C--%7C%7B%20ORDER_DETAILS%20%3A%20contains%60.**%20An%20order%20contains%20one%20or%20more%20lines%2C%0A%20%20%20%20%20%20and%20each%20line%20belongs%20to%20exactly%20one%20order.%20The%20bar%20on%20the%20right%20end%20is%20the%20claim%0A%20%20%20%20%20%20that%20an%20order%20with%20no%20lines%20is%20not%20a%20thing.%0A%20%20%20%20-%20**%60EMPLOYEES%20%7C%7C--o%7B%20EMPLOYEE_TERRITORIES%20%7Do--%7C%7C%20TERRITORIES%60.**%20Two%20one-to-many%0A%20%20%20%20%20%20relationships%20meeting%20at%20an%20intersection%20table%2C%20which%20is%20the%20many-to-many%20from%20%C2%A73%0A%20%20%20%20%20%20drawn%20the%20way%20%C2%A73%20said%20to%20draw%20it.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20read%20the%20other%20three%20lines%20out%20loud%20in%20both%20directions.%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%20minimum%20mark%20is%20a%20claim%20you%20can%20test%0A%0A%20%20%20%20The%20line%20into%20%60EMPLOYEE_TERRITORIES%60%20from%20%60TERRITORIES%60%20carries%20a%20circle%2C%20which%20says%20a%0A%20%20%20%20territory%20can%20be%20covered%20by%20nobody.%20%C2%A74%20found%20four%20such%20territories%2C%20so%20the%20circle%20is%0A%20%20%20%20right%20and%20a%20bar%20there%20would%20have%20been%20wrong.%0A%0A%20%20%20%20The%20test%20runs%20one%20way.%20Four%20rows%20disprove%20a%20bar.%20No%20number%20of%20rows%20can%20prove%20a%20circle%2C%0A%20%20%20%20because%20%22zero%20is%20allowed%22%20is%20a%20statement%20about%20what%20the%20business%20permits%2C%20and%20today's%0A%20%20%20%20data%20is%20free%20to%20be%20narrower%20than%20that.%20Every%20customer%20in%20this%20warehouse%20has%20placed%20at%0A%20%20%20%20least%20one%20order%2C%20and%20the%20line%20still%20reads%20zero%20or%20more%2C%20because%20a%20customer%20who%20has%20not%0A%20%20%20%20ordered%20yet%20is%20still%20a%20customer.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20the%20%60ORDERS%60%20to%20%60ORDER_DETAILS%60%20line%20claims%20every%20order%20has%20at%20least%0A%20%20%20%20one%20line.%20Write%20the%20query%20that%20would%20disprove%20it%2C%20run%20it%2C%20and%20state%20what%20the%20answer%0A%20%20%20%20entitles%20you%20to%20conclude.%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%20PK%20and%20FK%20inside%20the%20box%0A%0A%20%20%20%20An%20ERD%20can%20list%20the%20columns.%20%60PK%60%20marks%20the%20primary%20key%2C%20%60FK%60%20marks%20a%20foreign%20key%2C%0A%20%20%20%20and%20one%20column%20can%20carry%20both.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(erd)%3A%0A%20%20%20%20erd(%22erd-enrollment%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%60ENROLLMENT%60%20shows%20%C2%A72%20and%20%C2%A73%20at%20the%20same%20time%3A%20a%20surrogate%20primary%20key%2C%20two%20foreign%0A%20%20%20%20keys%2C%20and%20one%20column%20of%20its%20own.%20Drawn%20the%20other%20way%2C%20%60StudentID%60%20and%20%60CourseID%60%0A%20%20%20%20would%20each%20be%20marked%20%60PK%20FK%60%2C%20because%20a%20compound%20primary%20key%20made%20of%20two%20foreign%20keys%0A%20%20%20%20is%20the%20same%20design%20written%20differently.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20draw%20%60orders%60%2C%20%60%22Order%20Details%22%60%20and%20%60products%60%20on%20paper.%20Put%20the%0A%20%20%20%20columns%20in%20the%20boxes%2C%20mark%20every%20%60PK%60%20and%20%60FK%60%2C%20and%20put%20the%20right%20marks%20on%20both%20ends%0A%20%20%20%20of%20both%20lines.%20Check%20the%20columns%20with%20%60DESCRIBE%60%20before%20you%20decide.%0A%0A%20%20%20%20%23%23%23%20%E2%9C%94%20Check%20your%20understanding%0A%0A%20%20%20%201.%20A%20line%20ends%20in%20a%20circle%20and%20a%20crow's%20foot.%20What%20two%20things%20does%20that%20tell%20you%3F%0A%20%20%20%202.%20You%20read%20a%20relationship%20in%20one%20direction%20and%20it%20is%20true%20of%20the%20business.%20Why%20is%0A%20%20%20%20%20%20%20that%20not%20enough%3F%0A%20%20%20%203.%20An%20ERD%20claims%20every%20order%20has%20at%20least%20one%20line.%20What%20one%20query%20tells%20you%20whether%0A%20%20%20%20%20%20%20the%20data%20agrees%2C%20and%20what%20does%20a%20zero%20result%20entitle%20you%20to%20conclude%3F%0A%20%20%20%204.%20You%20are%20handed%20an%20ERD%20with%20no%20%60PK%60%20or%20%60FK%60%20markings%20anywhere.%20What%20can%20you%20still%0A%20%20%20%20%20%20%20work%20out%20from%20the%20lines%20alone%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.%20It%20exists%20because%20each%20section%20above%20teaches%20one%20thing%2C%20and%20a%0A%20%20%20%20real%20design%20needs%20all%20five%20of%20them%20at%20once.%0A%0A%20%20%20%20**Design%20the%20database%20for%20something%20you%20already%20deal%20with.**%20Any%20of%20these%20would%20do%3A%20a%0A%20%20%20%20club's%20members%20and%20its%20events%2C%20a%20team's%20projects%20and%20the%20hours%20booked%20against%20them%2C%20a%0A%20%20%20%20shop's%20suppliers%20and%20what%20it%20buys%20from%20each.%20Pick%20something%20you%20can%20describe%20from%0A%20%20%20%20memory.%20Writing%20the%20description%20is%20the%20hard%20part%2C%20and%20the%20drawing%20takes%2020%20minutes%0A%20%20%20%20once%20the%20description%20is%20right.%0A%0A%20%20%20%20**Done%20looks%20like**%20one%20ERD%20and%20one%20page%20of%20prose.%20The%20ERD%20needs%20four%20entities%20or%0A%20%20%20%20more%2C%20at%20least%20one%20many-to-many%20with%20its%20intersection%20table%20drawn%2C%20a%20primary%20key%0A%20%20%20%20named%20on%20every%20box%2C%20and%20every%20foreign%20key%20marked.%20The%20prose%20gives%2C%20for%20each%0A%20%20%20%20relationship%20line%2C%20the%20two%20sentences%20from%20%C2%A75.%0A%0A%20%20%20%20**Four%20decisions%20are%20yours%2C%20and%20no%20drawing%20tool%20will%20make%20them.**%0A%0A%20%20%20%201.%20Which%20of%20your%20nouns%20are%20entities%20and%20which%20are%20attributes%3F%20An%20address%20is%20a%20column%0A%20%20%20%20%20%20%20until%20the%20day%20somebody%20needs%20two%20of%20them.%0A%20%20%20%202.%20Natural%20key%20or%20surrogate%20key%2C%20table%20by%20table.%20For%20each%20natural%20key%20you%20keep%2C%20state%0A%20%20%20%20%20%20%20what%20would%20have%20to%20happen%20for%20it%20to%20break.%0A%20%20%20%203.%20Which%20relationship%20ends%20carry%20a%20circle%3F%20Mark%20the%20ones%20you%20are%20guessing%20at.%0A%20%20%20%204.%20What%20happens%20on%20a%20delete%3F%20Name%20one%20row%20somebody%20might%20delete%20and%20state%20what%20should%0A%20%20%20%20%20%20%20happen%20to%20everything%20pointing%20at%20it.%0A%0A%20%20%20%20**Going%20further.**%20Build%20it.%20Write%20the%20%60CREATE%20TABLE%60%20statements%20with%20real%20foreign%0A%20%20%20%20keys%2C%20which%20is%20%C2%A724%2C%20load%2010%20rows%20into%20each%20table%2C%20then%20insert%20a%20row%20that%20violates%20one%0A%20%20%20%20of%20your%20own%20constraints%20and%20read%20the%20message%20it%20gives%20you.%20After%20that%2C%20hand%20the%20ERD%0A%20%20%20%20to%20somebody%20who%20is%20not%20in%20this%20course%20without%20explaining%20it%2C%20and%20ask%20them%20to%20describe%20the%0A%20%20%20%20business%20back%20to%20you.%20Write%20down%20every%20question%20they%20had%20to%20ask%2C%20because%20each%20one%20is%0A%20%20%20%20a%20place%20the%20diagram%20is%20not%20saying%20what%20you%20think%20it%20says.%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-%20Normalization%20has%20three%20numbered%20forms%20and%20this%20chapter%20never%20named%20them.%20Ask%20what%0A%20%20%20%20%20%20first%2C%20second%20and%20third%20normal%20form%20each%20forbid%2C%20and%20ask%20for%20one%20example%20table%20that%0A%20%20%20%20%20%20breaks%20each.%0A%20%20%20%20-%20Ask%20what%20an%20index%20is%2C%20why%20a%20primary%20key%20usually%20gets%20one%20without%20being%20asked%2C%20and%0A%20%20%20%20%20%20what%20having%20one%20costs.%0A%20%20%20%20-%20Ask%20how%20%60ON%20DELETE%20CASCADE%60%2C%20%60ON%20DELETE%20SET%20NULL%60%20and%20%60ON%20DELETE%20RESTRICT%60%20differ%2C%0A%20%20%20%20%20%20and%20which%20of%20them%20the%20engine%20you%20are%20using%20supports.%0A%20%20%20%20-%20Ask%20how%20to%20get%20an%20auto-numbered%20primary%20key%20in%20DuckDB%2C%20given%20that%20%60AUTOINCREMENT%60%0A%20%20%20%20%20%20is%20a%20syntax%20error%20there.%0A%20%20%20%20-%20Ask%20what%20a%20star%20schema%20is%2C%20and%20why%20a%20database%20built%20for%20reporting%20often%20breaks%20the%0A%20%20%20%20%20%20rules%20in%20%C2%A73%20deliberately.%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*%22Design%20the%20database%20tables%20for%20a%20food%20truck%20that%20takes%20catering%20bookings.%22*%0A%0A%20%20%20%20Then%20read%20what%20came%20back%20and%20answer%20four%20questions.%0A%0A%20%20%20%201.%20**How%20many%20tables%20did%20it%20invent%3F**%20You%20named%20two%20things.%0A%20%20%20%202.%20**Which%20keys%20did%20it%20choose%2C%20and%20are%20they%20natural%20or%20surrogate%3F**%20Read%20the%20column%0A%20%20%20%20%20%20%20definitions%20and%20ignore%20the%20comments%20above%20them.%0A%20%20%20%203.%20**Did%20it%20create%20an%20intersection%20table%3F**%20If%20it%20did%2C%20what%20many-to-many%20has%20it%0A%20%20%20%20%20%20%20decided%20your%20business%20has%3F%0A%20%20%20%204.%20**Did%20it%20declare%20foreign%20keys%20at%20all%2C%20and%20did%20it%20say%20anything%20about%20deletes%3F**%0A%0A%20%20%20%20You%20asked%20for%20one%20thing%20and%20got%20a%20dozen%20decisions%2C%20and%20every%20one%20of%20them%20is%20a%20claim%0A%20%20%20%20about%20a%20business%20you%20never%20described.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20send%20the%20same%20sentence%20twice%2C%20in%20two%20separate%20conversations%2C%20and%20put%0A%20%20%20%20the%20two%20table%20lists%20side%20by%20side.%20Then%20send%20it%20a%20third%20time%20with%20one%20extra%20sentence%0A%20%20%20%20about%20how%20bookings%20actually%20work%2C%20and%20see%20which%20of%20its%20inventions%20survive.%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%20A%20checklist%0A%0A%20%20%20%20-%20%5B%20%5D%20I%20can%20name%20the%20parts%20of%20a%20table%20and%20state%20what%20one%20row%20of%20it%20means%0A%20%20%20%20-%20%5B%20%5D%20I%20can%20check%20whether%20a%20column%20is%20a%20primary%20key%20using%20two%20counts%0A%20%20%20%20-%20%5B%20%5D%20I%20can%20explain%20when%20a%20compound%20key%20is%20needed%20and%20when%20a%20surrogate%20key%20is%20easier%0A%20%20%20%20-%20%5B%20%5D%20I%20can%20tell%20a%20one-to-many%20from%20a%20many-to-many%20in%20a%20description%20of%20a%20business%0A%20%20%20%20-%20%5B%20%5D%20I%20can%20explain%20why%20a%20many-to-many%20needs%20a%20third%20table%20and%20what%20that%20table%20holds%0A%20%20%20%20-%20%5B%20%5D%20I%20can%20state%20which%20of%20two%20tables%20holds%20the%20foreign%20key%2C%20and%20why%20it%20has%20to%20be%20that%0A%20%20%20%20%20%20%20%20%20%20one%0A%20%20%20%20-%20%5B%20%5D%20I%20can%20tell%20an%20orphan%20from%20a%20parent%20with%20no%20children%0A%20%20%20%20-%20%5B%20%5D%20I%20can%20state%20what%20this%20warehouse%20enforces%20and%20what%20it%20merely%20happens%20to%20satisfy%0A%20%20%20%20-%20%5B%20%5D%20I%20can%20read%20every%20mark%20on%20an%20ERD%20line%20and%20state%20the%20relationship%20out%20loud%20in%20both%0A%20%20%20%20%20%20%20%20%20%20directions%0A%20%20%20%20-%20%5B%20%5D%20I%20can%20name%20the%20one%20relationship%20end%20on%20the%20%C2%A75%20diagram%20that%20the%20data%20proves%0A%20%20%20%20%20%20%20%20%20%20is%20optional%0A%0A%20%20%20%20**Part%20B%20is%20one%20table.**%20It%20covers%20%60SELECT%60%2C%20%60WHERE%60%20and%20%60ORDER%20BY%60%2C%20and%20the%20first%0A%20%20%20%20questions%20you%20get%20to%20ask%20a%20database.%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
372fcfabd62a3b3596e96dc78fe1b689