# =============================================================================
# These queries are from the PostGIS tutorial at https://postgis.net/workshops/postgis-intro/.
# =============================================================================

import file=postgis_tutorial_schema_indexes
----

import file=postgis_tutorial_stats
----

# 11.2b
opt
SELECT
    name, boroname
FROM
    nyc_neighborhoods
WHERE
    st_intersects(
        geom,
        st_geomfromtext('POINT(583571 4506714)', 26918)
    )
ORDER BY
    name, boroname
----
sort
 ├── columns: name:3 boroname:2
 ├── immutable
 ├── ordering: +3,+2
 └── project
      ├── columns: boroname:2 name:3
      ├── immutable
      └── select
           ├── columns: boroname:2 name:3 geom:4!null
           ├── immutable
           ├── index-join nyc_neighborhoods
           │    ├── columns: boroname:2 name:3 geom:4
           │    └── inverted-filter
           │         ├── columns: gid:1!null
           │         ├── inverted expression: /7
           │         │    ├── tight: false, unique: false
           │         │    └── union spans: ["B\xfd\xff\xff\xff\xff\xff\xff\xff\xff", "B\xfd\xff\xff\xff\xff\xff\xff\xff\xff"]
           │         ├── pre-filterer expression
           │         │    └── st_intersects('0101000020266900000000000026CF21410000008016315141', geom:4)
           │         ├── key: (1)
           │         └── scan nyc_neighborhoods@nyc_neighborhoods_geom_idx,inverted
           │              ├── columns: gid:1!null geom_inverted_key:7!null
           │              ├── inverted constraint: /7/1
           │              │    └── spans: ["B\xfd\xff\xff\xff\xff\xff\xff\xff\xff", "B\xfd\xff\xff\xff\xff\xff\xff\xff\xff"]
           │              ├── key: (1)
           │              └── fd: (1)-->(7)
           └── filters
                └── st_intersects(geom:4, '0101000020266900000000000026CF21410000008016315141') [outer=(4), immutable, constraints=(/4: (/NULL - ])]

# 11.6
opt
SELECT
    name
FROM
    nyc_streets
WHERE
    st_dwithin(
        geom,
        st_geomfromtext('POINT(583571 4506714)', 26918),
        10
    )
ORDER BY
    name ASC
----
sort
 ├── columns: name:3
 ├── immutable
 ├── ordering: +3
 └── project
      ├── columns: name:3
      ├── immutable
      └── select
           ├── columns: name:3 geom:6!null
           ├── immutable
           ├── index-join nyc_streets
           │    ├── columns: name:3 geom:6
           │    └── inverted-filter
           │         ├── columns: gid:1!null
           │         ├── inverted expression: /9
           │         │    ├── tight: false, unique: false
           │         │    └── union spans: ["B\xfd\xff\xff\xff\xff\xff\xff\xff\xff", "B\xfd\xff\xff\xff\xff\xff\xff\xff\xff"]
           │         ├── pre-filterer expression
           │         │    └── st_dwithin('0101000020266900000000000026CF21410000008016315141', geom:6, 10.0)
           │         ├── key: (1)
           │         └── scan nyc_streets@nyc_streets_geom_idx,inverted
           │              ├── columns: gid:1!null geom_inverted_key:9!null
           │              ├── inverted constraint: /9/1
           │              │    └── spans: ["B\xfd\xff\xff\xff\xff\xff\xff\xff\xff", "B\xfd\xff\xff\xff\xff\xff\xff\xff\xff"]
           │              ├── key: (1)
           │              └── fd: (1)-->(9)
           └── filters
                └── st_dwithin(geom:6, '0101000020266900000000000026CF21410000008016315141', 10.0) [outer=(6), immutable, constraints=(/6: (/NULL - ])]

# 12.1.2
opt
SELECT
    name, boroname
FROM
    nyc_neighborhoods
