Post

SQL Spatial Analysis Functions Cheatsheet

A comprehensive cheatsheet covering all SQL spatial analysis functions, including Spatial Data Types, Geometry Constructors, Input / Output (WKT & WKB), Geometry Properties, Measurement Functions, Spatial Relationship Functions, Spatial Set Operations, Coordinate & Projection, GeoJSON & Geohash, Spatial Indexes, Window & Aggregate Spatial Functions, Cross-Engine Master Table, and Common Patterns & Examples. Includes inline and table-level syntax, add/drop/modify examples, cross-engine compatibility.

SQL Spatial Analysis Functions Cheatsheet

πŸ—ΊοΈ SQL Spatial Analysis Functions Cheatsheet

Geometry types Β· Construction Β· Measurement Β· Relationships Β· Cross-engine compatibility


Legend

SymbolMeaning
βœ…Supported (same or very similar syntax)
⚠️Supported with different syntax or behavior
❌Not supported / no direct equivalent

In Article

Here’s your SQL Spatial Analysis Functions Cheatsheet! It covers 13 sections:

#SectionWhat’s Inside
1Spatial Data TypesPOINT, LINESTRING, POLYGON, GEOMETRY, GEOGRAPHY, SRID table
2Geometry ConstructorsBuild POINT, LINESTRING, POLYGON, MULTI*, GEOMETRYCOLLECTION
3Input / Output (WKT & WKB)ST_AsText, ST_AsBinary, ST_GeomFromText, ST_Envelope
4Geometry PropertiesType, validity, coordinates, rings, collections
5Measurement FunctionsDistance (flat & spherical), Area, Length, Hausdorff
6Spatial Relationship FunctionsST_Contains, ST_Within, ST_Intersects, ST_Touches + all MBR predicates
7Spatial Set OperationsIntersection, Union, Difference, Buffer, ConvexHull, Simplify, Centroid
8Coordinate & ProjectionST_Transform, ST_SwapXY, ST_LineInterpolatePoint, Geohash
9GeoJSON & GeohashImport/export GeoJSON, build FeatureCollection
10Spatial IndexesR-Tree (MySQL), GiST/BRIN (PostGIS), Grid (SQL Server), Oracle setup
11Aggregate Spatial FunctionsST_Collect, ST_Union, ST_Extent, KNN nearest-neighbor
12Cross-Engine Master TableAll features Γ— all engines at a glance
13Common PatternsGPS storage, radius search, geofencing, route length, spatial join, buffer zones

A few important gotchas are highlighted: GEOMETRY vs GEOGRAPHY accuracy tradeoff, SQLite/SpatiaLite requiring a PRAGMA, and the MBR pre-filter + precise function performance pattern for large datasets.


Categories

  1. Spatial Data Types
  2. Geometry Constructors
  3. Input / Output (WKT & WKB)
  4. Geometry Properties
  5. Measurement Functions
  6. Spatial Relationship Functions
  7. Spatial Set Operations
  8. Coordinate & Projection Functions
  9. GeoJSON & Geohash
  10. Spatial Indexes
  11. Window & Aggregate Spatial Functions
  12. Cross-Engine Master Table
  13. Common Patterns & Examples

1. Spatial Data Types

Column types that store geometric/geographic objects.

MySQL Spatial Types

TypeDescriptionExample
POINTSingle X,Y coordinateA shop location
LINESTRINGOrdered sequence of pointsA road, river
POLYGONClosed ring(s) β€” area with holesA city boundary
MULTIPOINTCollection of PointsMultiple ATMs
MULTILINESTRINGCollection of LineStringsMultiple roads
MULTIPOLYGONCollection of PolygonsCountry with islands
GEOMETRYCOLLECTIONMix of any geometry typesMixed dataset
GEOMETRYGeneric β€” accepts any typeFlexible column

Declaring Spatial Columns

1
2
3
4
5
6
7
8
CREATE TABLE locations (
  id          INT            NOT NULL AUTO_INCREMENT PRIMARY KEY,
  name        VARCHAR(100)   NOT NULL,
  position    POINT          NOT NULL SRID 4326,   -- WGS84 lat/lng
  region      POLYGON,
  route       LINESTRING,
  geom        GEOMETRY                             -- accepts any type
);

SRID β€” Spatial Reference System Identifier

SRIDSystemUnitUse
4326WGS 84 (GPS)Degrees lat/lngGlobal GPS coordinates
3857Web MercatorMetersGoogle Maps, OpenStreetMap tiles
32636UTM Zone 36NMetersEgypt / Middle East
2263NY State PlaneFeetLocal US surveys
1
2
3
4
5
6
7
8
-- Create column with enforced SRID
position POINT NOT NULL SRID 4326

-- Check SRID of a geometry
SELECT ST_SRID(position) FROM locations;

-- Change SRID
SELECT ST_SRID(position, 4326) FROM locations;   -- MySQL 8.0+

Cross-Engine β€” Spatial Types

TypeMySQLSQLite + SpatiaLitePostgreSQL + PostGISSQL ServerOracle Spatial
POINTβœ…βœ…βœ…βœ… geometryβœ… SDO_GEOMETRY
LINESTRINGβœ…βœ…βœ…βœ…βœ…
POLYGONβœ…βœ…βœ…βœ…βœ…
GEOMETRY (generic)βœ…βœ…βœ…βœ…βœ…
GEOGRAPHY (spherical)βŒβŒβœ…βœ…βœ…
SRID enforcementβœ… 8.0+βœ…βœ…βœ…βœ…

