# Temporal Types (Part 2)

## Modifications

We explain next the semantics of the modification operations (that is, `insert`, `update`, and `delete`) for temporal types. These operations have similar semantics as the corresponding operations for application-time temporal tables introduced in the SQL:2011 standard. The main difference is that SQL uses tuple timestamping (where timestamps are attached to tuples), while temporal values in MobilityDB use attribute timestamping (where timestamps are attached to attribute values). Please refer to this article for a detailed discussion about tuple versus attribute timestamping in temporal databases.

The `insert` operation adds to a temporal value the instants of another one without modifying the existing instants, as illustrated in insert_fig.

### Insert operation for temporal values.

As shown in the figure, the temporal values may only intersect at their boundary, and in that case, they must have the same base value at their common timestamps, otherwise an error is raised. The result of the operation is the union of the instants for both temporal values, as shown in the first result of the figure. This is equivalent to a `merge` operation explained below. Alternatively, as shown in the second result of the figure, the inserted fragments that are disjoint with the original value are connected to the last instant before and the first instant after the fragment. A Boolean parameter `connect` is used to choose between the two results, and the parameter is set to true by default. Notice that this only applies to continuous temporal values.

The `update` operation replaces the instants in the first temporal value with those of the second one as illustrated in update_delete_fig.

### Update and delete operation for temporal values.

As in the case of an `insert` operation, an additional Boolean parameter determines whether the replaced disconnected fragments are connected in the resulting value, as shown in the two possible results in the figure. When the two temporal values are either disjoint or only overlap at their boundary, this corresponds to an `insert` operation as explained above. In this case, the `update` operation behaves as an `upsert` operation in SQL.

The `deleteTime` operation removes the instants of a temporal value that intersect a time value. This operation can be used in two different situations, illustrated in update_delete_fig.

1. In the first case, shown as the top result in the figure, the meaning of operation is to introduce time gaps after removing the instants of the temporal value intersecting the time value. This is equivalent to the restriction operations (ttype_restrictions), which restrict a temporal value to the complement of the time value.

2. The second case, shown as the bottom result in the figure, is used for removing erroneous values (e.g., detected as outliers) without introducing a time gap, or for removing time gaps. In this case, the instants of the temporal value are deleted and the last instant before and the first instant after a removed fragment are connected. This behaviour is specified by setting an additional Boolean parameter of the operation. Notice that this only applies to continuous temporal values.

### Modification operations for temporal tables in SQL

modif_sql_fig shows the equivalent modification operations for temporal tables in the SQL standard. Intuitively, these figures are obtained by rotating 90 degrees clockwise the corresponding figures for temporal values (insert_fig and update_delete_fig). This follows from the fact that in SQL consecutive tuples ordered by time are typically connected through the `LEAD` and `LAG` window functions.

- Insert a temporal value into another one

  `insert(ttype,ttype,connect=true) → ttype`

      SELECT insert(tint '{1@2001-01-01, 3@2001-01-03, 5@2001-01-05}',
        tint '{3@2001-01-03, 7@2001-01-07}');
      -- {1@2001-01-01, 3@2001-01-03, 5@2001-01-05, 7@2001-01-07}
      SELECT insert(tint '{1@2001-01-01, 3@2001-01-03, 5@2001-01-05}',
        tint '{5@2001-01-03, 7@2001-01-07}');
      -- ERROR: The temporal values have different value at their overlapping instant 2001-01-03
      SELECT insert(tfloat '[1@2001-01-01, 2@2001-01-02]',
        tfloat '[1@2001-01-03, 1@2001-01-05]');
      -- [1@2001-01-01, 2@2001-01-02, 1@2001-01-03, 1@2001-01-05]
      SELECT insert(tfloat '[1@2001-01-01, 2@2001-01-02]',
        tfloat '[1@2001-01-03, 1@2001-01-05]', false);
      -- {[1@2001-01-01, 2@2001-01-02], [1@2001-01-03, 1@2001-01-05]}
      SELECT asText(insert(tgeompoint '{[Point(1 1 1)@2001-01-01, Point(2 2 2)@2001-01-02],
        [Point(3 3 3)@2001-01-04],[Point(1 1 1)@2001-01-05]}',
        tgeompoint 'Point(1 1 1)@2001-01-03'));
      /* {[POINT Z (1 1 1)@2001-01-01, POINT Z (2 2 2)@2001-01-02, POINT Z (1 1 1)@2001-01-03,
       POINT Z (3 3 3)@2001-01-04], [POINT Z (1 1 1)@2001-01-05]} */

