transcript.md
↑ ContentsState Planner chat · https://stateplanner.com
- State: Tennessee (TN)
- Started: October 22, 2026, 12:35 AM UTC
- Runtime: 1 min 47 sec
- Status: completed
Prompt
Which Tennessee school districts gained or lost the most students since 2017–18? Map the schools by enrollment change, and say whether the gains are in the counties permitting the most new homes.
Model: Anthropic: Claude Opus 5.5 (anthropic/claude-opus-5.5, via openrouter)
Response
Now a county map setting permits beside enrollment change.
District enrollment change, 2017‑18 to 2024‑25 (NCES Common Core of Data)
Statewide, public school enrollment barely changed: 1,001,967 students in fall 2017 and 1,002,363 in fall 2024. Seven schools in 2017‑18 and 15 in 2024‑25 reported no enrollment and are left out of the totals. Underneath that flat total, students shifted between districts.
Districts with the largest gains
| District | 2017‑18 | 2024‑25 | Change | % |
|---|---|---|---|---|
| Tennessee Public Charter School Commission | – | 6,979 | +6,979 | new |
| Rutherford County | 45,292 | 52,127 | +6,835 | +15.1% |
| Montgomery County | 34,841 | 39,385 | +4,544 | +13.0% |
| Johnson County* | 2,110 | 4,952 | +2,842 | +134.7% |
| Wilson County | 18,343 | 20,703 | +2,360 | +12.9% |
| Williamson County | 39,322 | 41,593 | +2,271 | +5.8% |
| Lewis County* | 1,725 | 3,731 | +2,006 | +116.3% |
| Memphis‑Shelby County Schools | 109,591 | 111,125 | +1,534 | +1.4% |
| Union County* | 4,207 | 5,514 | +1,307 | +31.1% |
| Hamilton County | 44,822 | 45,981 | +1,159 | +2.6% |
Districts with the largest losses
| District | 2017‑18 | 2024‑25 | Change | % |
|---|---|---|---|---|
| Achievement School District | 11,419 | 1,412 | −10,007 | −87.6% |
| Davidson County (Metro Nashville) | 84,728 | 81,022 | −3,706 | −4.4% |
| Sullivan County | 9,758 | 7,878 | −1,880 | −19.3% |
| Tipton County | 11,114 | 9,975 | −1,139 | −10.2% |
| Lauderdale County | 4,159 | 3,277 | −882 | −21.2% |
| Carter County | 5,290 | 4,450 | −840 | −15.9% |
| Cheatham County | 6,277 | 5,451 | −826 | −13.2% |
| Greene County | 6,783 | 6,088 | −695 | −10.2% |
| Hawkins County | 6,705 | 6,038 | −667 | −9.9% |
| Fayette County | 3,560 | 2,909 | −651 | −18.3% |
* Johnson, Lewis and Union counties grew because of full‑virtual schools they host. Tennessee Connections Academy has about 3,000 students in Johnson County, Volunteer State Virtual Academy about 2,000 in Lewis, and Tennessee Virtual Academy and its junior high about 2,700 in Union. Those students live all over the state. Leave the virtual schools out and all three counties are roughly flat or down.
Two of the biggest moves are students changing district rather than arriving or leaving:
- The Achievement School District's large loss mostly matches the new Charter Commission district and Memphis‑Shelby's gain.
- Counted by where the schools are, Davidson County lost only about 700 students, not 3,700, because the Charter Commission's schools sit mostly in Nashville.
Schools by enrollment change

Map legend (each school is matched to itself by its NCES school ID):
- Dark blue: gained 200 or more students (68 schools)
- Light blue: gained 50–199 (202)
- Grey: changed by fewer than 50 (804)
- Orange: lost 50–199 (483)
- Dark red: lost 200 or more (95)
- Green: school ID new since 2017‑18 (263 schools, about 82,500 students)
- Black: school ID not listed in 2024‑25 (124 schools that had about 36,200 students)
- White: enrollment not reported in one of the two years
A school's ID changes when it moves to another district, as when schools left the Achievement School District, so some green and black dots are the same building under a new operator. Many of the gains among existing schools are in Rutherford, Montgomery, Wilson and Williamson counties. Losses are spread widely across rural counties.
Are the gains where the most homes are being permitted?
Mostly yes, but the three largest counties don't fit. I compared each county's school enrollment, counted by where its schools are and leaving out full‑virtual schools, with the housing units permitted there in 2017–2024 (Census Building Permits Survey).