πŸ’‘ GEOMETRY vs GEOGRAPHY: GEOMETRY uses flat (Euclidean) math β€” fast but inaccurate over long distances. GEOGRAPHY uses spherical (ellipsoidal) math β€” slower but accurate for real-world GPS distances. Use GEOGRAPHY in PostGIS / SQL Server for distances > ~100 km.


2. Geometry Constructors

Build geometry objects from coordinates.

POINT

1
2
3
4
5
6
7
8
-- ST_Point(longitude, latitude)  ← note X=lng, Y=lat order
SELECT ST_Point(31.2357, 30.0444);                         -- Cairo
SELECT ST_PointFromText('POINT(31.2357 30.0444)', 4326);
SELECT Point(31.2357, 30.0444);                            -- MySQL shorthand (no SRID)

-- Store a point
INSERT INTO locations (name, position)
VALUES ('Cairo Center', ST_PointFromText('POINT(31.2357 30.0444)', 4326));

LINESTRING

1
2
3
4
5
6
7
8
-- Road from point A to point B to point C
SELECT ST_LineStringFromText('LINESTRING(31.2 30.0, 31.3 30.1, 31.4 30.2)', 4326);

SELECT LineString(
  Point(31.2, 30.0),
  Point(31.3, 30.1),
  Point(31.4, 30.2)
);

POLYGON

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- Closing ring: first point = last point
SELECT ST_PolygonFromText(
  'POLYGON((31.0 29.9, 31.5 29.9, 31.5 30.5, 31.0 30.5, 31.0 29.9))',
  4326
);

-- Polygon with a hole (donut shape)
SELECT ST_PolygonFromText(
  'POLYGON(
    (31.0 29.9, 31.5 29.9, 31.5 30.5, 31.0 30.5, 31.0 29.9),
    (31.1 30.0, 31.2 30.0, 31.2 30.1, 31.1 30.1, 31.1 30.0)
  )',
  4326
);

MULTIPOINT / MULTILINESTRING / MULTIPOLYGON

1
2
3
SELECT ST_MultiPointFromText('MULTIPOINT(31.2 30.0, 31.5 30.3, 31.8 30.6)', 4326);
SELECT ST_MultiLineStringFromText('MULTILINESTRING((31.0 30.0, 31.1 30.1),(31.2 30.2, 31.3 30.3))', 4326);
SELECT ST_MultiPolygonFromText('MULTIPOLYGON(((31.0 30.0,31.1 30.0,31.1 30.1,31.0 30.0)),((31.2 30.2,31.3 30.2,31.3 30.3,31.2 30.2)))', 4326);

GEOMETRYCOLLECTION

1
2
3
4
SELECT ST_GeomCollFromText(
  'GEOMETRYCOLLECTION(POINT(31.2 30.0), LINESTRING(31.0 30.0, 31.5 30.5))',
  4326
);

Cross-Engine β€” Constructors

FunctionMySQLSpatiaLitePostGISSQL ServerOracle
ST_PointFromText()βœ…βœ…βœ…βš οΈ geometry::STPointFromText()⚠️ SDO_GEOMETRY(2001,...)
ST_LineStringFromText()βœ…βœ…βœ…βš οΈ geometry::STLineFromText()βœ…
ST_PolygonFromText()βœ…βœ…βœ…βš οΈ geometry::STPolyFromText()βœ…
ST_GeomFromGeoJSON()βœ… 8.0+βœ…βœ…βš οΈ geometry::Parse()βœ…
ST_Point(x,y)βœ…βœ…βœ…βš οΈ geometry::Point(x,y,srid)❌

3. Input / Output (WKT & WKB)

Convert geometries to/from standard text and binary formats.

WKT β€” Well-Known Text

1
2
3
4
5
6
7
8
9
-- Geometry β†’ WKT string
SELECT ST_AsText(position)    FROM locations;     -- 'POINT(31.2357 30.0444)'
SELECT ST_AsWKT(position)     FROM locations;     -- synonym

-- WKT string β†’ Geometry
SELECT ST_GeomFromText('POINT(31.2357 30.0444)', 4326);
SELECT ST_PointFromText('POINT(31.2357 30.0444)', 4326);
SELECT ST_LineFromText('LINESTRING(0 0, 1 1, 2 2)', 4326);
SELECT ST_PolyFromText('POLYGON((0 0,1 0,1 1,0 1,0 0))', 4326);

WKB β€” Well-Known Binary

1
2
3
4
5
6
-- Geometry β†’ WKB (binary β€” used for storage/transfer)
SELECT ST_AsBinary(position)  FROM locations;
SELECT ST_AsWKB(position)     FROM locations;     -- synonym

-- WKB β†’ Geometry
SELECT ST_GeomFromWKB(wkb_column, 4326);

Bounding Box (MBR / Envelope)

1
2
3
4
-- Minimum Bounding Rectangle
SELECT ST_Envelope(geom)      FROM regions;
SELECT ST_AsText(ST_Envelope(geom)) FROM regions;
-- β†’ POLYGON((minX minY, maxX minY, maxX maxY, minX maxY, minX minY))

