Chapter 14 · Full-Text Search, R-Tree, Virtual Tables, and Extensions

R-Tree and Geospatial / Multidimensional Range Search

Use SQLite R*Tree to index two-dimensional bounding boxes, query overlap efficiently, reason about floating-point bounds, and distinguish spatial candidate search from complete GIS geometry semantics.

Beginner105–125 minutes2D R*Tree bounding-box labSQLite 3.53.4 baselineRTree requiredLast reviewed: August 2026

Learning outcomes

A B-tree orders keys along one lexicographic dimension. Many spatial/range problems instead ask “which rectangles overlap this rectangle?” R*Tree is SQLite's specialized virtual-table module for that kind of multidimensional candidate search. It is not a complete GIS system, which is precisely why the lesson keeps geometry simple and observable.

01

Explain rectangle intersection as a multidimensional search problem.

02

Create an R*Tree virtual table with an id and min/max bounds for each dimension.

03

Write overlap, containment, and point-style bounding-box queries.

04

Explain why a conventional composite B-tree is not equivalent to an R*Tree.

05

Account for the default 32-bit floating-point coordinate representation and documented outward rounding.

06

Build a local plant-map range-search lab without requiring an external GIS extension.

Start with rectangles, not maps

Imagine a local coordinate system measured in meters inside North Plant. Each asset occupies a rectangular footprint: min_x to max_x and min_y to max_y. A maintenance planner draws a viewport or work zone and asks which asset rectangles overlap it. That is the native R*Tree problem.

text · overlap rule
Two rectangles overlap when all four conditions hold:asset.max_x >= query.min_xasset.min_x <= query.max_xasset.max_y >= query.min_yasset.min_y <= query.max_y

Confirm the R*Tree module, then create the index

sql · R*Tree capability and schema
SELECT nameFROM pragma_module_listWHERE name IN ('rtree','rtree_i32');DROP TABLE IF EXISTS device_bounds;CREATE VIRTUAL TABLE device_bounds USING rtree(  device_id,  min_x, max_x,  min_y, max_y);

The first column is the row identifier. Each dimension is represented by a lower and upper bound. R*Tree supports up to five dimensions, but two dimensions are enough to build the mental model.

Populate FieldNotes asset footprints

sql · plant-coordinate rectangles
INSERT INTO device_bounds VALUES(1, 10.0, 13.5, 20.0, 23.0),  -- PUMP-007(2, 18.0, 22.0, 19.0, 22.0),  -- FAN-014(3, 12.0, 12.4, 28.0, 28.4),  -- SENS-003(4, 30.0, 35.0, 10.0, 15.0),  -- storage skid(5, 21.5, 24.0, 22.0, 26.0);  -- valve stationSELECT * FROM device_bounds ORDER BY device_id;

In a real schema, an ordinary asset/device table owns names, status, foreign keys, and business attributes. The R*Tree owns only the specialized multidimensional search bounds plus an id that lets the result join back to relational data.

Overlap queries are symmetric range constraints

sql · viewport overlap
-- Query rectangle: x=11..21, y=18..24SELECT device_id,min_x,max_x,min_y,max_yFROM device_boundsWHERE max_x >= 11.0  AND min_x <= 21.0  AND max_y >= 18.0  AND min_y <= 24.0ORDER BY device_id;

The module can use several bound constraints together. You do not need to constrain every coordinate for an R*Tree query to be useful, although tighter boxes and more applicable constraints usually reduce candidates.

Join candidate ids back to relational facts

sql · spatial candidates plus device data
SELECT d.device_code,       d.device_name,       d.status,       b.min_x,b.max_x,b.min_y,b.max_yFROM device_bounds AS bJOIN device AS d ON d.device_id=b.device_idWHERE b.max_x >= 11.0  AND b.min_x <= 21.0  AND b.max_y >= 18.0  AND b.min_y <= 24.0ORDER BY d.device_code;

This separation is powerful: R*Tree answers “which ids have bounding boxes that qualify?” while ordinary tables retain the richer relational contract. Do not try to turn the virtual table into the whole application schema.

Point and containment queries are variations of the same bounds

sql · point and contained-within forms
-- Which asset boxes contain the point (12.2, 21.0)?SELECT device_idFROM device_boundsWHERE min_x <= 12.2 AND max_x >= 12.2  AND min_y <= 21.0 AND max_y >= 21.0;-- Which asset boxes are completely inside x=9..25, y=18..30?SELECT device_idFROM device_boundsWHERE min_x >= 9.0 AND max_x <= 25.0  AND min_y >= 18.0 AND max_y <= 30.0ORDER BY device_id;