Map legend:
- Purple shading: housing units permitted from 2017 to 2024 per 1,000 residents in 2017. The darkest counties permitted 80 or more.
- Green dots: county enrollment rose.
- Orange dots: county enrollment fell.
What the comparison shows:
- Seven of the 10 counties with the largest enrollment gains are also among the 10 that permitted the most homes: Rutherford (28,300 units), Montgomery (22,900), Williamson (18,400), Hamilton (17,500), Wilson (16,400), Sumner (14,800) and Maury (14,000). Most of these are around Nashville or Clarksville.
- The other three of the top 10 permitting counties lost students:
- Davidson permitted the most homes in the state, 86,300 units, yet lost about 700 students (−0.8%).
- Knox permitted 24,300 units and lost about 1,000 (−1.6%).
- Shelby permitted 15,000 units and lost about 3,900 (−2.5%).
- Counties grouped by permits per resident (about 24 counties each):
- Top quarter: enrollment up 2.9%, with 12 of 24 counties gaining.
- Second quarter: down 3.4%.
- Third quarter: down 4.7%.
- Bottom quarter: down 6.7%, with only one county gaining.
- Overall the link is moderate. The rank correlation between permits per resident and enrollment change is 0.45. Building a lot of housing is no guarantee of more students: Maury (149 units per 1,000), Sevier (100) and Fayette (76) all permitted heavily, and Sevier and Fayette lost students.
Caveats:
- Permits authorize construction; they don't count completed homes, and some permitted units are never built. Pickett County has no permit record in these years.
- Enrollment is the official fall count. It covers public schools only, so it doesn't capture moves to private school or homeschooling.
- Districts aren't the same as counties. City districts such as Murfreesboro, Lebanon and the Shelby suburbs are inside their counties' totals.
I can also compare against 2017–2023 permits to allow for the time between a permit and families moving in, or look only at single‑family units.
Exploratory Queries
Query 1 — Check years, school counts and enrollment coverage per year.
SELECT school_year,
count(*) n,
count(enrollment) with_enr,
sum(enrollment) enr,
count(DISTINCT leaid) districts
FROM tn_nces_public_schools
GROUP BY 1
ORDER BY 1
| school_year | n | with_enr | enr | districts |
|---|---|---|---|---|
| 2017-18 | 1789 | 1782 | 1001967 | 147 |
| 2018-19 | 1862 | 1862 | 1006309 | 147 |
| 2019-20 | 1894 | 1878 | 1014744 | 147 |
| 2020-21 | 1907 | 1880 | 984066 | 148 |
| 2021-22 | 1931 | 1906 | 995549 | 149 |
| 2022-23 | 1932 | 1900 | 1005626 | 148 |
| 2023-24 | 1924 | 1905 | 1003495 | 148 |
| 2024-25 | 1928 | 1913 | 1002363 | 149 |
Query 2 — Check permit years available.
SELECT min(YEAR),
max(YEAR),
count(DISTINCT geoid)
FROM tn_bps_county_permits
WHERE YEAR >= 2017
| min | max | count |
|---|---|---|
| 2017 | 2025 | 94 |
Query 3 — Enrollment change by district, 2017-18 to 2024-25.
WITH a AS (
SELECT leaid,
max(district_name) dn,
sum(enrollment) e,
count(*) s,
count(enrollment) se,
mode() WITHIN GROUP (
ORDER BY county_name
) cty
FROM tn_nces_public_schools
WHERE school_year = '2017-18'
GROUP BY 1
),
b AS (
SELECT leaid,
max(district_name) dn,
sum(enrollment) e,
count(*) s,
count(enrollment) se,
mode() WITHIN GROUP (
ORDER BY county_name
) cty
FROM tn_nces_public_schools
WHERE school_year = '2024-25'
GROUP BY 1
)
SELECT coalesce(b.leaid, a.leaid) leaid,
coalesce(b.dn, a.dn) district,
coalesce(b.cty, a.cty) county,
a.e e17,
b.e e24,
coalesce(b.e, 0) - coalesce(a.e, 0) chg,
round(100.0 * (b.e - a.e) / nullif(a.e, 0), 1) pct,
a.s s17,
b.s s24
FROM a
FULL JOIN b USING (leaid)
ORDER BY chg DESC
| leaid | district | county | e17 | e24 | chg | pct | s17 | s24 |
|---|---|---|---|---|---|---|---|---|
| 4700157 | Tennessee Public Charter School Commission | Davidson County | 6979 | 6979 | 17 | |||
| 4703690 | Rutherford County | Rutherford County | 45292 | 52127 | 6835 | 15.1 | 47 | 55 |
| 4703030 | Montgomery County | Montgomery County | 34841 | 39385 | 4544 | 13.0 | 39 | 46 |
| 4702160 | Johnson County | Johnson County | 2110 | 4952 | 2842 | 134.7 | 7 | 8 |
| 4704550 | Wilson County | Wilson County | 18343 | 20703 | 2360 | 12.9 | 21 | 25 |
| 4704530 | Williamson County | Williamson County | 39322 | 41593 | 2271 | 5.8 | 44 | 52 |
| 4702430 | Lewis County | Lewis County | 1725 | 3731 | 2006 | 116.3 | 4 | 7 |
| 4700148 | Memphis-Shelby County Schools | Shelby County | 109591 | 111125 | 1534 | 1.4 | 201 | 223 |
| 4704290 | Union County | Union County | 4207 | 5514 | 1307 | 31.1 | 10 | 11 |
| 4701590 | Hamilton County | Hamilton County | 44822 | 45981 | 1159 | 2.6 | 78 | 82 |
| 4704020 | Sumner County | Sumner County | 29636 | 30719 | 1083 | 3.7 | 49 | 53 |
| 4704551 | University Schools | Shelby County | 957 | 957 | 3 | |||
| 4700154 | Lakeland | Shelby County | 1652 | 2538 | 886 | 53.6 | 2 | 2 |
| 4702370 | Lebanon | Wilson County | 3773 | 4628 | 855 | 22.7 | 6 | 7 |
| 4703240 | Oak Ridge | Anderson County | 4440 | 5078 | 638 | 14.4 | 7 | 8 |
| 4700990 | DeKalb County | DeKalb County | 3041 | 3644 | 603 | 19.8 | 5 | 7 |
| 4700149 | Collierville | Shelby County | 8671 | 9263 | 592 | 6.8 | 8 | 10 |
| 4703150 | Murfreesboro | Rutherford County | 8841 | 9342 | 501 | 5.7 | 12 | 13 |
| 4702700 | Maryville | Blount County | 5349 | 5717 | 368 | 6.9 | 7 | 8 |
| 4703480 | Putnam County | Putnam County | 11421 | 11776 | 355 | 3.1 | 20 | 22 |
| 4702400 | Lenoir City | Loudon County | 2297 | 2638 | 341 | 14.8 | 3 | 4 |
| 4702760 | Maury County | Maury County | 12791 | 13098 | 307 | 2.4 | 21 | 24 |
| 4700180 | Bedford County | Bedford County | 8754 | 9042 | 288 | 3.3 | 14 | 15 |
| 4702610 | Manchester | Coffee County | 1400 | 1628 | 228 | 16.3 | 3 | 3 |
| 4700060 | Alcoa | Blount County | 2047 | 2270 | 223 | 10.9 | 4 | 4 |
25 out of 151 are listed here.
Query 4 — Check for virtual schools driving big percent gains in small districts.
SELECT school_year,
district_name,
school_name,
virtual,
enrollment
FROM tn_nces_public_schools
WHERE leaid IN ('4702160', '4702430', '4704290', '4700990')
AND school_year IN ('2017-18', '2024-25')
AND (
virtual ILIKE '%full%'
OR school_name ILIKE '%virtual%'
OR school_name ILIKE '%online%'
OR enrollment > 1000
)
ORDER BY 2,
3,
1
| school_year | district_name | school_name | virtual | enrollment |
|---|---|---|---|---|
| 2024-25 | DeKalb County | DeKalb County Schools Adult Education Virtual School | Full virtual | 784 |
| 2024-25 | Johnson County | Tennessee Connections Academy Johnson County 4-8 | Full virtual | 1471 |
| 2024-25 | Johnson County | Tennessee Connections Academy Johnson County 9-12 | Full virtual | 1543 |
| 2024-25 | Lewis County | Volunteer State Virtual Academy Elementary School | Full virtual | 614 |
| 2024-25 | Lewis County | Volunteer State Virtual Academy High School | Full virtual | 943 |
| 2024-25 | Lewis County | Volunteer State Virtual Academy Middle School | Full virtual | 421 |
| 2017-18 | Union County | Tennessee Virtual Academy | Full virtual | 1216 |
| 2024-25 | Union County | Tennessee Virtual Academy | Full virtual | 1867 |
| 2024-25 | Union County | Tennessee Virtual Junior High School | Full virtual | 856 |
Query 5 — County-level school enrollment change (non-virtual) versus housing units permitted 2017-2024.
WITH e AS (
SELECT county_fips,
sum(enrollment) FILTER (
WHERE school_year = '2017-18'
) e17,
sum(enrollment) FILTER (
WHERE school_year = '2024-25'
) e24
FROM tn_nces_public_schools
WHERE school_year IN ('2017-18', '2024-25')
AND coalesce(virtual, '') <> 'Full virtual'
GROUP BY 1
),
p AS (
SELECT geoid,
sum(units_total) u
FROM tn_bps_county_permits
WHERE YEAR BETWEEN 2017 AND 2024
GROUP BY 1
),
pop AS (
SELECT geoid,
pop_estimate
FROM tn_census_county_pop_est
WHERE YEAR = 2017
)
SELECT c.name,
e.e17,
e.e24,
e.e24 - e.e17 chg,
round(100.0 * (e.e24 - e.e17) / e.e17, 1) pct,
p.u units,
round(1000.0 * p.u / pop.pop_estimate, 1) per1k,
rank() OVER (
ORDER BY p.u DESC NULLS LAST
) units_rank,
rank() OVER (
ORDER BY e.e24 - e.e17 DESC
) chg_rank
FROM tn_census_counties c
LEFT JOIN e ON e.county_fips = c.geoid
LEFT JOIN p ON p.geoid = c.geoid
LEFT JOIN pop ON pop.geoid = c.geoid
ORDER BY units DESC NULLS LAST
| name | e17 | e24 | chg | pct | units | per1k | units_rank | chg_rank |
|---|---|---|---|---|---|---|---|---|
| Davidson County | 85583 | 84879 | -704 | -0.8 | 86283 | 122.6 | 1 | 87 |
| Rutherford County | 54133 | 61904 | 7771 | 14.4 | 28327 | 88.6 | 2 | 1 |
| Knox County | 60937 | 59932 | -1005 | -1.6 | 24337 | 52.3 | 3 | 92 |
| Montgomery County | 34841 | 38634 | 3793 | 10.9 | 22928 | 111.7 | 4 | 2 |
| Williamson County | 43025 | 44637 | 1612 | 3.7 | 18391 | 80.2 | 5 | 4 |
| Hamilton County | 44794 | 45727 | 933 | 2.1 | 17518 | 48.9 | 6 | 6 |
| Wilson County | 22061 | 25216 | 3155 | 14.3 | 16401 | 119.6 | 7 | 3 |
| Shelby County | 153052 | 149173 | -3879 | -2.5 | 15030 | 16.1 | 8 | 95 |
| Sumner County | 29636 | 30671 | 1035 | 3.5 | 14826 | 80.0 | 9 | 5 |
| Maury County | 12791 | 13098 | 307 | 2.4 | 13985 | 149.0 | 10 | 9 |
| Sevier County | 14512 | 14408 | -104 | -0.7 | 9675 | 99.7 | 11 | 33 |
| Blount County | 18166 | 18195 | 29 | 0.2 | 6869 | 52.6 | 12 | 17 |
| Robertson County | 11331 | 11201 | -130 | -1.1 | 6041 | 85.3 | 13 | 37 |
| Washington County | 16388 | 15750 | -638 | -3.9 | 6001 | 46.2 | 14 | 83 |
| Loudon County | 7184 | 7723 | 539 | 7.5 | 4763 | 90.8 | 15 | 7 |
| Putnam County | 11416 | 11776 | 360 | 3.2 | 4681 | 61.1 | 16 | 8 |
| Bradley County | 15984 | 15960 | -24 | -0.2 | 4639 | 44.0 | 17 | 23 |
| Sullivan County | 20840 | 18818 | -2022 | -9.7 | 3728 | 23.8 | 18 | 94 |
| Dickson County | 8387 | 7737 | -650 | -7.8 | 3499 | 66.2 | 19 | 85 |
| Cumberland County | 7419 | 6970 | -449 | -6.1 | 3491 | 59.3 | 20 | 72 |
| Fayette County | 3560 | 2941 | -619 | -17.4 | 3067 | 75.7 | 21 | 82 |
| Bedford County | 8754 | 9042 | 288 | 3.3 | 3004 | 62.2 | 22 | 10 |
| Anderson County | 11961 | 12027 | 66 | 0.6 | 2588 | 34.1 | 23 | 15 |
| Franklin County | 5876 | 5355 | -521 | -8.9 | 2558 | 61.1 | 24 | 76 |
| Cheatham County | 6277 | 5381 | -896 | -14.3 | 2454 | 60.7 | 25 | 91 |
25 out of 95 are listed here.
Query 6 — Correlation between permits per 1,000 residents and non-virtual enrollment change by county, plus permit-tier summaries.
WITH e AS (
SELECT county_fips,
sum(enrollment) FILTER (
WHERE school_year = '2017-18'
) e17,
sum(enrollment) FILTER (
WHERE school_year = '2024-25'
) e24
FROM tn_nces_public_schools
WHERE school_year IN ('2017-18', '2024-25')
AND coalesce(virtual, '') <> 'Full virtual'
GROUP BY 1
),
p AS (
SELECT geoid,
sum(units_total) u
FROM tn_bps_county_permits
WHERE YEAR BETWEEN 2017 AND 2024
GROUP BY 1
),
pop AS (
SELECT geoid,
pop_estimate
FROM tn_census_county_pop_est
WHERE YEAR = 2017
),
j AS (
SELECT c.geoid,
e.e17,
e.e24,
100.0 * (e.e24 - e.e17) / e.e17 pct,
coalesce(p.u, 0) u,
1000.0 * coalesce(p.u, 0) / pop.pop_estimate per1k
FROM tn_census_counties c
JOIN e ON e.county_fips = c.geoid
LEFT JOIN p ON p.geoid = c.geoid
JOIN pop ON pop.geoid = c.geoid
),
r AS (
SELECT *,
rank() OVER (
ORDER BY per1k
) rp,
rank() OVER (
ORDER BY pct
) re,
ntile(4) OVER (
ORDER BY per1k DESC
) q
FROM j
)
SELECT (
SELECT round(corr(rp, re)::numeric, 2)
FROM r
) spearman,
(
SELECT round(corr(per1k, pct)::numeric, 2)
FROM r
) pearson,
q,
count(*) n,
round(min(per1k), 1) min_per1k,
sum(e24 - e17) chg,
round(100.0 * sum(e24 - e17) / sum(e17), 1) pct,
count(*) FILTER (
WHERE e24 > e17
) gaining
FROM r
GROUP BY q
ORDER BY q
| spearman | pearson | q | n | min_per1k | chg | pct | gaining |
|---|---|---|---|---|---|---|---|
| 0.45 | 0.54 | 1 | 24 | 51.4 | 13191 | 2.9 | 12 |
| 0.45 | 0.54 | 2 | 24 | 23.8 | -7100 | -3.4 | 5 |
| 0.45 | 0.54 | 3 | 24 | 6.1 | -12432 | -4.7 | 2 |
| 0.45 | 0.54 | 4 | 23 | 0.0 | -4274 | -6.7 | 1 |
Query 7 — Count schools in each change category for the legend.
WITH a AS (
SELECT ncessch,
enrollment e
FROM tn_nces_public_schools
WHERE school_year = '2017-18'
),
b AS (
SELECT ncessch,
enrollment e
FROM tn_nces_public_schools
WHERE school_year = '2024-25'
)
SELECT CASE
WHEN a.ncessch IS NULL THEN 'new'
WHEN b.ncessch IS NULL THEN 'gone'
WHEN a.e IS NULL
OR b.e IS NULL THEN 'na'
WHEN b.e - a.e <= -200 THEN 'l2'
WHEN b.e - a.e <= -50 THEN 'l1'
WHEN b.e - a.e < 50 THEN 'z'
WHEN b.e - a.e < 200 THEN 'g1'
ELSE 'g2'
END cat,
count(*),
sum(coalesce(b.e, 0) - coalesce(a.e, 0)) net
FROM a
FULL JOIN b USING (ncessch)
GROUP BY 1
ORDER BY 1
| cat | count | net |
|---|---|---|
| g1 | 202 | 19019 |
| g2 | 68 | 21833 |
| gone | 124 | -36161 |
| l1 | 483 | -49501 |
| l2 | 95 | -28844 |
| na | 13 | -4882 |
| new | 263 | 82539 |
| z | 804 | -3607 |
Map Layer Queries
School Enrollment Change 2017-18 to 2024-25: Counties
SELECT geom,
name AS "County"
FROM tn_census_counties
Picture: maps/turn-1-school-enrollment-change.png
School Enrollment Change 2017-18 to 2024-25: Schools by enrollment change — colored by "cat": Lost 200+ students (#b2182b), Lost 50–199 (#ef8a62), Within ±49 (#e0e0e0), Gained 50–199 (#67a9cf), Gained 200+ (#2166ac), New school ID since 2017-18 (#1b9e77), School ID not listed in 2024-25 (#000000), Enrollment not reported (#ffffff)
WITH a AS (
SELECT ncessch,
enrollment e,
school_name,
district_name,
geom
FROM tn_nces_public_schools
WHERE school_year = '2017-18'
),
b AS (
SELECT ncessch,
enrollment e,
school_name,
district_name,
virtual,
geom
FROM tn_nces_public_schools
WHERE school_year = '2024-25'
)
SELECT coalesce(b.geom, a.geom) geom,
coalesce(b.school_name, a.school_name) AS "School",
coalesce(b.district_name, a.district_name) AS "District",
a.e AS "Enrollment 2017-18",
b.e AS "Enrollment 2024-25",
b.e - a.e AS "Change",
b.virtual AS "Virtual",
CASE
WHEN a.ncessch IS NULL THEN 'new'
WHEN b.ncessch IS NULL THEN 'gone'
WHEN a.e IS NULL
OR b.e IS NULL THEN 'na'
WHEN b.e - a.e <= -200 THEN 'l2'
WHEN b.e - a.e <= -50 THEN 'l1'
WHEN b.e - a.e < 50 THEN 'z'
WHEN b.e - a.e < 200 THEN 'g1'
ELSE 'g2'
END AS cat
FROM a
FULL JOIN b USING (ncessch)
Picture: maps/turn-1-school-enrollment-change.png
Housing Permits vs School Enrollment Change: Counties by housing units permitted per 1,000 residents, 2017–2024 — colored by "tier": 80+ units per 1,000 (#54278f), 40–79 (#9e9ac8), 10–39 (#dadaeb), Under 10 (#fcfbfd), No permit record (#ffffff)
WITH e AS (
SELECT county_fips,
sum(enrollment) FILTER (
WHERE school_year = '2017-18'
) e17,
sum(enrollment) FILTER (
WHERE school_year = '2024-25'
) e24
FROM tn_nces_public_schools
WHERE school_year IN ('2017-18', '2024-25')
AND coalesce(virtual, '') <> 'Full virtual'
GROUP BY 1
),
p AS (
SELECT geoid,
sum(units_total) u
FROM tn_bps_county_permits
WHERE YEAR BETWEEN 2017 AND 2024
GROUP BY 1
),
pop AS (
SELECT geoid,
pop_estimate
FROM tn_census_county_pop_est
WHERE YEAR = 2017
)
SELECT c.geom,
c.name AS "County",
p.u AS "Housing units permitted 2017-2024",
round(1000.0 * p.u / pop.pop_estimate, 1) AS "Units per 1,000 residents(2017 pop)",
e.e17 AS "Enrollment 2017-18 (excl. virtual)",
e.e24 AS "Enrollment 2024-25 (excl. virtual)",
e.e24 - e.e17 AS "Enrollment change",
round(100.0 * (e.e24 - e.e17) / e.e17, 1) AS "Enrollment change %",
CASE
WHEN p.u IS NULL THEN 'q0'
WHEN 1000.0 * p.u / pop.pop_estimate >= 80 THEN 'q4'
WHEN 1000.0 * p.u / pop.pop_estimate >= 40 THEN 'q3'
WHEN 1000.0 * p.u / pop.pop_estimate >= 10 THEN 'q2'
ELSE 'q1'
END AS tier
FROM tn_census_counties c
LEFT JOIN e ON e.county_fips = c.geoid
LEFT JOIN p ON p.geoid = c.geoid
LEFT JOIN pop ON pop.geoid = c.geoid
Picture: maps/turn-1-permits-vs-enrollment.png
Housing Permits vs School Enrollment Change: County enrollment change (excl. virtual): gained — colored by "dir": Enrollment rose (#1a9850), Enrollment fell (#f46d43)
WITH e AS (
SELECT county_fips,
sum(enrollment) FILTER (
WHERE school_year = '2017-18'
) e17,
sum(enrollment) FILTER (
WHERE school_year = '2024-25'
) e24
FROM tn_nces_public_schools
WHERE school_year IN ('2017-18', '2024-25')
AND coalesce(virtual, '') <> 'Full virtual'
GROUP BY 1
)
SELECT ST_PointOnSurface(c.geom) geom,
c.name AS "County",
e.e24 - e.e17 AS "Enrollment change",
round(100.0 * (e.e24 - e.e17) / e.e17, 1) AS "Change %",
CASE
WHEN e.e24 > e.e17 THEN 'up'
ELSE 'down'
END dir
FROM tn_census_counties c
JOIN e ON e.county_fips = c.geoid
Picture: maps/turn-1-permits-vs-enrollment.png
Housing Permits vs School Enrollment Change: Counties by housing units permitted per 1,000 residents, 2017–2024 — colored by "tier": 80+ units per 1,000 (#54278f), 40–79 (#9e9ac8), 10–39 (#dadaeb), Under 10 (#fcfbfd), No permit record (#ffffff)
WITH e AS (
SELECT county_fips,
sum(enrollment) FILTER (
WHERE school_year = '2017-18'
) e17,
sum(enrollment) FILTER (
WHERE school_year = '2024-25'
) e24
FROM tn_nces_public_schools
WHERE school_year IN ('2017-18', '2024-25')
AND coalesce(virtual, '') <> 'Full virtual'
GROUP BY 1
),
p AS (
SELECT geoid,
sum(units_total) u
FROM tn_bps_county_permits
WHERE YEAR BETWEEN 2017 AND 2024
GROUP BY 1
),
pop AS (
SELECT geoid,
pop_estimate
FROM tn_census_county_pop_est
WHERE YEAR = 2017
)
SELECT c.geom,
c.name AS "County",
p.u AS "Housing units permitted 2017-2024",
round(1000.0 * p.u / pop.pop_estimate, 1) AS "Units per 1,000 residents(2017 pop)",
e.e17 AS "Enrollment 2017-18 (excl. virtual)",
e.e24 AS "Enrollment 2024-25 (excl. virtual)",
e.e24 - e.e17 AS "Enrollment change",
round(100.0 * (e.e24 - e.e17) / e.e17, 1) AS "Enrollment change %",
CASE
WHEN p.u IS NULL THEN 'q0'
WHEN 1000.0 * p.u / pop.pop_estimate >= 80 THEN 'q4'
WHEN 1000.0 * p.u / pop.pop_estimate >= 40 THEN 'q3'
WHEN 1000.0 * p.u / pop.pop_estimate >= 10 THEN 'q2'
ELSE 'q1'
END AS tier
FROM tn_census_counties c
LEFT JOIN e ON e.county_fips = c.geoid
LEFT JOIN p ON p.geoid = c.geoid
LEFT JOIN pop ON pop.geoid = c.geoid
Picture: maps/turn-1-permits-vs-enrollment-2.png
Housing Permits vs School Enrollment Change: County school enrollment change, 2017-18 to 2024-25 (excl. full-virtual) — colored by "dir": Enrollment rose (#1a9850), Enrollment fell (#f46d43)
WITH e AS (
SELECT county_fips,
sum(enrollment) FILTER (
WHERE school_year = '2017-18'
) e17,
sum(enrollment) FILTER (
WHERE school_year = '2024-25'
) e24
FROM tn_nces_public_schools
WHERE school_year IN ('2017-18', '2024-25')
AND coalesce(virtual, '') <> 'Full virtual'
GROUP BY 1
)
SELECT ST_PointOnSurface(c.geom) geom,
c.name AS "County",
e.e24 - e.e17 AS "Enrollment change",
round(100.0 * (e.e24 - e.e17) / e.e17, 1) AS "Change %",
CASE
WHEN e.e24 > e.e17 THEN 'up'
ELSE 'down'
END dir
FROM tn_census_counties c
JOIN e ON e.county_fips = c.geoid
Picture: maps/turn-1-permits-vs-enrollment-2.png
Used Data Sets
tn_bps_county_permits— last synced 2026-10-14 · 3,288 rowstn_census_counties— last synced 2026-09-27 · 95 rowstn_census_county_pop_est— last synced 2026-10-15 · 5,320 rowstn_nces_public_schools— last synced 2026-10-09 · 15,167 rows