Cross-Engine β€” Input / Output

FunctionMySQLSpatiaLitePostGISSQL ServerOracle
ST_AsText()βœ…βœ…βœ…βš οΈ .STAsText() methodβœ…
ST_AsBinary()βœ…βœ…βœ…βš οΈ .STAsBinary()βœ…
ST_GeomFromText()βœ…βœ…βœ…βš οΈ geometry::STGeomFromText()βœ…
ST_GeomFromWKB()βœ…βœ…βœ…βš οΈ geometry::STGeomFromWKB()βœ…
ST_AsGeoJSON()βœ… 8.0+βœ…βœ…βš οΈ manual or FOR JSONβœ… 12c+
ST_Envelope()βœ…βœ…βœ…βš οΈ .STEnvelope()⚠️ SDO_GEOM.SDO_MBR()

4. Geometry Properties

Inspect and decompose geometry objects.

Type & Validity

1
2
3
4
5
6
7
SELECT ST_GeometryType(geom)  FROM locations;   -- 'ST_Point', 'ST_Polygon' …
SELECT ST_IsValid(geom)       FROM regions;     -- 1 = valid, 0 = invalid
SELECT ST_IsEmpty(geom)       FROM regions;     -- 1 if empty geometry
SELECT ST_IsSimple(geom)      FROM routes;      -- 1 if no self-intersections
SELECT ST_IsClosed(geom)      FROM routes;      -- 1 if LineString start = end
SELECT ST_Dimension(geom)     FROM shapes;      -- 0=point, 1=line, 2=polygon
SELECT ST_SRID(position)      FROM locations;   -- spatial reference system ID

Points & Coordinates

1
2
3
4
5
6
7
8
9
10
SELECT ST_X(position)         FROM locations;   -- longitude (X)
SELECT ST_Y(position)         FROM locations;   -- latitude  (Y)
SELECT ST_Latitude(position)  FROM locations;   -- MySQL 8.0+ β€” latitude
SELECT ST_Longitude(position) FROM locations;   -- MySQL 8.0+ β€” longitude

-- Extract start/end of a linestring
SELECT ST_StartPoint(route)   FROM routes;
SELECT ST_EndPoint(route)     FROM routes;
SELECT ST_NumPoints(route)    FROM routes;      -- total points in linestring
SELECT ST_PointN(route, 2)    FROM routes;      -- 2nd point (1-indexed)

Polygon Rings

1
2
3
SELECT ST_ExteriorRing(boundary)     FROM regions;    -- outer ring as LINESTRING
SELECT ST_NumInteriorRings(boundary) FROM regions;    -- count of holes
SELECT ST_InteriorRingN(boundary, 1) FROM regions;    -- 1st hole as LINESTRING

Collections

1
2
SELECT ST_NumGeometries(collection)  FROM multi_shapes;    -- count of sub-geometries
SELECT ST_GeometryN(collection, 1)   FROM multi_shapes;    -- extract 1st geometry

Cross-Engine β€” Properties

FunctionMySQLSpatiaLitePostGISSQL ServerOracle
ST_GeometryType()βœ…βœ…βœ…βš οΈ .STGeometryType()⚠️ .Get_GType()
ST_X() / ST_Y()βœ…βœ…βœ…βš οΈ .STX / .STY properties⚠️ SDO_UTIL.GETVERTICES()
ST_IsValid()βœ…βœ…βœ…βš οΈ .STIsValid()⚠️ SDO_GEOM.VALIDATE_GEOMETRY()
ST_StartPoint()βœ…βœ…βœ…βš οΈ .STStartPoint()βœ…
ST_NumPoints()βœ…βœ…βœ…βš οΈ .STNumPoints()βœ…
ST_ExteriorRing()βœ…βœ…βœ…βš οΈ .STExteriorRing()βœ…

5. Measurement Functions

Calculate distances, areas, and lengths.

Distance

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
-- Cartesian (flat) distance β€” fast, less accurate over long distances
SELECT ST_Distance(
  ST_PointFromText('POINT(31.2357 30.0444)', 4326),   -- Cairo
  ST_PointFromText('POINT(29.9553 31.1342)', 4326)    -- Giza
) AS dist_degrees;

-- Spherical distance β€” accurate real-world distance in meters
SELECT ST_Distance_Sphere(
  ST_PointFromText('POINT(31.2357 30.0444)', 4326),
  ST_PointFromText('POINT(29.9553 31.1342)', 4326)
) AS dist_meters;

-- PostGIS β€” use GEOGRAPHY type for accurate sphere distance
SELECT ST_Distance(
  'SRID=4326;POINT(31.2357 30.0444)'::geography,
  'SRID=4326;POINT(29.9553 31.1342)'::geography
) AS dist_meters;

Area

1
2
3
4
5
6
-- ST_Area returns area in the unit of the SRID
-- SRID 4326 β†’ square degrees (not useful); use projected SRID (e.g. 3857) for mΒ²
SELECT ST_Area(boundary)         FROM regions;

-- PostGIS with geography for mΒ²
SELECT ST_Area(boundary::geography) / 1000000 AS area_km2  FROM regions;