WHERE
    st_intersects(
        geom,
        st_geomfromtext(
            'LINESTRING(586782 4504202,586864 4504216)',
            26918
        )
    )
ORDER BY
    name, boroname
----
sort
 ├── columns: name:3 boroname:2
 ├── immutable
 ├── ordering: +3,+2
 └── project
      ├── columns: boroname:2 name:3
      ├── immutable
      └── select
           ├── columns: boroname:2 name:3 geom:4!null
           ├── immutable
           ├── index-join nyc_neighborhoods
           │    ├── columns: boroname:2 name:3 geom:4
           │    └── inverted-filter
           │         ├── columns: gid:1!null
           │         ├── inverted expression: /7
           │         │    ├── tight: false, unique: false
           │         │    └── union spans: ["B\xfd\xff\xff\xff\xff\xff\xff\xff\xff", "B\xfd\xff\xff\xff\xff\xff\xff\xff\xff"]
           │         ├── pre-filterer expression
           │         │    └── st_intersects('01020000202669000002000000000000003CE8214100000080A22E514100000000E0E8214100000000A62E5141', geom:4)
           │         ├── key: (1)
           │         └── scan nyc_neighborhoods@nyc_neighborhoods_geom_idx,inverted
           │              ├── columns: gid:1!null geom_inverted_key:7!null
           │              ├── inverted constraint: /7/1
           │              │    └── spans: ["B\xfd\xff\xff\xff\xff\xff\xff\xff\xff", "B\xfd\xff\xff\xff\xff\xff\xff\xff\xff"]
           │              ├── key: (1)
           │              └── fd: (1)-->(7)
           └── filters
                └── st_intersects(geom:4, '01020000202669000002000000000000003CE8214100000080A22E514100000000E0E8214100000000A62E5141') [outer=(4), immutable, constraints=(/4: (/NULL - ])]

# 12.2.3
opt
SELECT
    name
FROM
    nyc_streets
WHERE
    st_dwithin(
        geom,
        st_geomfromtext(
            'LINESTRING(586782 4504202,586864 4504216)',
            26918
        ),
        0.1
    )
ORDER BY
    name
----
sort
 ├── columns: name:3
 ├── immutable
 ├── ordering: +3
 └── project
      ├── columns: name:3
      ├── immutable
      └── select
           ├── columns: name:3 geom:6!null
           ├── immutable
           ├── index-join nyc_streets
           │    ├── columns: name:3 geom:6
           │    └── inverted-filter
           │         ├── columns: gid:1!null
           │         ├── inverted expression: /9
           │         │    ├── tight: false, unique: false
           │         │    └── union spans: ["B\xfd\xff\xff\xff\xff\xff\xff\xff\xff", "B\xfd\xff\xff\xff\xff\xff\xff\xff\xff"]
           │         ├── pre-filterer expression
           │         │    └── st_dwithin('01020000202669000002000000000000003CE8214100000080A22E514100000000E0E8214100000000A62E5141', geom:6, 0.1)
           │         ├── key: (1)
           │         └── scan nyc_streets@nyc_streets_geom_idx,inverted
           │              ├── columns: gid:1!null geom_inverted_key:9!null
           │              ├── inverted constraint: /9/1
           │              │    └── spans: ["B\xfd\xff\xff\xff\xff\xff\xff\xff\xff", "B\xfd\xff\xff\xff\xff\xff\xff\xff\xff"]
           │              ├── key: (1)
           │              └── fd: (1)-->(9)
           └── filters
                └── st_dwithin(geom:6, '01020000202669000002000000000000003CE8214100000080A22E514100000000E0E8214100000000A62E5141', 0.1) [outer=(6), immutable, constraints=(/6: (/NULL - ])]

# 12.2.4
opt
SELECT
    sum(popn_total)
FROM
    nyc_census_blocks
WHERE
    st_dwithin(
        geom,
        st_geomfromtext(
            'LINESTRING(586782 4504202,586864 4504216)',
            26918
        ),
        50
    )
