# Temporal Types: Aggregation and Indexing

## Aggregation

The temporal aggregate functions generalize the traditional aggregate functions. Their semantics is that they compute the value of the function at every instant in the union of the temporal extents of the values to aggregate. In contrast, recall that all other functions manipulating temporal types compute the value of the function at every instant in the intersection of the temporal extents of the arguments.

The temporal aggregate functions are the following ones:

- For all temporal types, the function `tCount` generalize the traditional function `count`. The temporal count can be used to compute at each point in time the number of available objects (for example, number of cars in an area).

- For all temporal types, function `extent` returns a bounding box that encloses a set of temporal values. Depending on the base type, the result of this function can be a `tstzspan`, a `tbox` or an `stbox`.

- For the temporal Boolean type, the functions `tAnd` and `tOr` generalize the traditional functions `and` and `or`.

- For temporal numeric types, there are two types of temporal aggregate functions. The functions `tMin`, `tMax`, `tSum`, and `tAvg` generalize the traditional functions `min`, `max`, `sum`, and `avg`. Furthermore, the functions `wMin`, `wMax`, `wCount`, `wSum`, and `wAvg` are window (or cumulative) versions of the traditional functions that, given a time interval w, compute the value of the function at an instant t by considering the values during the interval [t-w, t]. All window aggregate functions are available for temporal integers, while for temporal floats only window minimum and maximum are meaningful.

- For the temporal text type, the functions `tMin` y `tMax` generalize the traditional functions `min` and `max`.

- Finally, for temporal point types, the function `tCentroid` generalizes the function `ST_Centroid` provided by PostGIS. For example, given set of objects that move together (that is, a convoy or a flock) the temporal centroid will produce a temporal point that represents at each instant the geometric center (or the center of mass) of all the moving objects.

In the examples that follow, we suppose the tables `Department` and `Trip` contain the two tuples introduced in ttype_examples.

- Temporal count

  `tCount(ttype) → {tintSeq,tintSeqSet}`

      SELECT tCount(NoEmps) FROM Department;
      -- {[1@2001-01-01, 2@2001-02-01, 1@2001-08-01, 1@2001-10-01)}

- Bounding box extent

  `extent(temp) → {tstzspan,tbox,stbox}`

      SELECT extent(noEmps) FROM Department;
      -- TBOX XT((4,12),[2001-01-01,2001-10-01])
      SELECT extent(Trip) FROM Trips;
      -- STBOX XT(((0,0),(3,3)),[2001-01-01 08:00:00+01, 2001-01-01 08:20:00+01))