Length / Perimeter

1
2
3
4
5
SELECT ST_Length(route)          FROM routes;    -- length of linestring
SELECT ST_Perimeter(boundary)    FROM regions;   -- perimeter of polygon

-- PostGIS β€” accurate length in meters using geography
SELECT ST_Length(route::geography) AS length_m   FROM routes;

Hausdorff & FrΓ©chet Distance (shape similarity)

1
2
3
-- How similar are two line shapes? (lower = more similar)
SELECT ST_HausdorffDistance(route_a, route_b)  FROM route_pairs;
SELECT ST_FrechetDistance(route_a, route_b)    FROM route_pairs;   -- MySQL 8.0.37+

Cross-Engine β€” Measurement

FunctionMySQLSpatiaLitePostGISSQL ServerOracle
ST_Distance() (flat)βœ…βœ…βœ…βš οΈ .STDistance()⚠️ SDO_GEOM.SDO_DISTANCE()
ST_Distance_Sphere()βœ…βœ… SphereDistance()⚠️ Use geography type⚠️ Use geography type⚠️ SDO_GEOM.SDO_DISTANCE() with tolerance
ST_Area()βœ…βœ…βœ…βš οΈ .STArea()⚠️ SDO_GEOM.SDO_AREA()
ST_Length()βœ…βœ…βœ…βš οΈ .STLength()⚠️ SDO_GEOM.SDO_LENGTH()
ST_Perimeter()βœ…βœ…βœ…βŒ use .STLength() on ringβœ…
ST_HausdorffDistance()βœ…βœ…βœ…βŒβŒ

6. Spatial Relationship Functions

Test topological and geometric relationships between two geometries.

DE-9IM Predicates (Standard)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
-- Contains: does A completely contain B?
SELECT ST_Contains(region, point)    FROM areas, locations;     -- 1 or 0

-- Within: is A completely inside B?
SELECT ST_Within(point, region)      FROM locations, areas;     -- inverse of Contains

-- Intersects: do A and B share any space?
SELECT ST_Intersects(geom_a, geom_b) FROM shapes;

-- Overlaps: do A and B partially overlap (same dimension)?
SELECT ST_Overlaps(poly_a, poly_b)   FROM polygons;

-- Touches: do A and B share only a boundary point?
SELECT ST_Touches(geom_a, geom_b)    FROM shapes;

-- Crosses: do A and B cross each other (different dimensions)?
SELECT ST_Crosses(linestring, poly)  FROM routes, areas;

-- Disjoint: do A and B share NO space at all?
SELECT ST_Disjoint(geom_a, geom_b)   FROM shapes;

-- Equals: are A and B geometrically identical?
SELECT ST_Equals(geom_a, geom_b)     FROM shapes;

MBR (Minimum Bounding Rectangle) Predicates β€” Faster but less precise

1
2
3
4
5
6
7
8
9
10
-- Use MBR checks for large dataset filtering (index-friendly)
SELECT ST_MBRContains(mbr_a, mbr_b)  FROM shapes;
SELECT ST_MBRWithin(mbr_a, mbr_b)    FROM shapes;
SELECT ST_MBRIntersects(mbr_a, mbr_b)FROM shapes;
SELECT ST_MBROverlaps(mbr_a, mbr_b)  FROM shapes;
SELECT ST_MBREquals(mbr_a, mbr_b)    FROM shapes;
SELECT ST_MBRDisjoint(mbr_a, mbr_b)  FROM shapes;
SELECT ST_MBRTouches(mbr_a, mbr_b)   FROM shapes;
SELECT ST_MBRCoveredBy(mbr_a, mbr_b) FROM shapes;
SELECT ST_MBRCovers(mbr_a, mbr_b)    FROM shapes;

Practical: Find all points inside a polygon

1
2
3
4
5
-- Find all stores inside a city boundary
SELECT s.name, ST_AsText(s.position)
FROM stores s
JOIN cities c ON c.name = 'Cairo'
WHERE ST_Contains(c.boundary, s.position);

Practical: Find points within a radius

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-- All restaurants within 2 km of a given point (flat distance)
SELECT name
FROM restaurants
WHERE ST_Distance_Sphere(
  position,
  ST_PointFromText('POINT(31.2357 30.0444)', 4326)
) <= 2000;

-- Better: use bounding box first (uses spatial index), then refine
SELECT name
FROM restaurants
WHERE MBRContains(
  ST_Buffer(ST_PointFromText('POINT(31.2357 30.0444)', 4326), 0.02),
  position
)
AND ST_Distance_Sphere(position, ST_PointFromText('POINT(31.2357 30.0444)', 4326)) <= 2000;

Cross-Engine β€” Relationships