----
scalar-group-by
 ├── columns: sum:14
 ├── cardinality: [1 - 1]
 ├── immutable
 ├── key: ()
 ├── fd: ()-->(14)
 ├── select
 │    ├── columns: popn_total:3 geom:10!null
 │    ├── immutable
 │    ├── index-join nyc_census_blocks
 │    │    ├── columns: popn_total:3 geom:10
 │    │    └── inverted-filter
 │    │         ├── columns: gid:1!null
 │    │         ├── inverted expression: /13
 │    │         │    ├── tight: false, unique: false
 │    │         │    └── union spans: ["B\xfd\xff\xff\xff\xff\xff\xff\xff\xff", "B\xfd\xff\xff\xff\xff\xff\xff\xff\xff"]
 │    │         ├── pre-filterer expression
 │    │         │    └── st_dwithin('01020000202669000002000000000000003CE8214100000080A22E514100000000E0E8214100000000A62E5141', geom:10, 50.0)
 │    │         ├── key: (1)
 │    │         └── scan nyc_census_blocks@nyc_census_blocks_geom_idx,inverted
 │    │              ├── columns: gid:1!null geom_inverted_key:13!null
 │    │              ├── inverted constraint: /13/1
 │    │              │    └── spans: ["B\xfd\xff\xff\xff\xff\xff\xff\xff\xff", "B\xfd\xff\xff\xff\xff\xff\xff\xff\xff"]
 │    │              ├── key: (1)
 │    │              └── fd: (1)-->(13)
 │    └── filters
 │         └── st_dwithin(geom:10, '01020000202669000002000000000000003CE8214100000080A22E514100000000E0E8214100000000A62E5141', 50.0) [outer=(10), immutable, constraints=(/10: (/NULL - ])]
 └── aggregations
      └── sum [as=sum:14, outer=(3)]
           └── popn_total:3

# 13.0
opt
SELECT
    subways.name AS subway_name,
    neighborhoods.name AS neighborhood_name,
    neighborhoods.boroname AS borough
FROM
    nyc_neighborhoods AS neighborhoods
    JOIN nyc_subway_stations AS subways ON
            st_contains(neighborhoods.geom, subways.geom)
WHERE
    subways.name = 'Broad St'
ORDER BY
    subway_name, neighborhood_name, borough
----
sort
 ├── columns: subway_name:11!null neighborhood_name:3 borough:2
 ├── immutable
 ├── fd: ()-->(11)
 ├── ordering: +3,+2 opt(11) [actual: +3,+2]
 └── project
      ├── columns: boroname:2 neighborhoods.name:3 subways.name:11!null
      ├── immutable
      ├── fd: ()-->(11)
      └── inner-join (lookup nyc_neighborhoods [as=neighborhoods])
           ├── columns: boroname:2 neighborhoods.name:3 neighborhoods.geom:4!null subways.name:11!null subways.geom:23!null
           ├── key columns: [46] = [1]
           ├── lookup columns are key
           ├── immutable
           ├── fd: ()-->(11)
           ├── inner-join (inverted nyc_neighborhoods@nyc_neighborhoods_geom_idx,inverted [as=neighborhoods])
           │    ├── columns: subways.name:11!null subways.geom:23 neighborhoods.gid:46!null
           │    ├── inverted-expr
           │    │    └── st_coveredby(subways.geom:23, neighborhoods.geom:49)
           │    ├── fd: ()-->(11)
           │    ├── select
           │    │    ├── columns: subways.name:11!null subways.geom:23
           │    │    ├── fd: ()-->(11)
           │    │    ├── scan nyc_subway_stations [as=subways]
           │    │    │    └── columns: subways.name:11 subways.geom:23
           │    │    └── filters
           │    │         └── subways.name:11 = 'Broad St' [outer=(11), constraints=(/11: [/'Broad St' - /'Broad St']; tight), fd=()-->(11)]
           │    └── filters (true)
           └── filters
                └── st_contains(neighborhoods.geom:4, subways.geom:23) [outer=(4,23), immutable, constraints=(/4: (/NULL - ]; /23: (/NULL - ])]