- Update a temporal value with another one

  `update(ttype,ttype,connect=true) → ttype`

      SELECT update(tint '{1@2001-01-01, 3@2001-01-03, 5@2001-01-05}',
        tint '{5@2001-01-03, 7@2001-01-07}');
      -- {1@2001-01-01, 5@2001-01-03, 5@2001-01-05, 7@2001-01-07}
      SELECT update(tfloat '[1@2001-01-01, 1@2001-01-05]',
        tfloat '[1@2001-01-02, 3@2001-01-03, 1@2001-01-04]');
      -- {[1@2001-01-01, 1@2001-01-02, 3@2001-01-03, 1@2001-01-04, 1@2001-01-05]}
      SELECT asText(update(tgeompoint '{[Point(1 1 1)@2001-01-01, Point(3 3 3)@2001-01-03,
        Point(1 1 1)@2001-01-05], [Point(1 1 1)@2001-01-07]}',
        tgeompoint '[Point(2 2 2)@2001-01-02, Point(2 2 2)@2001-01-04]'));
      /* {[POINT Z (1 1 1)@2001-01-01, POINT Z (2 2 2)@2001-01-02, POINT Z (2 2 2)@2001-01-04,
        POINT Z (1 1 1)@2001-01-05], [POINT Z (1 1 1)@2001-01-07]} */
      SELECT asText(update(
        tgeometry '[Point(1 1)@2001-01-01, Point(3 3)@2001-01-03, Point(1 1)@2001-01-05]',
        tgeometry '[Linestring(2 2,3 3)@2001-01-02, Linestring(3 3,2 2)@2001-01-04]'));
      /* [POINT(1 1)@2001-01-01, LINESTRING(2 2,3 3)@2001-01-02,
          LINESTRING(3 3,2 2)@2001-01-04], (POINT(3 3)@2001-01-04, POINT(1 1)@2001-01-05] */

- Delete the instants of a temporal value that intersect a time value

  `deleteTime(ttype,time,connect=true) → ttype`

      SELECT deleteTime(tint '[1@2001-01-01, 1@2001-01-03]', timestamptz '2001-01-02', false);
      -- {[1@2001-01-01, 1@2001-01-02), (1@2001-01-02, 1@2001-01-03]}
      SELECT deleteTime(tint '[1@2001-01-01, 1@2001-01-03]', timestamptz '2001-01-04');
      -- [1@2001-01-01, 1@2001-01-03]
      SELECT deleteTime(tfloat '[1@2001-01-01, 4@2001-01-02, 2@2001-01-04, 5@2001-01-05]',
        tstzspan '[2001-01-02, 2001-01-04]');
      -- [1@2001-01-01, 5@2001-01-05]
      SELECT asText(deleteTime(tgeompoint '{[Point(1 1 1)@2001-01-01,
        Point(2 2 2)@2001-01-02], [Point(3 3 3)@2001-01-04, Point(3 3 3)@2001-01-05]}',
        tstzspan '[2001-01-02, 2001-01-04]'));
      /* {[POINT Z (1 1 1)@2001-01-01, POINT Z (2 2 2)@2001-01-02, POINT Z (3 3 3)@2001-01-04,
        POINT Z (3 3 3)@2001-01-05]} */