FunctionMySQLSpatiaLitePostGISSQL ServerOracle
ST_Contains()βœ…βœ…βœ…βš οΈ .STContains()⚠️ SDO_CONTAINS()
ST_Within()βœ…βœ…βœ…βš οΈ .STWithin()⚠️ SDO_WITHIN_DISTANCE()
ST_Intersects()βœ…βœ…βœ…βš οΈ .STIntersects()⚠️ SDO_ANYINTERACT()
ST_Overlaps()βœ…βœ…βœ…βš οΈ .STOverlaps()⚠️ SDO_OVERLAPS()
ST_Touches()βœ…βœ…βœ…βš οΈ .STTouches()⚠️ SDO_TOUCH()
ST_Crosses()βœ…βœ…βœ…βš οΈ .STCrosses()⚠️ SDO_CROSSES()
ST_Disjoint()βœ…βœ…βœ…βš οΈ .STDisjoint()⚠️ SDO_DISJOINT()
ST_Equals()βœ…βœ…βœ…βš οΈ .STEquals()⚠️ SDO_EQUAL()
MBR predicatesβœ…βœ…βš οΈ && operator❌❌

7. Spatial Set Operations

Compute new geometries from the intersection, union, or difference of two geometries.

Intersection β€” shared area

1
2
SELECT ST_AsText(ST_Intersection(poly_a, poly_b)) FROM region_pairs;
-- Returns the geometry that both inputs share

Union β€” combined area

1
2
SELECT ST_AsText(ST_Union(poly_a, poly_b)) FROM region_pairs;
-- Returns one geometry covering both inputs (no duplicates)

Difference β€” subtract B from A

1
2
SELECT ST_AsText(ST_Difference(city_boundary, restricted_zone)) FROM areas;
-- Returns the part of A that is NOT in B

Symmetric Difference β€” XOR of geometries

1
2
SELECT ST_AsText(ST_SymDifference(poly_a, poly_b)) FROM region_pairs;
-- Returns area in A or B but NOT in both (like XOR)

Buffer β€” expand geometry by distance

1
2
3
4
5
6
7
8
9
10
11
12
13
-- Buffer a point (creates a circle in flat/Cartesian space)
SELECT ST_AsText(ST_Buffer(ST_PointFromText('POINT(31.2357 30.0444)', 4326), 0.01));

-- Buffer a road corridor
SELECT ST_AsText(ST_Buffer(route, 0.001)) FROM routes;

-- PostGIS β€” buffer with projected SRID (meters)
SELECT ST_AsText(
  ST_Buffer(
    ST_Transform(ST_PointFromText('POINT(31.2357 30.0444)', 4326), 32636),
    500   -- 500 meters in UTM Zone 36N
  )
);

Convex Hull β€” smallest convex shape enclosing geometry

1
2
SELECT ST_AsText(ST_ConvexHull(geom)) FROM point_clouds;
-- Returns the tightest convex polygon around all input points

Simplify β€” reduce vertex count (generalize shape)

1
2
3
-- Reduce complexity of a detailed polygon (Douglas-Peucker algorithm)
SELECT ST_AsText(ST_Simplify(boundary, 0.001)) FROM regions;
-- Tolerance: larger = more simplification, less detail

Centroid β€” geometric center point

1
2
3
SELECT ST_AsText(ST_Centroid(boundary)) FROM regions;
-- Note: centroid may fall OUTSIDE a concave polygon
-- Use ST_PointOnSurface() for a guaranteed-inside point (PostGIS)

Cross-Engine β€” Set Operations

FunctionMySQLSpatiaLitePostGISSQL ServerOracle
ST_Intersection()βœ…βœ…βœ…βš οΈ .STIntersection()⚠️ SDO_GEOM.SDO_INTERSECTION()
ST_Union()βœ…βœ…βœ…βš οΈ .STUnion()⚠️ SDO_GEOM.SDO_UNION()
ST_Difference()βœ…βœ…βœ…βš οΈ .STDifference()⚠️ SDO_GEOM.SDO_DIFFERENCE()
ST_SymDifference()βœ…βœ…βœ…βš οΈ .STSymDifference()⚠️ SDO_GEOM.SDO_XOR()
ST_Buffer()βœ…βœ…βœ…βš οΈ .STBuffer()⚠️ SDO_GEOM.SDO_BUFFER()
ST_ConvexHull()βœ…βœ…βœ…βš οΈ .STConvexHull()⚠️ SDO_GEOM.SDO_CONVEXHULL()
ST_Simplify()βœ…βœ…βœ…βŒβš οΈ SDO_UTIL.SIMPLIFY()
ST_Centroid()βœ…βœ…βœ…βŒ use .STCentroid() MSSQL extensionβœ…

8. Coordinate & Projection Functions

Transform coordinates between spatial reference systems.

Transform (Reproject)

1
2
3
4
5
6
7
8
9
10
-- PostGIS β€” transform from WGS84 (4326) to UTM (32636)
SELECT ST_AsText(
  ST_Transform(
    ST_PointFromText('POINT(31.2357 30.0444)', 4326),
    32636
  )
);

-- MySQL 8.0+
SELECT ST_Transform(position, 32636) FROM locations;

Swap X/Y coordinates

1
2
-- Fix geometries where lat/lng are accidentally reversed
SELECT ST_SwapXY(position) FROM locations;

Line Interpolation (point along a line)

1
2
3
4
5
6
7
8
-- Point 50% along a route
SELECT ST_AsText(ST_LineInterpolatePoint(route, 0.5)) FROM routes;

-- Multiple points at regular intervals along a route
SELECT ST_AsText(ST_LineInterpolatePoints(route, 0.25)) FROM routes;  -- every 25%

-- Point at specific distance from start
SELECT ST_AsText(ST_PointAtDistance(route, 1000)) FROM routes;  -- 1000 map units from start