For exact containment near floating-point boundaries, apply the rounding guidance in the next section instead of assuming decimal text values are represented exactly.

Default R*Tree coordinates are 32-bit floating point

The default rtree module stores coordinates as single-precision floating-point values. When a coordinate cannot be represented exactly, SQLite rounds lower bounds slightly downward and upper bounds slightly upward. This outward rounding is desirable for overlap queries because it avoids false negatives at edges; it can admit a few extra candidates.

Contained-within caution

SQLite documents that contained-within queries near boundaries may need the query box expanded slightly—about 0.000012%—to account for outward rounding. For high-stakes exact geometry, use the R*Tree as a candidate index and apply an exact second-stage predicate in application logic or a trusted geometry extension.

Use rtree_i32 when the coordinate domain is truly integer

If your logical bounds are signed 32-bit integers, SQLite also exposes rtree_i32. It stores integer coordinates, though the R-tree algorithm still uses floating-point computations internally. Choosing integer coordinates can be natural for pixel boxes, grid cells, or fixed engineering units—but do not rescale real-world coordinates without documenting units and range limits.

sql · integer-coordinate variant
CREATE VIRTUAL TABLE temp.grid_bounds USING rtree_i32(  object_id,  min_col,max_col,  min_row,max_row);

Why a composite B-tree is not the same index

A normal index such as (min_x,max_x,min_y,max_y) is ordered lexicographically: first by min_x, then by the next column among rows sharing the earlier prefix. An overlap query constrains lower and upper bounds across multiple dimensions in both directions. R*Tree is designed around bounding regions rather than one leftmost B-tree ordering.

Design Strength Weakness for rectangle overlap
Composite B-tree Excellent for compatible equality/range/order patterns in its key order Cannot become a multidimensional R-tree merely by adding four coordinate columns.
R*Tree Designed for multidimensional range/bounding-box search Specialized schema and semantics; not a replacement for ordinary relational indexes.
Full GIS extension Can add exact geometry predicates, coordinate systems, projections, etc. Adds dependency/packaging/trust complexity beyond core R*Tree.

R*Tree gives candidates, not universal GIS semantics

Core R*Tree does not know that 51.5 means latitude, how to cross the antimeridian, what a polygon hole means, or which Earth projection is appropriate. It indexes numeric bounds. For exact lines/polygons/circles or geodetic distance, you need additional domain logic or a trusted spatial extension and must verify its coordinate-system semantics.

Lab: maintenance work-zone selection

sql · parameterized work-zone query
-- Work zone chosen by a planner:--   qminx=17, qmaxx=25, qminy=18, qmaxy=27WITH zone(qminx,qmaxx,qminy,qmaxy) AS (  VALUES(17.0,25.0,18.0,27.0))SELECT b.device_id,b.min_x,b.max_x,b.min_y,b.max_yFROM device_bounds AS b, zone AS zWHERE b.max_x >= z.qminx  AND b.min_x <= z.qmaxx  AND b.max_y >= z.qminy  AND b.min_y <= z.qmaxyORDER BY b.device_id;

Predict the ids before running it, then shrink and move the query rectangle. Use EXPLAIN QUERY PLAN to confirm the query is operating through the virtual table; avoid depending on the exact text of the plan because EQP formatting is not a stable application API.

Verification checkpoint

R*Tree checkpoint

Keep the scope to numeric bounds and candidate search.

  1. What question is R*Tree optimized to answer?
  2. Why keep device names/status in an ordinary table?
  3. What four inequalities express 2D rectangle overlap?
  4. How does default coordinate rounding affect overlap queries?
  5. Why is a four-column B-tree not equivalent to R*Tree?
  6. When would a second-stage exact geometry predicate be appropriate?
Review the answers

R*Tree efficiently narrows multidimensional bounding-box/range candidates. Ordinary tables retain relational facts/constraints. Overlap requires max_x>=qminx, min_x<=qmaxx, max_y>=qminy, and min_y<=qmaxy. Default float bounds are rounded outward, favoring no missed overlaps but allowing extra candidates. A B-tree has lexicographic key order rather than R-tree multidimensional organization. Use an exact second-stage test when bounding boxes are only an approximation of the true geometry or precision requirements demand it.

Production judgment and bridge

R*Tree shows why SQLite's module architecture is valuable: a specialized access method remains queryable through SQL without pretending it is an ordinary B-tree index. Lesson 5 closes the chapter by treating extensions as deployable executable dependencies rather than convenient files to load casually.

Authoritative references

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only assets compatible with the Ethereum/ERC-20 network. Do not send TRC-20/TRON assets.