- Append a temporal instant to a temporal value

  `appendInstant(ttype,ttypeInst,interp=NULL) → ttype`

  `appendInstant(ttypeInst,interp=NULL,maxdist=NULL,maxt=NULL) → ttypeSeq`

  The first version of the function returns the result of appending the second argument to the first one. If either input is NULL, then NULL is returned.

  The second version of the function above is an aggregate function that returns the result of successively appending a set of rows of temporal values. This means that it operates in the same way the `SUM()` and `AVG()` functions do and like most aggregates, it also ignores NULL values. Two optional arguments state a maximum distance and a maximum time interval such that a gap is introduced whenever two consecutive instants have a distance or a time gap greater than these values. For temporal points the distance is specified in units of the coordinate system. If one of the arguments are not given, it is not taken into account for determining the gaps.

      SELECT appendInstant(tint '1@2001-01-01', tint '1@2001-01-02');
      -- [1@2001-01-01, 1@2001-01-02]
      SELECT appendInstant(tint '1@2001-01-01', tint '1@2001-01-02', 'discrete');
      -- {1@2001-01-01, 1@2001-01-02}
      SELECT appendInstant(tint '[1@2001-01-01]', tint '1@2001-01-02');
      -- [1@2001-01-01, 1@2001-01-02]
      SELECT asText(appendInstant(tgeompoint '{[Point(1 1 1)@2001-01-01,
        Point(2 2 2)@2001-01-02], [Point(3 3 3)@2001-01-04, Point(3 3 3)@2001-01-05]}',
        tgeompoint 'Point(1 1 1)@2001-01-06'));
      /* {[POINT Z (1 1 1)@2001-01-01, POINT Z (2 2 2)@2001-01-02],
         [POINT Z (3 3 3)@2001-01-04, POINT Z (3 3 3)@2001-01-05,
         POINT Z (1 1 1)@2001-01-06]} */
      SELECT asText(appendInstant(tgeometry '{[Point(1 1)@2001-01-01, Point(2 2)@2001-01-02],
        [Linestring(2 2,3 3)@2001-01-04, Point(3 3)@2001-01-05]}',
        tgeometry 'Linestring(1 1,2 2)@2001-01-06'));
      /* {[POINT(1 1)@2001-01-01, POINT(2 2)@2001-01-02], [LINESTRING(2 2,3 3)@2001-01-04,
           POINT(3 3)@2001-01-05, LINESTRING(1 1,2 2)@2001-01-06]} */

      WITH temp(inst) AS (
        SELECT tfloat '1@2001-01-01' UNION SELECT tfloat '2@2001-01-02' UNION
        SELECT tfloat '3@2001-01-03' UNION SELECT tfloat '4@2001-01-04' UNION
        SELECT tfloat '5@2001-01-05' )
      SELECT appendInstant(inst ORDER BY inst) FROM temp;
      -- [1@2001-01-01, 5@2001-01-05]
      WITH temp(inst) AS (
        SELECT tfloat '1@2001-01-01' UNION SELECT tfloat '2@2001-01-02' UNION
        SELECT tfloat '3@2001-01-03' UNION SELECT tfloat '4@2001-01-04' UNION
        SELECT tfloat '5@2001-01-05' )
      SELECT appendInstant(inst, 'discrete' ORDER BY inst) FROM temp;
      -- {1@2001-01-01, 2@2001-01-02, 3@2001-01-03, 4@2001-01-04, 5@2001-01-05}
      WITH temp(inst) AS (
        SELECT tgeogpoint 'Point(1 1)@2001-01-01' UNION
        SELECT tgeogpoint 'Point(2 2)@2001-01-02' UNION
        SELECT tgeogpoint 'Point(3 3)@2001-01-03' UNION
        SELECT tgeogpoint 'Point(4 4)@2001-01-04' UNION
        SELECT tgeogpoint 'Point(5 5)@2001-01-05' )
      SELECT asText(appendInstant(inst ORDER BY inst)) FROM temp;
      /* [POINT(1 1)@2001-01-01, POINT(2 2)@2001-01-02, POINT(3 3)@2001-01-03,
         POINT(4 4)@2001-01-04, POINT(5 5)@2001-01-05] */

  Notice that in the first query above with `tfloat`, the intermediate observations were removed by the normalization process since they were redundant due to linear interpolation. This is not the case for the second query with discrete interpolation or the third query with `tgeogpoint`, where geodetic coordinates are used.

      WITH temp(inst) AS (
        SELECT tfloat '1@2001-01-01' UNION SELECT tfloat '2@2001-01-02' UNION
        SELECT tfloat '4@2001-01-04' UNION SELECT tfloat '5@2001-01-05' UNION
        SELECT tfloat '7@2001-01-07' )
      SELECT appendInstant(inst, NULL, 0.0, '1 day' ORDER BY inst) FROM temp;
      -- {[1@2001-01-01, 2@2001-01-02], [4@2001-01-04, 5@2001-01-05], [7@2001-01-07]}
      WITH temp(inst) AS (
        SELECT tgeompoint 'Point(1 1)@2001-01-01' UNION
        SELECT tgeompoint 'Point(2 2)@2001-01-02' UNION
        SELECT tgeompoint 'Point(4 4)@2001-01-04' UNION
        SELECT tgeompoint 'Point(5 5)@2001-01-05' UNION
        SELECT tgeompoint 'Point(7 7)@2001-01-07' )
      SELECT asText(appendInstant(inst, NULL, sqrt(2), '1 day' ORDER BY inst)) FROM temp;
      /* {[POINT(1 1)@2001-01-01, POINT(2 2)@2001-01-02],
         [POINT(4 4)@2001-01-04, POINT(5 5)@2001-01-05], [POINT(7 7)@2001-01-07]} */

- Append a temporal sequence to a temporal value

  `appendSequence(ttype,ttypeSeq) → {ttypeSeq,ttypeSeqSet}`

  `appendSequence(ttypeSeq) → {ttypeSeq,ttypeSeqSet}`

  The first version of the function returns the result of appending the second argument to the first one. If either input is NULL, then NULL is returned.

  The second version of the function above is an aggregate function that returns the result of successively appending a set of rows of temporal values. This means that it operates in the same way the `SUM()` and `AVG()` functions do and like most aggregates, it also ignores NULL values.

      SELECT appendSequence(tint '1@2001-01-01', tint '{2@2001-01-02, 3@2001-01-03}');
      -- {1@2001-01-01, 2@2001-01-02, 3@2001-01-03}
      SELECT appendSequence(tint '[1@2001-01-01, 2@2001-01-02]',
        tint '[2@2001-01-02, 3@2001-01-03]');
      -- [1@2001-01-01, 2@2001-01-02, 3@2001-01-03]
      SELECT asText(appendSequence(tgeompoint '{[Point(1 1 1)@2001-01-01,
        Point(2 2 2)@2001-01-02], [Point(3 3 3)@2001-01-04, Point(3 3 3)@2001-01-05]}',
        tgeompoint '[Point(3 3 3)@2001-01-05, Point(1 1 1)@2001-01-06]'));
      /* {[POINT Z (1 1 1)@2001-01-01, POINT Z (2 2 2)@2001-01-02],
         [POINT Z (3 3 3)@2001-01-04, POINT Z (3 3 3)@2001-01-05,
         POINT Z (1 1 1)@2001-01-06]} */
      SELECT asText(appendSequence(tgeometry '{[Point(1 1)@2001-01-01, Point(2 2)@2001-01-02],
        [Linestring(3 3,2 2)@2001-01-04, Point(3 3)@2001-01-05]}',
        tgeometry '[Point(3 3)@2001-01-05, Linestring(3 3,1 1)@2001-01-06]'));
      /* {[POINT(1 1)@2001-01-01, POINT(2 2)@2001-01-02], [LINESTRING(3 3,2 2)@2001-01-04,
           POINT(3 3)@2001-01-05, LINESTRING(3 3,1 1)@2001-01-06]} */