Geohash (encode coordinates as string)

1
2
3
4
5
6
7
8
9
10
11
-- Encode point as geohash (shorter = lower precision)
SELECT ST_GeoHash(position, 10) FROM locations;       -- 10-char precision
SELECT ST_GeoHash(ST_PointFromText('POINT(31.2357 30.0444)',4326), 8);
-- β†’ 'svhwsq3w'

-- Decode geohash back to point
SELECT ST_AsText(ST_PointFromGeoHash('svhwsq3w', 0));

-- Extract lat/lng from geohash
SELECT ST_LatFromGeoHash('svhwsq3w');
SELECT ST_LongFromGeoHash('svhwsq3w');

Cross-Engine β€” Projections

FunctionMySQLSpatiaLitePostGISSQL ServerOracle
ST_Transform()βœ… 8.0+βœ…βœ…βš οΈ manual via CLR⚠️ SDO_CS.TRANSFORM()
ST_SwapXY()βœ…βœ…βœ…βŒβŒ
ST_LineInterpolatePoint()βœ…βœ…βœ…βŒβŒ
ST_GeoHash()βœ…βœ…βœ…βŒβŒ
ST_PointFromGeoHash()βœ…βœ…βš οΈ ST_GeomFromGeoHash()❌❌

9. GeoJSON & Geohash

Exchange spatial data in standard web-friendly formats.

GeoJSON Input / Output

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
-- Geometry β†’ GeoJSON string
SELECT ST_AsGeoJSON(position) FROM locations;
-- β†’ '{"type":"Point","coordinates":[31.2357,30.0444]}'

SELECT ST_AsGeoJSON(boundary) FROM regions;
-- β†’ '{"type":"Polygon","coordinates":[[[31.0,29.9],...]]}'

-- GeoJSON string β†’ Geometry
SELECT ST_GeomFromGeoJSON(
  '{"type":"Point","coordinates":[31.2357,30.0444]}'
);

-- Insert from GeoJSON
INSERT INTO locations (name, position)
VALUES (
  'Cairo',
  ST_GeomFromGeoJSON('{"type":"Point","coordinates":[31.2357,30.0444]}')
);

-- Full feature with metadata (MySQL 8.0+)
SELECT ST_AsGeoJSON(
  ST_GeomFromText('POINT(31.2357 30.0444)', 4326),
  4,      -- max decimal digits
  2       -- options flag: include SRID
);

PostGIS β€” Build GeoJSON FeatureCollection

1
2
3
4
5
6
7
8
9
10
11
SELECT json_build_object(
  'type', 'FeatureCollection',
  'features', json_agg(
    json_build_object(
      'type',       'Feature',
      'geometry',   ST_AsGeoJSON(position)::json,
      'properties', json_build_object('name', name, 'category', category)
    )
  )
)
FROM locations;

Cross-Engine β€” GeoJSON

FunctionMySQLSpatiaLitePostGISSQL ServerOracle
ST_AsGeoJSON()βœ… 8.0+βœ…βœ…βš οΈ manual JSON buildβœ… 12c+
ST_GeomFromGeoJSON()βœ… 8.0+βœ…βœ… ST_GeomFromGeoJSON()⚠️ geometry::Parse()βœ… 12c+

10. Spatial Indexes

Critical for performance β€” without a spatial index, every spatial query does a full table scan.

MySQL β€” SPATIAL INDEX

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-- Require NOT NULL + no SRID on older MySQL, or SRID on 8.0+
CREATE TABLE locations (
  id        INT   NOT NULL AUTO_INCREMENT PRIMARY KEY,
  name      VARCHAR(100) NOT NULL,
  position  POINT NOT NULL SRID 4326,
  SPATIAL INDEX idx_position (position)   -- inline at creation
);

-- Add spatial index later
ALTER TABLE locations ADD SPATIAL INDEX idx_position (position);
CREATE SPATIAL INDEX idx_position ON locations(position);

-- Drop spatial index
DROP INDEX idx_position ON locations;
ALTER TABLE locations DROP INDEX idx_position;

PostgreSQL (PostGIS) β€” GiST / SP-GiST / BRIN

1
2
3
4
5
6
7
8
9
10
11
-- GiST index β€” most common, supports all operators
CREATE INDEX idx_position ON locations USING GIST (position);

-- SP-GiST β€” better for non-overlapping data (points, quadtrees)
CREATE INDEX idx_position ON locations USING SPGIST (position);

-- BRIN β€” very small, good for large append-only tables
CREATE INDEX idx_position ON locations USING BRIN (position);

-- Partial spatial index (only index active records)
CREATE INDEX idx_active_pos ON locations USING GIST (position) WHERE active = TRUE;

SQL Server β€” Spatial Index

1
2
3
4
CREATE SPATIAL INDEX idx_position
ON locations(position)
USING GEOMETRY_AUTO_GRID
WITH (BOUNDING_BOX = (29.0, 22.0, 37.0, 32.0));   -- xmin,ymin,xmax,ymax for Egypt

Oracle β€” Spatial Index (R-Tree)

1
2
3
4
5
6
7
8
9
10
11
12
CREATE INDEX idx_position ON locations(position)
INDEXTYPE IS MDSYS.SPATIAL_INDEX_V2;