# 13.1a
opt
SELECT
    neighborhoods.name AS neighborhood_name,
    sum(census.popn_total) AS population,
    100.0 * sum(census.popn_white) / sum(census.popn_total)
        AS white_pct,
    100.0 * sum(census.popn_black) / sum(census.popn_total)
        AS black_pct
FROM
    nyc_neighborhoods AS neighborhoods
    JOIN nyc_census_blocks AS census ON
            st_intersects(neighborhoods.geom, census.geom)
WHERE
    neighborhoods.boroname = 'Manhattan'
GROUP BY
    neighborhoods.name
ORDER BY
    white_pct DESC
----
sort
 ├── columns: neighborhood_name:3 population:21 white_pct:24 black_pct:25
 ├── immutable
 ├── key: (3)
 ├── fd: (3)-->(21,24,25)
 ├── ordering: -24
 └── project
      ├── columns: white_pct:24 black_pct:25 name:3 sum:21
      ├── immutable
      ├── key: (3)
      ├── fd: (3)-->(21,24,25)
      ├── group-by (hash)
      │    ├── columns: name:3 sum:21 sum:22 sum:23
      │    ├── grouping columns: name:3
      │    ├── immutable
      │    ├── key: (3)
      │    ├── fd: (3)-->(21-23)
      │    ├── inner-join (lookup nyc_census_blocks [as=census])
      │    │    ├── columns: neighborhoods.boroname:2!null name:3 neighborhoods.geom:4!null popn_total:10 popn_white:11 popn_black:12 census.geom:17!null
      │    │    ├── key columns: [26] = [8]
      │    │    ├── lookup columns are key
      │    │    ├── immutable
      │    │    ├── fd: ()-->(2)
      │    │    ├── inner-join (inverted nyc_census_blocks@nyc_census_blocks_geom_idx,inverted [as=census])
      │    │    │    ├── columns: neighborhoods.boroname:2!null name:3 neighborhoods.geom:4 census.gid:26!null
      │    │    │    ├── inverted-expr
      │    │    │    │    └── st_intersects(neighborhoods.geom:4, census.geom:35)
      │    │    │    ├── fd: ()-->(2)
      │    │    │    ├── select
      │    │    │    │    ├── columns: neighborhoods.boroname:2!null name:3 neighborhoods.geom:4
      │    │    │    │    ├── fd: ()-->(2)
      │    │    │    │    ├── scan nyc_neighborhoods [as=neighborhoods]
      │    │    │    │    │    └── columns: neighborhoods.boroname:2 name:3 neighborhoods.geom:4
      │    │    │    │    └── filters
      │    │    │    │         └── neighborhoods.boroname:2 = 'Manhattan' [outer=(2), constraints=(/2: [/'Manhattan' - /'Manhattan']; tight), fd=()-->(2)]
      │    │    │    └── filters (true)
      │    │    └── filters
      │    │         └── st_intersects(neighborhoods.geom:4, census.geom:17) [outer=(4,17), immutable, constraints=(/4: (/NULL - ]; /17: (/NULL - ])]
      │    └── aggregations
      │         ├── sum [as=sum:21, outer=(10)]
      │         │    └── popn_total:10
      │         ├── sum [as=sum:22, outer=(11)]
      │         │    └── popn_white:11
      │         └── sum [as=sum:23, outer=(12)]
      │              └── popn_black:12
      └── projections
           ├── (sum:22 * 100.0) / sum:21 [as=white_pct:24, outer=(21,22), immutable]
           └── (sum:23 * 100.0) / sum:21 [as=black_pct:25, outer=(21,23), immutable]

# 13.1c
opt
SELECT
    100.0 * sum(popn_white) / sum(popn_total) AS white_pct,
    100.0 * sum(popn_black) / sum(popn_total) AS black_pct,
    sum(popn_total) AS popn_total