- Temporal and, temporal or

  `tOr(tbool) → tbool`

  `tAnd(tbool) → tbool`

      SELECT tAnd(NoEmps #> 6) FROM Department;
      -- {[t@2001-01-01, f@2001-04-01, f@2001-10-01)}
      SELECT tOr(NoEmps #> 6) FROM Department;
      -- {[t@2001-01-01, f@2001-08-01, f@2001-10-01)}

- Temporal minimum, maximum, sum, and average

  `tMin(ttype) → ttype`

  `tMax(ttype) → ttype`

  `tSum(tnumber) → {tnumberSeq,tnumberSeqSet}`

  `tAvg(tnumber) → {tfloatSeq,tfloatSeqSet}`

      SELECT tMin(NoEmps) FROM Department;
      -- {[10@2001-01-01, 4@2001-02-01, 6@2001-06-01, 6@2001-10-01)}
      SELECT tMax(NoEmps) FROM Department;
      -- {[10@2001-01-01, 12@2001-04-01, 6@2001-08-01, 6@2001-10-01)}
      SELECT tSum(NoEmps) FROM Department;
      /* {[10@2001-01-01, 14@2001-02-01, 16@2001-04-01, 18@2001-06-01, 6@2001-08-01,
         6@2001-10-01)} */
      SELECT tAvg(NoEmps) FROM Department;
      /* {[10@2001-01-01, 10@2001-02-01), [7@2001-02-01, 7@2001-04-01),
         [8@2001-04-01, 8@2001-06-01), [9@2001-06-01, 9@2001-08-01),
         [6@2001-08-01, 6@2001-10-01) */

- Window count

  `wCount(tnumber,interval) → {tintSeq,tintSeqSet}`

      SELECT wCount(NoEmps, interval '2 days') FROM Department;
      /* {[1@2001-01-01, 2@2001-02-01, 3@2001-04-01, 2@2001-04-03, 3@2001-06-01, 2@2001-06-03,
         1@2001-08-03, 1@2001-10-03)} */

- Window minimum, maximum, sum, and average

  `wMin(tnumber,interval) → {tnumberSeq,tnumberSeqSet}`

  `wMax(tnumber,interval) → {tnumberDiscSeq,tnumberSeqSet}`

  `wSum(tint,interval) → {tintSeq,tintSeqSet}`

  `wAvg(tint,interval) → {tfloatSeq,tfloatSeqSet}`

      SELECT wMin(NoEmps, interval '2 days') FROM Department;
      -- {[10@2001-01-01, 4@2001-04-01, 6@2001-06-03, 6@2001-10-03)}
      SELECT wMax(NoEmps, interval '2 days') FROM Department;
      -- {[10@2001-01-01, 12@2001-04-01, 6@2001-08-03, 6@2001-10-03)}
      SELECT wSum(NoEmps, interval '2 days') FROM Department;
      /* {[10@2001-01-01, 14@2001-02-01, 26@2001-04-01, 16@2001-04-03, 22@2001-06-01,
         18@2001-06-03, 6@2001-08-03, 6@2001-10-03)} */
      SELECT round(wAvg(NoEmps, interval '2 days'), 3) FROM Department;
      /*  Interp=Step;{[10@2001-01-01, 7@2001-02-01, 8.667@2001-04-01, 8@2001-04-03,
         7.333@2001-06-01, 9@2001-06-03, 6@2001-08-03, 6@2001-10-03)} */

- Temporal centroid

  `tCentroid(tgeompoint) → tgeompoint`

      SELECT tCentroid(Trip) FROM Trips;
      /* {[POINT(0 0)@2001-01-01 08:00:00+00, POINT(1 0)@2001-01-01 08:05:00+00),
         [POINT(0.5 0)@2001-01-01 08:05:00+00, POINT(1.5 0.5)@2001-01-01 08:10:00+00,
         POINT(2 1.5)@2001-01-01 08:15:00+00),
         [POINT(2 2)@2001-01-01 08:15:00+00, POINT(3 3)@2001-01-01 08:20:00+00)} */

## Indexing

GiST and SP-GiST indexes can be created for table columns of temporal types. The GiST index implements an R-tree and the SP-GiST index implements an n-dimensional quad-tree. Examples of index creation are as follows:

    CREATE INDEX Department_NoEmps_Gist_Idx ON Department USING Gist(NoEmps);
    CREATE INDEX Trips_Trip_SPGist_Idx ON Trips USING SPGist(Trip);

The GiST and SP-GiST indexes store the bounding box for the temporal types. As explained in ttype_p1, these are

- the `tstzspan` type for the `tbool` and `ttext` types,

- the `tbox` type for the `tint` and `tfloat` types,

- the `stbox` type for the `tgeompoint`, `tgeogpoint`, `tgeometry` and `tgeography` types.

A GiST or SP-GiST index can accelerate queries involving the following operators (see ttype_bbox for more information):

- `<<`, `&<`, `&>`, `>>`, which only consider the value dimension in temporal alphanumeric types,

- `<<`, `&<`, `&>`, `>>`, `<<|`, `&<|`, `|&>`, `|>>`, `&</`, `<</`, `/>>`, and `/&>`, which only consider the spatial dimension in temporal point types,

- `&<#`, `<<#`, `#>>`, `#&>`, which only consider the time dimension for all temporal types,

- `&&`, `@>`, `<@`, `~=`, and `|=|`, which consider as many dimensions as they are shared by the indexed column and the query argument. These operators work on bounding boxes (that is, `tstzspan`, `tbox`, or `stbox`), not the entire values.

For example, given the index defined above on the `Department` table and a query that involves a condition with the `&&` (overlaps) operator, if the right argument is a temporal float then both the value and the time dimensions are considered for filtering the tuples of the relation, while if the right argument is a float value, a float span, or a time type, then either the value or the time dimension will be used for filtering the tuples of the relation. Furthermore, a bounding box can be constructed from a value/span and/or a timestamp/period, which can be used for filtering the tuples of the relation. Examples of queries using the index on the `Department` table defined above are given next.

    SELECT * FROM Department WHERE NoEmps && intspan '[1, 5)';
    SELECT * FROM Department WHERE NoEmps && tstzspan '[2001-04-01, 2001-05-01)';
    SELECT * FROM Department WHERE NoEmps &&
      tbox(intspan '[1, 5)', tstzspan '[2001-04-01, 2001-05-01)');
    SELECT * FROM Department WHERE NoEmps &&
      tfloat '{[1@2001-01-01, 1@2001-02-01), [5@2001-04-01, 5@2001-05-01)}';

Similarly, examples of queries using the index on the `Trips` table defined above are given next.

    SELECT * FROM Trips WHERE Trip && geometry 'Polygon((0 0,0 1,1 1,1 0,0 0))';
    SELECT * FROM Trips WHERE Trip && timestamptz '2001-01-01';
    SELECT * FROM Trips WHERE Trip && tstzspan '[2001-01-01, 2001-01-05)';
    SELECT * FROM Trips WHERE Trip &&
      stbox(geometry 'Polygon((0 0,0 1,1 1,1 0,0 0))', tstzspan '[2001-01-01, 2001-01-05]');
    SELECT * FROM Trips WHERE Trip &&
      tgeompoint '{[Point(0 0)@2001-01-01, Point(1 1)@2001-01-02, Point(1 1)@2001-01-05)}';

Finally, B-tree indexes can be created for table columns of all temporal types. For this index type, the only useful operation is equality. There is a B-tree sort ordering defined for values of temporal types, with corresponding `<`, `<=`, `>`, `>=` and operators, but the ordering is rather arbitrary and not usually useful in the real world. B-tree support for temporal types is primarily meant to allow sorting internally in queries, rather than creation of actual indexes.

In order to speed up several of the functions for temporal types, we can add in the `WHERE` clause of queries a bounding box comparison that make uses of the available indexes. For example, this would be typically the case for the functions that project the temporal types to the value/spatial and/or time dimensions. This will filter out the tuples with an index as shown in the following query.

    SELECT atTime(T.Trip, tstzspan '[2001-01-01, 2001-01-02)')
    FROM Trips T
    -- Bouding box index filtering
    WHERE T.Trip && tstzspan '[2001-01-01, 2001-01-02)';

In the case of temporal points, all spatial relationships with the ever and always semantics (see tgeo_spatial_rel) automatically include a bounding box comparison that will make use of any indexes that are available on the temporal points. For this reason, the first version of the relationships is typically used for filtering the tuples with the help of an index when computing the temporal relationships as shown in the following query.

    SELECT tIntersects(T.Trip, R.Geom)
    FROM Trips T, Regions R
    -- Bouding box index filtering
    WHERE eIntersects(T.Trip, R.Geom);

## Statistics and Selectivity

### Statistics Collection

The PostgreSQL planner relies on statistical information about the contents of tables in order to generate the most efficient execution plan for queries. These statistics include a list of some of the most common values in each column and a histogram showing the approximate data distribution in each column. For large tables, a random sample of the table contents is taken, rather than examining every row. This enables large tables to be analyzed in a small amount of time. The statistical information is gathered by the `ANALYZE` command and stored in the `pg_statistic` catalog table. Since different kinds of statistics may be appropriate for different kinds of data, the table only stores very general statistics (such as number of null values) in dedicated columns. Everything else is stored in five “slots”, which are couples of array columns that store the statistics for a column of an arbitrary type.

The statistics collected for time types and temporal types are based on those collected by PostgreSQL for scalar types and span types. For scalar types, such as `float`, the following statistics are collected:

1. fraction of null values,

2. average width, in bytes, of non-null values,

3. number of different non-null values,

4. array of most common values and array of their frequencies,

5. histogram of values, where the most common values are excluded,

6. correlation between physical and logical row ordering.

For span types, like `tstzspan`, three additional histograms are collected:

1. length histogram of non-empty spans,

2. histograms of lower and upper bounds.

For geometries, in addition to (1)–(3), the following statistics are collected:

1. number of dimensions of the values, N-dimensional bounding box, number of rows in the table, number of rows in the sample, number of non-null values,

2. N-dimensional histogram that divides the bounding box into a number of cells and keeps the proportion of values that intersects with each cell.

The statistics collected for columns of the time and span types `tstzset`, `tstzspan`, `tstzspanset`, `intspan`, and `floatspan` replicate those collected by PostgreSQL for the `tstzrange`. This is clear for the span types in MobilityDB, which are more efficient versions of the range types in PostgreSQL. For the `tstzset` and the `tstzspanset` types, a value is converted into its bounding period, then the statistics for the `tstzspan` type are collected.

The statistics collected for columns of temporal types depend on their subtype and their base type. In addition to statistics (1)–(3) that are collected for all temporal types, statistics are collected for the time and the value dimensions independently. More precisely, the following statistics are collected for the time dimension:

- For columns of instant subtype, the statistics (4)–(6) are collected for the timestamps.

- For columns of other subtype, the statistics (7)–(8) are collected for the (bounding box) periods.

The following statistics are collected for the value dimension:

- For columns of temporal types with step interpolation (that is, `tbool`, `ttext`, or `tint`):

  - For the instant subtype, the statistics (4)–(6) are collected for the values.

  - For all other subtypes, the statistics (7)–(8) are collected for the values.

- For columns of the temporal float type (that is, `tfloat`):

  - For the instant subtype, the statistics (4)–(6) are collected for the values.

  - For all other subtype, the statistics (7)–(8) are collected for the (bounding) value spans.

- For columns of temporal point types (that is, `tgeompoint` and `tgeogpoint`) the statistics (9)–(10) are collected for the points.

### Selectivity Estimation

Boolean operators in PostgreSQL can be associated with two selectivity functions, which compute how likely a value of a given type will match a given criterion. These selectivity functions rely on the statistics collected. There are two types of selectivity functions. The restriction selectivity functions try to estimate the percentage of the rows in a table that satisfy a `WHERE`-clause condition of the form `column OP constant`. On the other hand, the join selectivity functions try to estimate the percentage of the rows in a table that satisfy a `WHERE`-clause condition of the form `table1.column1 OP table2.column2`.

MobilityDB defines 23 classes of Boolean operators (such as `=`, `<`, `&&`, `<<`, etc.), each of which can have as left or right arguments a PostgreSQL type (such as `integer`, `timestamptz`, etc.) or a MobilityDB type (such as `tstzspan`, `tint`, etc.). As a consequence, there is a very high number of operators with different arguments to be considered for the selectivity functions. The approach taken was to group these combinations into classes corresponding to the value and time dimensions. The classes correspond to the type of statistics collected as explained in the previous section.

MobilityDB estimates both restriction and join selectivity for time, span, and temporal types.