-- Required: insert metadata first
INSERT INTO user_sdo_geom_metadata VALUES (
  'LOCATIONS', 'POSITION',
  MDSYS.SDO_DIM_ARRAY(
    MDSYS.SDO_DIM_ELEMENT('X', 29.0, 37.0, 0.005),
    MDSYS.SDO_DIM_ELEMENT('Y', 22.0, 32.0, 0.005)
  ),
  4326
);

Cross-Engine β€” Spatial Indexes

FeatureMySQLSpatiaLitePostGISSQL ServerOracle
Spatial index typeR-TreeR-TreeGiST / SP-GiST / BRINGrid-basedR-Tree
SyntaxSPATIAL INDEXCREATE SPATIAL INDEXUSING GISTCREATE SPATIAL INDEXINDEXTYPE IS MDSYS.SPATIAL_INDEX
Column must be NOT NULLβœ… requiredβœ…βŒ optional❌❌
Partial / filtered indexβŒβŒβœ…βœ…βŒ
Automatic SRID enforcementβœ… 8.0+βŒβœ…βœ…βœ…

⚠️ Performance tip: Always use a spatial index + a bounding box pre-filter (MBR/envelope check) before applying precise spatial functions like ST_Contains or ST_Distance. This dramatically reduces the number of rows that need exact computation.


11. Window & Aggregate Spatial Functions

Aggregate geometries across groups or windows.

ST_Collect β€” aggregate into a collection (no merge)

1
2
3
4
-- PostGIS: collect all points per city into a MULTIPOINT
SELECT city, ST_Collect(position) AS all_positions
FROM locations
GROUP BY city;

ST_Union β€” aggregate and merge (dissolve boundaries)

1
2
3
4
-- PostGIS: merge all district polygons into a single city polygon
SELECT city_id, ST_Union(district_boundary) AS city_boundary
FROM districts
GROUP BY city_id;

ST_Extent β€” bounding box of a group

1
2
3
4
5
-- PostGIS: bounding box of all stores in a region
SELECT region, ST_Extent(position) AS bbox
FROM stores
GROUP BY region;
-- β†’ BOX(minX minY, maxX maxY)

ST_ConvexHull on group

1
2
3
4
-- Convex hull enclosing all delivery points per route
SELECT route_id, ST_AsText(ST_ConvexHull(ST_Collect(position))) AS hull
FROM deliveries
GROUP BY route_id;

Nearest Neighbor (PostGIS KNN)

1
2
3
4
5
6
-- 5 nearest locations to a target point using <-> distance operator
SELECT name, ST_Distance(position, target.pt) AS dist
FROM locations,
     (SELECT ST_PointFromText('POINT(31.2357 30.0444)', 4326) AS pt) target
ORDER BY position <-> target.pt
LIMIT 5;

Cross-Engine β€” Aggregate Spatial

FunctionMySQLSpatiaLitePostGISSQL ServerOracle
ST_Collect() aggregateβœ…βœ…βœ…βŒβŒ
ST_Union() aggregateβŒβœ…βœ…βŒβš οΈ SDO_AGGR_UNION()
ST_Extent() aggregateβŒβœ…βœ…βŒβš οΈ SDO_AGGR_MBR()
KNN <-> operatorβŒβŒβœ…βŒβŒ

12. Cross-Engine Master Table

CategoryMySQLSpatiaLitePostGISSQL ServerOracle Spatial
Spatial typesβœ…βœ…βœ… + GEOGRAPHYβœ… + GEOGRAPHYβœ… SDO_GEOMETRY
WKT / WKB I/Oβœ…βœ…βœ…βœ…βœ…
GeoJSON I/Oβœ… 8.0+βœ…βœ…βš οΈβœ… 12c+
Geohashβœ…βœ…βœ…βŒβŒ
Measurement (flat)βœ…βœ…βœ…βœ…βœ…
Measurement (spherical)βœ… ST_Distance_Sphereβœ…βœ… GEOGRAPHY typeβœ… GEOGRAPHY typeβœ… with tolerance
Relationship predicatesβœ… full DE-9IMβœ…βœ…βœ…βœ…
MBR predicatesβœ…βœ…βš οΈ && operator❌❌
Set operations (buffer, union…)βœ…βœ…βœ…βœ…βœ…
Coordinate transformβœ… 8.0+βœ…βœ…βš οΈ limitedβœ…
Spatial indexesβœ… R-Treeβœ… R-Treeβœ… GiST/BRINβœ… Gridβœ… R-Tree
Aggregate spatial functions⚠️ ST_Collect onlyβœ…βœ… full❌⚠️ limited
KNN nearest-neighbor indexβŒβŒβœ…βœ…βœ…
Deferrable constraint / FK on geomβŒβŒβœ…βŒβœ…

13. Common Patterns & Examples


Pattern 1 β€” Store and query GPS locations

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
-- Table setup
CREATE TABLE stores (
  id       INT   NOT NULL AUTO_INCREMENT PRIMARY KEY,
  name     VARCHAR(100) NOT NULL,
  position POINT NOT NULL SRID 4326,
  SPATIAL INDEX idx_pos (position)
);