FROM
    nyc_census_blocks AS census
    JOIN nyc_subway_stations AS subways ON
            st_dwithin(census.geom, subways.geom, 200)
WHERE
    strpos(subways.routes, 'A') > 0
----
project
 ├── columns: white_pct:36 black_pct:37 popn_total:34
 ├── cardinality: [1 - 1]
 ├── immutable
 ├── key: ()
 ├── fd: ()-->(34,36,37)
 ├── scalar-group-by
 │    ├── columns: sum:33 sum:34 sum:35
 │    ├── cardinality: [1 - 1]
 │    ├── immutable
 │    ├── key: ()
 │    ├── fd: ()-->(33-35)
 │    ├── inner-join (lookup nyc_census_blocks [as=census])
 │    │    ├── columns: popn_total:3 popn_white:4 popn_black:5 census.geom:10!null routes:24 subways.geom:29!null
 │    │    ├── key columns: [57] = [1]
 │    │    ├── lookup columns are key
 │    │    ├── immutable
 │    │    ├── inner-join (inverted nyc_census_blocks@nyc_census_blocks_geom_idx,inverted [as=census])
 │    │    │    ├── columns: routes:24 subways.geom:29 census.gid:57!null
 │    │    │    ├── inverted-expr
 │    │    │    │    └── st_dwithin(subways.geom:29, census.geom:66, 200.0)
 │    │    │    ├── immutable
 │    │    │    ├── select
 │    │    │    │    ├── columns: routes:24 subways.geom:29
 │    │    │    │    ├── immutable
 │    │    │    │    ├── scan nyc_subway_stations [as=subways]
 │    │    │    │    │    └── columns: routes:24 subways.geom:29
 │    │    │    │    └── filters
 │    │    │    │         └── strpos(routes:24, 'A') > 0 [outer=(24), immutable]
 │    │    │    └── filters (true)
 │    │    └── filters
 │    │         └── st_dwithin(census.geom:10, subways.geom:29, 200.0) [outer=(10,29), immutable, constraints=(/10: (/NULL - ]; /29: (/NULL - ])]
 │    └── aggregations
 │         ├── sum [as=sum:33, outer=(4)]
 │         │    └── popn_white:4
 │         ├── sum [as=sum:34, outer=(3)]
 │         │    └── popn_total:3
 │         └── sum [as=sum:35, outer=(5)]
 │              └── popn_black:5
 └── projections
      ├── (sum:33 * 100.0) / sum:34 [as=white_pct:36, outer=(33,34), immutable]
      └── (sum:35 * 100.0) / sum:34 [as=black_pct:37, outer=(34,35), immutable]

# 13.2
# The optimal plan for this query is to join census with subways and
# then with lines.
opt
SELECT
    lines.route,
    100.0 * sum(popn_white) / sum(popn_total) AS white_pct,
    100.0 * sum(popn_black) / sum(popn_total) AS black_pct,
    sum(popn_total) AS popn_total
FROM
    nyc_census_blocks AS census
    JOIN nyc_subway_stations AS subways ON
            st_dwithin(census.geom, subways.geom, 200)
    JOIN subway_lines AS lines ON
            strpos(subways.routes, lines.route) > 0
GROUP BY
    lines.route
ORDER BY
    black_pct DESC
