PostGISSpatial Relationships

ST_Contains

What is ST_Contains?

ST_Contains is a PostGIS spatial predicate that returns true when geometry A completely contains geometry B — meaning every point of B lies inside A, and the two geometries share at least one interior point. It is the inverse of ST_Within: ST_Contains(A, B) is equivalent to ST_Within(B, A).

SQL
ST_Contains(geometry geomA, geometry geomB)boolean

Under the DE-9IM matrix, ST_Contains evaluates the relation T*****FF*, which forbids any part of B from touching A's exterior.

When would you use ST_Contains?

Use ST_Contains whenever you need strict "is inside" semantics — finding all addresses inside a city boundary, all trees inside a park polygon, or all accidents inside a risk zone. It is also widely used in cartographic pipelines to suppress features that fall outside a region of interest before rendering.

SQL
1SELECT cities.name, COUNT(addresses.*) AS n_addresses
2FROM cities
3JOIN addresses ON ST_Contains(cities.geom, addresses.geom)
4GROUP BY cities.name;

FAQs

Does ST_Contains consider a boundary point as contained?

No. ST_Contains requires B to lie in A's interior — a point that sits exactly on A's boundary returns false. If you want boundary-inclusive semantics, use ST_Covers, which returns true as long as no part of B falls in A's exterior.

What's the difference between ST_Contains and ST_ContainsProperly?

ST_Contains allows B to touch A's boundary at some points as long as B also has an interior point inside A. ST_ContainsProperly is stricter: every point of B must lie in A's interior, with no boundary contact whatsoever. Properly-contains is useful for indexed join acceleration and when you need unambiguous interior-only relationships.

Does ST_Contains use spatial indexes?

Yes — ST_Contains is index-accelerated. The planner first runs a bounding-box check (A && B) against the GiST index on the geometry column and only evaluates the full predicate on candidates. Ensure you have CREATE INDEX ... USING GIST (geom) on both tables involved in a join.

Why does ST_Contains(poly, poly) sometimes surprise me at shared edges?

If B's boundary lies partially on A's boundary, the two polygons still satisfy ST_Contains only when B has at least one interior point inside A. For adjacency-style questions where polygons share an edge but neither is "inside" the other, use ST_Touches or ST_Overlaps instead.