-- Insert using lat/lng
INSERT INTO stores (name, position) VALUES
  ('Cairo Downtown', ST_PointFromText('POINT(31.2357 30.0444)', 4326)),
  ('Giza',           ST_PointFromText('POINT(31.2089 30.0131)', 4326)),
  ('Alexandria',     ST_PointFromText('POINT(29.9187 31.2001)', 4326));

-- Retrieve lat/lng
SELECT name,
  ST_Y(position) AS latitude,
  ST_X(position) AS longitude
FROM stores;

Pattern 2 β€” Find all points within N km of a location

1
2
3
4
5
6
7
8
9
10
-- All stores within 5 km of a given point
SET @center = ST_PointFromText('POINT(31.2357 30.0444)', 4326);
SET @radius_m = 5000;

SELECT
  name,
  ROUND(ST_Distance_Sphere(position, @center)) AS distance_m
FROM stores
WHERE ST_Distance_Sphere(position, @center) <= @radius_m
ORDER BY distance_m;

Pattern 3 β€” Find points inside a polygon (geofencing)

1
2
3
4
5
6
7
8
9
10
-- Define a delivery zone polygon
SET @zone = ST_GeomFromText(
  'POLYGON((31.1 29.9, 31.4 29.9, 31.4 30.2, 31.1 30.2, 31.1 29.9))',
  4326
);

-- Find all orders inside the delivery zone
SELECT o.id, o.customer_name
FROM orders o
WHERE ST_Contains(@zone, o.delivery_point);

Pattern 4 β€” Calculate area of a region (PostGIS)

1
2
3
4
5
6
-- Area in kmΒ² using geography type for accuracy
SELECT
  name,
  ROUND(ST_Area(boundary::geography) / 1000000, 2) AS area_km2
FROM regions
ORDER BY area_km2 DESC;

Pattern 5 β€” Route length in kilometers

1
2
3
4
5
6
7
-- MySQL (flat approximation)
SELECT name, ST_Length(route) AS length_degrees FROM routes;

-- PostGIS (accurate using geography)
SELECT name,
  ROUND(ST_Length(route::geography) / 1000, 2) AS length_km
FROM routes;

Pattern 6 β€” Nearest neighbor search (PostGIS KNN)

1
2
3
4
5
6
-- 10 nearest hospitals to a patient location
SELECT h.name, ST_Distance(h.position::geography, p.location::geography) AS dist_m
FROM hospitals h,
     (SELECT ST_SetSRID(ST_Point(31.2357, 30.0444), 4326)::geography AS location) p
ORDER BY h.position::geography <-> p.location
LIMIT 10;

Pattern 7 β€” Dissolve district boundaries into city boundary

1
2
3
4
5
6
7
-- PostGIS aggregate union
SELECT
  city_id,
  ST_AsText(ST_Union(boundary)) AS city_boundary,
  ROUND(ST_Area(ST_Union(boundary)::geography)/1000000, 2) AS total_km2
FROM districts
GROUP BY city_id;

Pattern 8 β€” Generate a buffer zone (PostGIS with reprojection)

1
2
3
4
5
6
7
8
9
10
-- 500m buffer around a hospital in UTM (accurate meters)
SELECT
  name,
  ST_AsGeoJSON(
    ST_Transform(
      ST_Buffer(ST_Transform(position, 32636), 500),   -- buffer 500m in UTM
      4326                                              -- back to WGS84
    )
  ) AS buffer_geojson
FROM hospitals;

Pattern 9 β€” Track which admin region a GPS point falls in

1
2
3
4
5
6
7
8
-- Reverse geocode: find which city a point belongs to
SELECT c.name AS city, c.country
FROM cities c
WHERE ST_Contains(
  c.boundary,
  ST_PointFromText('POINT(31.2357 30.0444)', 4326)
)
LIMIT 1;

Pattern 10 β€” Spatial join: count points per polygon

1
2
3
4
5
6
7
8
-- Count stores per district
SELECT
  d.name AS district,
  COUNT(s.id) AS store_count
FROM districts d
LEFT JOIN stores s ON ST_Contains(d.boundary, s.position)
GROUP BY d.id, d.name
ORDER BY store_count DESC;

Quick Reference Card

TaskFunction(s)
Create point from lng/latST_PointFromText('POINT(lng lat)', 4326)
Get coordinatesST_X(pt) β†’ lng, ST_Y(pt) β†’ lat
Distance (spherical)ST_Distance_Sphere(pt_a, pt_b) β†’ meters
Point inside polygonST_Contains(polygon, point)
Points in radiusST_Distance_Sphere(pt, center) <= radius
Overlap checkST_Intersects(geom_a, geom_b)
Merge polygonsST_Union(poly_a, poly_b)
Expand shapeST_Buffer(geom, distance)
Shape centerST_Centroid(geom)
Bounding boxST_Envelope(geom)
Convert to GeoJSONST_AsGeoJSON(geom)
Convert to WKTST_AsText(geom)
ReprojectST_Transform(geom, target_srid)
Validate geometryST_IsValid(geom)

Syntax primarily shown for MySQL 8.x. PostGIS (PostgreSQL), SQL Server, Oracle, and SpatiaLite (SQLite) alternatives noted throughout.

This post is licensed under CC BY 4.0 by the author.