----
sort
 ├── columns: route:33 white_pct:40 black_pct:41 popn_total:38
 ├── immutable
 ├── key: (33)
 ├── fd: (33)-->(38,40,41)
 ├── ordering: -41
 └── project
      ├── columns: white_pct:40 black_pct:41 route:33 sum:38
      ├── immutable
      ├── key: (33)
      ├── fd: (33)-->(38,40,41)
      ├── group-by (hash)
      │    ├── columns: route:33 sum:37 sum:38 sum:39
      │    ├── grouping columns: route:33
      │    ├── immutable
      │    ├── key: (33)
      │    ├── fd: (33)-->(37-39)
      │    ├── inner-join (cross)
      │    │    ├── columns: popn_total:3 popn_white:4 popn_black:5 census.geom:10!null routes:24 subways.geom:29!null route:33
      │    │    ├── immutable
      │    │    ├── inner-join (lookup nyc_census_blocks [as=census])
      │    │    │    ├── columns: popn_total:3 popn_white:4 popn_black:5 census.geom:10!null routes:24 subways.geom:29!null
      │    │    │    ├── key columns: [61] = [1]
      │    │    │    ├── lookup columns are key
      │    │    │    ├── immutable
      │    │    │    ├── inner-join (inverted nyc_census_blocks@nyc_census_blocks_geom_idx,inverted [as=census])
      │    │    │    │    ├── columns: routes:24 subways.geom:29 census.gid:61!null
      │    │    │    │    ├── inverted-expr
      │    │    │    │    │    └── st_dwithin(subways.geom:29, census.geom:70, 200.0)
      │    │    │    │    ├── scan nyc_subway_stations [as=subways]
      │    │    │    │    │    └── columns: routes:24 subways.geom:29
      │    │    │    │    └── filters (true)
      │    │    │    └── filters
      │    │    │         └── st_dwithin(census.geom:10, subways.geom:29, 200.0) [outer=(10,29), immutable, constraints=(/10: (/NULL - ]; /29: (/NULL - ])]
      │    │    ├── scan subway_lines [as=lines]
      │    │    │    └── columns: route:33
      │    │    └── filters
      │    │         └── strpos(routes:24, route:33) > 0 [outer=(24,33), immutable]
      │    └── aggregations
      │         ├── sum [as=sum:37, outer=(4)]
      │         │    └── popn_white:4
      │         ├── sum [as=sum:38, outer=(3)]
      │         │    └── popn_total:3
      │         └── sum [as=sum:39, outer=(5)]
      │              └── popn_black:5
      └── projections
           ├── (sum:37 * 100.0) / sum:38 [as=white_pct:40, outer=(37,38), immutable]
           └── (sum:39 * 100.0) / sum:38 [as=black_pct:41, outer=(38,39), immutable]

# 14.1a
opt
SELECT
    s.name, s.routes
FROM
    nyc_subway_stations AS s
    JOIN nyc_neighborhoods AS n ON
            st_contains(n.geom, s.geom)
WHERE
    n.name = 'Little Italy'
----
project
 ├── columns: name:4 routes:11
 ├── immutable
 └── inner-join (lookup nyc_subway_stations [as=s])
      ├── columns: s.name:4 routes:11 s.geom:16!null n.name:22!null n.geom:23!null
      ├── key columns: [34] = [1]
      ├── lookup columns are key
      ├── immutable
      ├── fd: ()-->(22)
      ├── inner-join (inverted nyc_subway_stations@nyc_subway_stations_geom_idx,inverted [as=s])
      │    ├── columns: n.name:22!null n.geom:23 s.gid:34!null
      │    ├── inverted-expr
      │    │    └── st_contains(n.geom:23, s.geom:49)
      │    ├── fd: ()-->(22)
      │    ├── select
      │    │    ├── columns: n.name:22!null n.geom:23
      │    │    ├── fd: ()-->(22)
      │    │    ├── scan nyc_neighborhoods [as=n]
      │    │    │    └── columns: n.name:22 n.geom:23
      │    │    └── filters
      │    │         └── n.name:22 = 'Little Italy' [outer=(22), constraints=(/22: [/'Little Italy' - /'Little Italy']; tight), fd=()-->(22)]
      │    └── filters (true)
      └── filters
           └── st_contains(n.geom:23, s.geom:16) [outer=(16,23), immutable, constraints=(/16: (/NULL - ]; /23: (/NULL - ])]

# 14.2b
opt
SELECT
    DISTINCT n.name, n.boroname
