SQL Inspection Reference (M0)
Standard set of read-only queries used to check the state of a PostgreSQL/PostGIS database
before trusting any assumption about it — schemas present, tables that exist, column types,
geometry/SRID, row counts, and spatial indexes. Run this pattern (“M0”) at the start of any
milestone, before writing analytical SQL against a table, so decisions are based on the live
database rather than on what a doc says it should look like (this project has already found
cases where docs and live state diverged — see docs/methodology.md, raw OSM CRS note).
Also useful as interview prep: each query maps to a specific “how do you check X in Postgres” question.
1. List non-system schemas
What schemas actually exist in this database, excluding Postgres internals.
1
2
3
4
5
6
7
8
SELECT
schema_name
FROM
information_schema.schemata
WHERE
schema_name NOT IN ('pg_catalog', 'information_schema')
AND schema_name NOT LIKE 'pg_toast%'
AND schema_name NOT LIKE 'pg_temp%';
2. List tables in specific schemas
Confirms which tables exist in a given schema (or set of schemas) — useful to check a schema is empty before building into it, or to see what a prior step actually created.
1
2
3
4
5
6
7
8
9
10
11
SELECT
table_schema,
table_name
FROM
information_schema.tables
WHERE
table_schema IN ('raw', 'processed', 'analysis')
AND table_type = 'BASE TABLE'
ORDER BY
table_schema,
table_name;
table_type = 'BASE TABLE' excludes views — drop that filter if you want views included too.
3. Inspect columns and types for one table
The information_schema.columns equivalent of \d <table> — data type, underlying UDT name
(useful for spotting geometry columns, which show as USER-DEFINED), and nullability.
1
2
3
4
5
6
7
8
9
10
11
12
SELECT
column_name AS "Name",
data_type AS "Type",
udt_name AS "UDT",
is_nullable AS "Nullable"
FROM
information_schema.columns
WHERE
table_schema = 'PLACEHOLDER_SCHEMA'
AND table_name = 'PLACEHOLDER_TABLE'
ORDER BY
ordinal_position;
Order by ordinal_position for the table’s real column order; ordering by data_type (as used
during this project’s M0 pass) groups similar columns together instead, which can be easier to
scan when checking many tables at once.
4. Geometry column type and SRID
The PostGIS-specific check: geometry type, SRID, and dimensionality for every spatial table in the database. This is the fastest way to confirm a table is in the CRS you think it’s in — never trust a dataset report’s stated CRS without checking this directly.
1
2
3
4
5
6
7
8
9
10
11
12
SELECT
f_table_schema AS "Schema",
f_table_name AS "Table",
f_geometry_column AS "Geometry",
type AS "Type",
srid,
coord_dimension AS "Dimension"
FROM
public.geometry_columns
ORDER BY
f_table_schema,
f_table_name;
Note the geometry column name isn’t always geom — check it here rather than assuming (this
project’s processed.stations uses geometry, while every other processed table uses geom).
5. Row count for one table
Exact count, not an estimate. Slow on very large tables (millions of rows) — for a fast
approximation instead, query pg_stat_user_tables.n_live_tup.
1
2
SELECT COUNT(*) AS row_count
FROM "PLACEHOLDER_SCHEMA"."PLACEHOLDER_TABLE";
6. Spatial (GiST) indexes present in a schema
Confirms whether a geometry column already has a GiST index — required before running
ST_Intersects/ST_Intersection against a large table, or you get a sequential scan. Don’t
assume an index exists just because a column is typed geometry; indexes are created
explicitly.
1
2
3
4
5
6
7
8
9
10
SELECT
schemaname,
tablename,
indexname,
indexdef
FROM
pg_indexes
WHERE
schemaname = 'PLACEHOLDER_SCHEMA'
AND indexdef ILIKE '%gist%';
Why this matters (interview framing)
information_schemavs.pg_catalog:information_schemais the SQL-standard, cross-database way to inspect metadata (schemas, tables, columns);pg_indexesandgeometry_columnsare Postgres/PostGIS-specific views layered on top for things the SQL standard doesn’t cover (indexes, spatial reference systems).- Why check before you build: catches drift between documentation and live state early —
cheaper to find a wrong assumption in a
SELECTthan after building an hour of analysis on top of it. - Why GiST specifically: PostGIS geometry columns need a spatial index (GiST, or SP-GiST in
some cases) to be queried efficiently — a plain B-tree index (Postgres’s default) doesn’t
understand bounding-box containment/overlap, which is what spatial predicates like
ST_Intersectsrely on. - Exact vs. approximate row counts:
COUNT(*)is always correct but requires a full scan;pg_stat_user_tablesis instant but reflects the lastANALYZE/VACUUM, so it can drift from the true count on a frequently-changing table.