- Merge the temporal values

  `merge(ttype,ttype) → ttype`

  `merge(ttype[]) → ttype`

  `merge(ttype) → ttype`

  The temporal values may only intersect at their boundary. In that case, for all temporal types excepted `tgeometry` and `tgeography`, their base values at the common timestamps must be the same, otherwise an error is raised. For the `tgeometry` and `tgeography` types, if the value at their common timestamps is different, the resulting value after the `merge` will be the spatial union of the values. The reason for a different behaviour for these types is that they are the only temporal types that are not scalar types, that is, their value at a given timestamp is not a single element of the domain of the base type, but rather a subset of their domain.

  The third version of the function above is an aggregate function that returns the result of successively appending a set of rows of temporal values. This means that it operates in the same way the `SUM()` and `AVG()` functions do and like most aggregates, it also ignores NULL values.

      SELECT merge(tint '1@2001-01-01', tint '1@2001-01-02');
      -- {1@2001-01-01, 1@2001-01-02}
      SELECT merge(tint '[1@2001-01-01, 2@2001-01-02]', tint '[2@2001-01-02, 1@2001-01-03]');
      -- [1@2001-01-01, 2@2001-01-02, 1@2001-01-03]
      SELECT merge(tint '[1@2001-01-01, 2@2001-01-02]', tint '[3@2001-01-03, 1@2001-01-04]');
      -- {[1@2001-01-01, 2@2001-01-02], [3@2001-01-03, 1@2001-01-04]}
      SELECT merge(tint '[1@2001-01-01, 2@2001-01-02]', tint '[1@2001-01-02, 2@2001-01-03]');
      -- ERROR:  The temporal values have different value at their common timestamp 2001-01-02
      SELECT asText(merge(tgeompoint '{[Point(1 1 1)@2001-01-01,
        Point(2 2 2)@2001-01-02], [Point(3 3 3)@2001-01-04, Point(3 3 3)@2001-01-05]}',
        tgeompoint '{[Point(3 3 3)@2001-01-05, Point(1 1 1)@2001-01-06]}'));
      /* {[POINT Z (1 1 1)@2001-01-01, POINT Z (2 2 2)@2001-01-02],
         [POINT Z (3 3 3)@2001-01-04, POINT Z (3 3 3)@2001-01-05,
         POINT Z (1 1 1)@2001-01-06]} */
      SELECT asText(merge(tgeometry 'Linestring(1 1,5 1)@2001-01-01',
        tgeometry '{Linestring(5 1,10 1)@2001-01-01, Point(3 3)@2001-01-02}'));
      -- {LINESTRING(1 1,5 1,10 1)@2001-01-01, POINT(3 3)@2001-01-02}

      SELECT merge(ARRAY[tint '1@2001-01-01', '1@2001-01-02']);
      -- {1@2001-01-01, 1@2001-01-02}
      SELECT merge(ARRAY[tint '{1@2001-01-01, 2@2001-01-02}', '{2@2001-01-02, 3@2001-01-03}']);
      -- {1@2001-01-01, 2@2001-01-02, 3@2001-01-03}
      SELECT merge(ARRAY[tint '{1@2001-01-01, 2@2001-01-02}', '{3@2001-01-03, 4@2001-01-04}']);
      -- {1@2001-01-01, 2@2001-01-02, 3@2001-01-03, 4@2001-01-04}
      SELECT merge(ARRAY[tint '[1@2001-01-01, 2@2001-01-02]', '[2@2001-01-02, 1@2001-01-03]']);
      -- [1@2001-01-01, 2@2001-01-02, 1@2001-01-03]
      SELECT merge(ARRAY[tint '[1@2001-01-01, 2@2001-01-02]', '[3@2001-01-03, 4@2001-01-04]']);
      -- {[1@2001-01-01, 2@2001-01-02], [3@2001-01-03, 4@2001-01-04]}
      SELECT asText(merge(ARRAY[tgeompoint '{[Point(1 1)@2001-01-01, Point(2 2)@2001-01-02],
        [Point(3 3)@2001-01-03, Point(4 4)@2001-01-04]}', '{[Point(4 4)@2001-01-04,
        Point(3 3)@2001-01-05], [Point(6 6)@2001-01-06, Point(7 7)@2001-01-07]}']));
      /* {[Point(1 1)@2001-01-01, Point(2 2)@2001-01-02], [Point(3 3)@2001-01-03,
         Point(4 4)@2001-01-04, Point(3 3)@2001-01-05],
         [Point(6 6)@2001-01-06, Point(7 7)@2001-01-07]} */
      SELECT asText(merge(ARRAY[tgeompoint '{[Point(1 1)@2001-01-01, Point(2 2)@2001-01-02]}',
        '{[Point(2 2)@2001-01-02, Point(1 1)@2001-01-03]}']));
      -- [Point(1 1)@2001-01-01, Point(2 2)@2001-01-02, Point(1 1)@2001-01-03]
      SELECT asText(merge(ARRAY[tgeometry '{[Point(1 1)@2001-01-01, Point(2 2)@2001-01-02]}',
        '{[Linestring(2 2,1 1)@2001-01-02, Point(1 1)@2001-01-03]}']));
      -- {[POINT(1 1)@2001-01-01, LINESTRING(2 2,1 1)@2001-01-02, POINT(1 1)@2001-01-03]}

      WITH temp(inst) AS (
      SELECT tfloat '1@2001-01-01' UNION SELECT tfloat '2@2001-01-02' UNION
      SELECT tfloat '3@2001-01-03' UNION SELECT tfloat '4@2001-01-04' UNION
      SELECT tfloat '5@2001-01-05' )
      SELECT merge(inst) FROM temp;
      -- {1@2001-01-01, 2@2001-01-02, 3@2001-01-03, 4@2001-01-04, 5@2001-01-05}

## Restrictions

There are three complementary sets of restriction functions. The first set functions restricts the temporal value with respect to a value or a time extent. Examples are `atValues` or `atTime`. The second set functions restricts the temporal value with respect to the complement of a value or a time extent. Examples are `minusValues` or `minusTime`. Finally, the third set functions restricts the temporal value before or after a timestamptz. Examples are `beforeTimestamp` or `afterTimestamp`.

- Restrict to (the complement of) a set of values

  `atValues(ttype,values) → ttype`

  `minusValues(ttype,values) → ttype`

      SELECT atValues(tint '[1@2001-01-01, 1@2001-01-15)', 1);
      -- [1@2001-01-01, 1@2001-01-15)
      SELECT atValues(tfloat '[1@2001-01-01, 4@2001-01-4)', floatset '{1, 3, 5}');
      -- {[1@2001-01-01], [3@2001-01-03]}
      SELECT atValues(tfloat '[1@2001-01-01, 4@2001-01-4)', floatspan '[1,3]');
      -- [1@2001-01-01, 3@2001-01-03]
      SELECT atValues(tfloat '[1@2001-01-01, 5@2001-01-05)',
        floatspanset '{[1,2], [3,4]}');
      -- {[1@2001-01-01, 2@2001-01-02],[3@2001-01-03, 4@2001-01-04]}
      SELECT asText(atValues(tgeompoint '[Point(0 0 0)@2001-01-01, Point(2 2 2)@2001-01-03)',
        geometry 'Point(1 1 1)'));
      -- {[POINT Z (1 1 1)@2001-01-02]}
      SELECT asText(atValues(tgeompoint '[Point(0 0)@2001-01-01, Point(2 2)@2001-01-03)',
        geomset '{"Point(0 0)", "Point(1 1)"}'));
      -- {[POINT(0 0)@2001-01-01], [POINT(1 1)@2001-01-02]}
      SELECT asText(atValues(tgeometry '[Point(0 0)@2001-01-01, Linestring(0 0,2 2)@2001-01-02,
        Linestring(0 0,2 2)@2001-01-03]', geometry 'Linestring(0 0,2 2)'));
      -- {[LINESTRING(0 0,2 2)@2001-01-02, LINESTRING(0 0,2 2)@2001-01-03]}

      SELECT minusValues(tint '[1@2001-01-01, 2@2001-01-02, 2@2001-01-03)', 1);
      -- {[2@2001-01-02, 2@2001-01-03)}
      SELECT minusValues(tfloat '[1@2001-01-01, 4@2001-01-4)', floatset '{2, 3}');
      /* {[1@2001-01-01, 2@2001-01-02), (2@2001-01-02, 3@2001-01-03),
         (3@2001-01-03, 4@2001-01-04)} */
      SELECT minusValues(tfloat '[1@2001-01-01, 4@2001-01-4)', floatspan '[2,3]');
      -- {[1@2001-01-01, 2@2001-01-02), (3@2001-01-03, 4@2001-01-04)}
      SELECT minusValues(tfloat '[1@2001-01-01, 5@2001-01-05)',
        floatspanset '{[1,2], [3,4]}');
      -- {(2@2001-01-02, 3@2001-01-03), (4@2001-01-04, 5@2001-01-05)}
      SELECT asText(minusValues(tgeompoint '[Point(0 0 0)@2001-01-01, Point(2 2 2)@2001-01-03)',
        geometry 'Point(1 1 1)'));
      /* {[POINT Z (0 0 0)@2001-01-01, POINT Z (1 1 1)@2001-01-02),
         (POINT Z (1 1 1)@2001-01-02, POINT Z (2 2 2)@2001-01-03)} */
      SELECT asText(minusValues(tgeompoint '[Point(0 0 0)@2001-01-01, Point(3 3 3)@2001-01-04)',
        geomset '{"Point(1 1 1)', 'Point(2 2 2)"}'));
      /* {[POINT Z (0 0 0)@2001-01-01, POINT Z (1 1 1)@2001-01-02),
         (POINT Z (1 1 1)@2001-01-02, POINT Z (2 2 2)@2001-01-03),
        (POINT Z (2 2 2)@2001-01-03, POINT Z (3 3 3)@2001-01-04)} */
      SELECT asText(minusValues(tgeometry '[Point(0 0)@2001-01-01, Linestring(0 0,2 2)@2001-01-02,
        Linestring(0 0,2 2)@2001-01-03]', geometry 'Linestring(0 0,2 2)'));
      --  {[POINT(0 0)@2001-01-01, POINT(0 0)@2001-01-02)}

- Restrict to (the complement of) a time value

  `atTime(ttype,times) → ttype`

  `minusTime(ttype,times) → ttype`

      SELECT atTime(tfloat '[1@2001-01-01, 5@2001-01-05)', timestamptz '2001-01-02');
      -- 2@2001-01-02
      SELECT atTime(tint '[1@2001-01-01, 1@2001-01-15)',
        tstzset '{2001-01-01, 2001-01-03}');
      -- {1@2001-01-01, 1@2001-01-03}
      SELECT atTime(tfloat '{[1@2001-01-01, 3@2001-01-03), [3@2001-01-04, 1@2001-01-06)}',
        tstzspan '[2001-01-02,2001-01-05)');
      -- {[2@2001-01-02, 3@2001-01-03), [3@2001-01-04, 2@2001-01-05)}
      SELECT atTime(tint '[1@2001-01-01, 1@2001-01-15)',
        tstzspanset '{[2001-01-01, 2001-01-03), [2001-01-04, 2001-01-05)}');
      -- {[1@2001-01-01, 1@2001-01-03),[1@2001-01-04, 1@2001-01-05)}

      SELECT minusTime(tfloat '[1@2001-01-01, 5@2001-01-05)', timestamptz '2001-01-02');
      -- {[1@2001-01-01, 2@2001-01-02), (2@2001-01-02, 5@2001-01-05)}
      SELECT minusTime(tint '[1@2001-01-01, 1@2001-01-15)',
        tstzset '{2001-01-02, 2001-01-03}');
      /* {[1@2001-01-01, 1@2001-01-02), (1@2001-01-02, 1@2001-01-03),
         (1@2001-01-03, 1@2001-01-15)} */
      SELECT minusTime(tfloat '{[1@2001-01-01, 3@2001-01-03), [3@2001-01-04, 1@2001-01-06)}',
        tstzspan '[2001-01-02,2001-01-05)');
      -- {[1@2001-01-01, 2@2001-01-02), [2@2001-01-05, 1@2001-01-06)}
      SELECT minusTime(tint '[1@2001-01-01, 1@2001-01-15)',
        tstzspanset '{[2001-01-02, 2001-01-03), [2001-01-04, 2001-01-05)}');
      /* {[1@2001-01-01, 1@2001-01-02), [1@2001-01-03, 1@2001-01-04),
         [1@2001-01-05, 1@2001-01-15)} */

- Keep the instants of a temporal value that are before/after or equal a timestamp

  `beforeTimestamp(ttype,timestamptz,strict bool=TRUE) → ttype`

  `afterTimestamp(ttype,timestamptz,strict bool=TRUE) → ttype`

      SELECT beforeTimestamp(tint '[1@2001-01-01, 1@2001-01-03]', timestamptz '2001-01-02');
      -- [1@2001-01-01, 1@2001-01-02)
      SELECT beforeTimestamp(tint '[1@2001-01-01, 1@2001-01-03]', timestamptz '2001-01-01');
      -- NULL
      SELECT beforeTimestamp(tint '[1@2001-01-01, 1@2001-01-03]', timestamptz '2001-01-01',
        false);
      -- [1@2001-01-01]
      SELECT afterTimestamp(tfloat '[1@2001-01-01, 3@2001-01-03]', timestamptz '2001-01-02]',
        false);
      -- [2@2001-01-02, 3@2001-01-03]
      SELECT asText(afterTimestamp(tgeompoint '{[Point(1 1 1)@2001-01-01,
        Point(2 2 2)@2001-01-02], [Point(3 3 3)@2001-01-04, Point(3 3 3)@2001-01-05]}',
        timestamptz '2001-01-03'));
      /* {[POINT Z (3 3 3)@2001-01-04, POINT Z (3 3 3)@2001-01-05]} */

## Bounding Box Operators

These operators test whether the bounding boxes of their arguments satisfy the predicate and result in a Boolean value. As stated in ttype_p1, the bounding box associated to a temporal type depends on the base type: It is the `tstzspan` type for the `tbool` and `ttext` types, the `tbox` type for the `tint` and `tfloat` types, and the `stbox` type for the `tgeompoint` and `tgeogpoint` types. Furthermore, as seen in box_conversions, many PostgreSQL, PostGIS, or MobilityDB types can be converted to the `tbox` and `stbox` types. For example, numeric and span types can be converted to type `tbox`, types `geometry` and `geography` can be converted to type `stbox`, and time types and temporal types can be converted to types `tbox` and `stbox`.

A first set of operators consider the topological relationships between the bounding boxes. There are five topological operators: overlaps (`&&`), contains (`@>`), contained (`<@`), same (`~=`), and adjacent (`-|-`). The arguments of these operators can be a base type, a box, or a temporal type and the operators verify the topological relationship taking into account the value and/or the time dimension depending on the type of the arguments.

Another set of operators consider the relative position of the bounding boxes. The operators `<<`, `>>`, `&<`, and `&>` consider the value dimension for `tint` and `tfloat` types and the X coordinates for the `tgeompoint` and `tgeogpoint` types, the operators `<<|`, `|>>`, `&<|`, and `|&>` consider the Y coordinates for the `tgeompoint` and `tgeogpoint` types, the operators `<</`, `/>>`, `&</`, and `/&>` consider the Z coordinates for the `tgeompoint` and `tgeogpoint` types, and the operators `<<#`, `#>>`, `#&<`, and `#&>` consider the time dimension for all temporal types.

Finally, it is worth noting that the bounding box operators allow to mix 2D/3D geometries but in that case, the computation is only performed on 2D.

We refer to box_topo_pos for the bounding box operators.

## Comparisons

### Traditional Comparisons

The traditional comparison operators (`=`, `<`, and so on) require that the left and right operands be of the same base type. Excepted equality and inequality, the other comparison operators are not useful in the real world but allow B-tree indexes to be constructed on temporal types. These operators compare the bounding periods (see setspan_comparisons), then the bounding boxes (see box_comparisons) and if those are equal, then the comparison depends on the subtype. For instant values, they compare first the timestamps and if those are equal, compare the values. For sequence values, they compare the first N instants, where N is the minimum of the number of composing instants of both values. Finally, for sequence set values, they compare the first N sequence values, where N is the minimum of the number of composing sequences of both values.

The equality and inequality operators consider the equivalent representation for different subtypes as shown next.

    SELECT tint '1@2001-01-01' = tint '{1@2001-01-01}';
    -- true
    SELECT tfloat '1.5@2001-01-01' = tfloat '[1.5@2001-01-01]';
    -- true
    SELECT ttext 'AAA@2001-01-01' = ttext '{[AAA@2001-01-01]}';
    -- true
    SELECT tgeompoint '{Point(1 1)@2001-01-01, Point(2 2)@2001-01-02}' =
      tgeompoint '{[Point(1 1)@2001-01-01], [Point(2 2)@2001-01-02]}';
    -- true
    SELECT tgeography '[Point(1 1)@2001-01-01, Linestring(1 1,2 2)@2001-01-02]' =
      tgeography '{[Point(1 1)@2001-01-01, Linestring(1 1,2 2)@2001-01-02]}';
    -- true

- Traditional comparisons

  `ttype {=, <>, <, >, <=, >=} ttype → boolean`

  `temporal_cmp(ttype,ttype) → int`

  Function `temporal_cmp` returns -1, 0, or 1 depending on whether the first argument is, respectively, less than, equal to, or greater than the second one.

      SELECT tint '[1@2001-01-01, 1@2001-01-04)' = tint '[2@2001-01-03, 2@2001-01-05)';
      -- false
      SELECT tint '[1@2001-01-01, 1@2001-01-04)' <> tint '[2@2001-01-03, 2@2001-01-05)';
      -- true
      SELECT tint '[1@2001-01-01, 1@2001-01-04)' < tint '[2@2001-01-03, 2@2001-01-05)';
      -- true
      SELECT ttfloat '[1@2001-01-01, 1@2001-01-04)' > ttfloat '[2@2001-01-03, 2@2001-01-05)';
      -- false
      SELECT tint '[1@2001-01-01, 1@2001-01-04)' <= tint '[2@2001-01-03, 2@2001-01-05)';
      -- true
      SELECT tint '[1@2001-01-01, 1@2001-01-04)' >= tint '[2@2001-01-03, 2@2001-01-05)';
      -- false
      SELECT temporal_cmp(tfloat '[1@2001-01-01, 1@2001-01-04)', '[2@2001-01-03, 2@2001-01-05)');
      -- -1

### Ever and Always Comparisons

A possible generalization of the traditional comparison operators (`=`, `<>`, `<`, `<=`, etc.) to temporal types consists in determining whether the comparison is ever or always true. In this case, the result is a Boolean value. MobilityDB provides operators to test whether the comparison of a temporal value and a value of the base type or two temporal values is ever or always true. These operators are denoted by prefixing the traditional comparison operators with, respectively, `?` (ever) and `%` (always). Some examples are `?=`, `%<>`, or `?<=`. Ever and always equality and non-equality are available for all temporal types, while ever and always inequalities are only available for temporal types whose base type has a total order defined, that is, `tint`, `tfloat`, or `ttext`. The ever and always comparisons are inverse operators: for example, `?=` is the inverse of `%<>`, and `?>` is the inverse of `%<=`.

- Ever and always comparisons

  `{base,ttype} {?=, ?<>, ?<, ?>, ?<=, ?>=} {base,ttype} → boolean`

  `{base,ttype} {%=, %<>, %<, %>, %<=, %>=} {base,ttype} → boolean`

  The operators do not take into account whether the bounds are inclusive or not.

      SELECT tint '[1@2001-01-01, 3@2001-01-04]' ?= 2;
      -- false
      SELECT tgeometry '[Point(0 0)@2001-01-01, Linestring(0 0,1 1)@2001-01-04]' ?=
        geometry 'Linestring(1 1,0 0)';
      -- true
      SELECT tfloat '[1@2001-01-01, 1@2001-01-04)' %= 1;
      -- true
      SELECT tgeometry '[Linestring(0 0,1 1)@2001-01-01, Linestring(1 1,0 0)@2001-01-04)' %=
        geometry 'Linestring(0 0,1 1)';
      -- true

      SELECT tfloat '[1@2001-01-01, 3@2001-01-04)' ?<> 2;
      -- true
      SELECT tgeompoint '[Point(1 1)@2001-01-01, Point(1 1)@2001-01-04)' ?<>
        geometry 'Point(1 1)';
      -- false
      SELECT tfloat '[1@2001-01-01, 3@2001-01-04)' %<> 2;
      -- false
      SELECT tgeogpoint '[Point(1 1)@2001-01-01, Point(1 1)@2001-01-04)' %<>
        geography 'Point(2 2)';
      -- true

      SELECT tint '[1@2001-01-01, 4@2001-01-04]' ?< 2;
      -- true
      SELECT tfloat '[1@2001-01-01, 4@2001-01-04)' %< 2;
      -- false

      SELECT tint '[1@2001-01-03, 1@2001-01-05]' ?> 0;
      -- true
      SELECT tfloat '[1@2001-01-03, 1@2001-01-05)' %> 1;
      -- false

      SELECT tint '[1@2001-01-01, 1@2001-01-05]' ?<= 2;
      -- true
      SELECT tfloat '[1@2001-01-01, 1@2001-01-05)' %<= 4;
      -- true

      SELECT ttext '{[AAA@2001-01-01, AAA@2001-01-03), [BBB@2001-01-04, BBB@2001-01-05)}'
         ?> 'AAA'::text;
      -- true
      SELECT ttext '{[AAA@2001-01-01, AAA@2001-01-03), [BBB@2001-01-04, BBB@2001-01-05)}'
         %> 'AAA'::text;
      -- false

### Temporal Comparisons

Another possible generalization of the traditional comparison operators (`=`, `<>`, `<`, `<=`, etc.) to temporal types consists in determining whether the comparison is true or false at each instant. In this case, the result is a temporal Boolean. The temporal comparison operators are denoted by prefixing the traditional comparison operators with `#`. Some examples are `#=` or `#<=`. Temporal equality and non-equality are available for all temporal types, while temporal inequalities are only available for temporal types whose base type has a total order defined, that is, `tint`, `tfloat`, or `ttext`.

- Temporal comparisons

  `{base,ttype} {#=, #<>, #<, #>, #<=, #>=} {base,ttype} → boolean`

      SELECT tfloat '[1@2001-01-01, 2@2001-01-04)' #= 3;
      -- {[f@2001-01-01, f@2001-01-04)}
      SELECT tfloat '[1@2001-01-01, 4@2001-01-04)' #= tfloat '[4@2001-01-02, 1@2001-01-05)';
      -- {[f@2001-01-02, t@2001-01-03], (f@2001-01-03, f@2001-01-04)}
      SELECT tgeompoint '[Point(0 0)@2001-01-01, Point(2 2)@2001-01-03)' #=
        geometry 'Point(1 1)';
      -- {[f@2001-01-01, t@2001-01-02], (f@2001-01-02, f@2001-01-03)}
      SELECT tgeometry '[Point(0 0)@2001-01-01, Linestring(1 1,2 2)@2001-01-03]' #=
        geometry 'Linestring(2 2,1 1)';
      -- {[f@2001-01-01, t@2001-01-03]}

      SELECT tfloat '[1@2001-01-01, 4@2001-01-04)' #<> 2;
      -- {[t@2001-01-01, f@2001-01-02], (t@2001-01-02, 2001-01-04)}
      SELECT tfloat '[1@2001-01-01, 4@2001-01-04)' #<> tfloat '[2@2001-01-02, 2@2001-01-05)';
      -- {[f@2001-01-02], (t@2001-01-02, t@2001-01-04)}
      SELECT tgeometry '[Point(0 0)@2001-01-01, Linestring(1 1,2 2)@2001-01-03]' #<>
        geometry 'Linestring(2 2,1 1)';
      -- {[t@2001-01-01, f@2001-01-03]}

      SELECT tfloat '[1@2001-01-01, 4@2001-01-04)' #< 2;
      -- {[t@2001-01-01, f@2001-01-02, f@2001-01-04)}
      SELECT 1 #> tint '[1@2001-01-03, 1@2001-01-05)';
      -- [f@2001-01-03, f@2001-01-05)
      SELECT tfloat '[1@2001-01-01, 1@2001-01-05)' #<= tfloat '{2@2001-01-03, 3@2001-01-04}';
      -- {t@2001-01-03, t@2001-01-04}
      SELECT 'AAA'::text #> ttext '{[AAA@2001-01-01, AAA@2001-01-03),
        [BBB@2001-01-04, BBB@2001-01-05)}';
      -- {[f@2001-01-01, f@2001-01-03), [t@2001-01-04, t@2001-01-05)}

## Miscellaneous

- Version of the MobilityDB extension

  `mobilitydb_version() → text`

      SELECT mobilitydb_version();
      -- MobilityDB 1.3.0

- Versions of the MobilityDB extension and its dependencies

  `mobilitydb_full_version() → text`

      SELECT mobilitydb_full_version();
      /* MobilityDB 1.3.0, PostgreSQL 17.2, PostGIS 3.5.2, GEOS 3.12.0-CAPI-1.18.0, PROJ 9.2.0,
         JSON-C 0.13.1, GSL 2.5 */