FROM
    nyc_subway_stations AS s
    JOIN nyc_neighborhoods AS n ON
            st_contains(n.geom, s.geom)
WHERE
    strpos(s.routes, '6') > 0
ORDER BY
    n.name, n.boroname
----
sort
 ├── columns: name:22 boroname:21
 ├── immutable
 ├── key: (21,22)
 ├── ordering: +22,+21
 └── distinct-on
      ├── columns: boroname:21 n.name:22
      ├── grouping columns: boroname:21 n.name:22
      ├── immutable
      ├── key: (21,22)
      └── inner-join (lookup nyc_neighborhoods [as=n])
           ├── columns: routes:11 s.geom:16!null boroname:21 n.name:22 n.geom:23!null
           ├── key columns: [27] = [20]
           ├── lookup columns are key
           ├── immutable
           ├── inner-join (inverted nyc_neighborhoods@nyc_neighborhoods_geom_idx,inverted [as=n])
           │    ├── columns: routes:11 s.geom:16 n.gid:27!null
           │    ├── inverted-expr
           │    │    └── st_coveredby(s.geom:16, n.geom:30)
           │    ├── immutable
           │    ├── select
           │    │    ├── columns: routes:11 s.geom:16
           │    │    ├── immutable
           │    │    ├── scan nyc_subway_stations [as=s]
           │    │    │    └── columns: routes:11 s.geom:16
           │    │    └── filters
           │    │         └── strpos(routes:11, '6') > 0 [outer=(11), immutable]
           │    └── filters (true)
           └── filters
                └── st_contains(n.geom:23, s.geom:16) [outer=(16,23), immutable, constraints=(/16: (/NULL - ]; /23: (/NULL - ])]

# 14.2c
opt
SELECT
    sum(popn_total)
FROM
    nyc_neighborhoods AS n
    JOIN nyc_census_blocks AS c ON
            st_intersects(n.geom, c.geom)
WHERE
    n.name = 'Battery Park'
----
scalar-group-by
 ├── columns: sum:21
 ├── cardinality: [1 - 1]
 ├── immutable
 ├── key: ()
 ├── fd: ()-->(21)
 ├── inner-join (lookup nyc_census_blocks [as=c])
 │    ├── columns: name:3!null n.geom:4!null popn_total:10 c.geom:17!null
 │    ├── key columns: [22] = [8]
 │    ├── lookup columns are key
 │    ├── immutable
 │    ├── fd: ()-->(3)
 │    ├── inner-join (inverted nyc_census_blocks@nyc_census_blocks_geom_idx,inverted [as=c])
 │    │    ├── columns: name:3!null n.geom:4 c.gid:22!null
 │    │    ├── inverted-expr
 │    │    │    └── st_intersects(n.geom:4, c.geom:31)
 │    │    ├── fd: ()-->(3)
 │    │    ├── select
 │    │    │    ├── columns: name:3!null n.geom:4
 │    │    │    ├── fd: ()-->(3)
 │    │    │    ├── scan nyc_neighborhoods [as=n]
 │    │    │    │    └── columns: name:3 n.geom:4
 │    │    │    └── filters
 │    │    │         └── name:3 = 'Battery Park' [outer=(3), constraints=(/3: [/'Battery Park' - /'Battery Park']; tight), fd=()-->(3)]
 │    │    └── filters (true)
 │    └── filters
 │         └── st_intersects(n.geom:4, c.geom:17) [outer=(4,17), immutable, constraints=(/4: (/NULL - ]; /17: (/NULL - ])]
 └── aggregations
      └── sum [as=sum:21, outer=(10)]
           └── popn_total:10

# 14.3c
opt
SELECT
    n.name,
    sum(c.popn_total) / (st_area(n.geom) / 1000000.0)
        AS popn_per_sqkm
FROM
    nyc_census_blocks AS c
    JOIN nyc_neighborhoods AS n ON
            st_intersects(c.geom, n.geom)
WHERE
    n.name = 'Upper West Side' OR n.name = 'Upper East Side'
GROUP BY
    n.name, n.geom
ORDER BY
    n.name
----
sort
 ├── columns: name:16!null popn_per_sqkm:22
 ├── immutable
 ├── ordering: +16
 └── project
      ├── columns: popn_per_sqkm:22 name:16!null
      ├── immutable
      ├── group-by (hash)
      │    ├── columns: name:16!null n.geom:17!null sum:21
      │    ├── grouping columns: name:16!null n.geom:17!null
      │    ├── immutable
      │    ├── key: (16,17)
      │    ├── fd: (16,17)-->(21)
      │    ├── inner-join (lookup nyc_census_blocks [as=c])
      │    │    ├── columns: popn_total:3 c.geom:10!null name:16!null n.geom:17!null
      │    │    ├── key columns: [30] = [1]
      │    │    ├── lookup columns are key
      │    │    ├── immutable
      │    │    ├── inner-join (inverted nyc_census_blocks@nyc_census_blocks_geom_idx,inverted [as=c])
      │    │    │    ├── columns: name:16!null n.geom:17 c.gid:30!null
      │    │    │    ├── inverted-expr
      │    │    │    │    └── st_intersects(n.geom:17, c.geom:39)
      │    │    │    ├── select
      │    │    │    │    ├── columns: name:16!null n.geom:17
      │    │    │    │    ├── scan nyc_neighborhoods [as=n]
      │    │    │    │    │    └── columns: name:16 n.geom:17
      │    │    │    │    └── filters
      │    │    │    │         └── (name:16 = 'Upper West Side') OR (name:16 = 'Upper East Side') [outer=(16), constraints=(/16: [/'Upper East Side' - /'Upper East Side'] [/'Upper West Side' - /'Upper West Side']; tight)]
      │    │    │    └── filters (true)
      │    │    └── filters
      │    │         └── st_intersects(c.geom:10, n.geom:17) [outer=(10,17), immutable, constraints=(/10: (/NULL - ]; /17: (/NULL - ])]
      │    └── aggregations
      │         └── sum [as=sum:21, outer=(3)]
      │              └── popn_total:3
      └── projections
           └── sum:21 / (st_area(n.geom:17) / 1e+06) [as=popn_per_sqkm:22, outer=(17,21), immutable]

# 15.0
opt
SELECT
    blocks.blkid
FROM
    nyc_census_blocks AS blocks
    JOIN nyc_subway_stations AS subways ON
            st_contains(blocks.geom, subways.geom)
WHERE
    subways.name = 'Broad St'
----
project
 ├── columns: blkid:2
 ├── immutable
 └── inner-join (lookup nyc_census_blocks [as=blocks])
      ├── columns: blkid:2 blocks.geom:10!null name:17!null subways.geom:29!null
      ├── key columns: [52] = [1]
      ├── lookup columns are key
      ├── immutable
      ├── fd: ()-->(17)
      ├── inner-join (inverted nyc_census_blocks@nyc_census_blocks_geom_idx,inverted [as=blocks])
      │    ├── columns: name:17!null subways.geom:29 blocks.gid:52!null
      │    ├── inverted-expr
      │    │    └── st_coveredby(subways.geom:29, blocks.geom:61)
      │    ├── fd: ()-->(17)
      │    ├── select
      │    │    ├── columns: name:17!null subways.geom:29
      │    │    ├── fd: ()-->(17)
      │    │    ├── scan nyc_subway_stations [as=subways]
      │    │    │    └── columns: name:17 subways.geom:29
      │    │    └── filters
      │    │         └── name:17 = 'Broad St' [outer=(17), constraints=(/17: [/'Broad St' - /'Broad St']; tight), fd=()-->(17)]
      │    └── filters (true)
      └── filters
           └── st_contains(blocks.geom:10, subways.geom:29) [outer=(10,29), immutable, constraints=(/10: (/NULL - ]; /29: (/NULL - ])]
