v60-0002-Document-temporal-update-delete.patch
application/octet-stream
Filename: v60-0002-Document-temporal-update-delete.patch
Type: application/octet-stream
Part: 3
From 1ddc0be41c48ebf91265ebd1e612fb6d3c763c59 Mon Sep 17 00:00:00 2001
From: "Paul A. Jungwirth" <pj@illuminatedcomputing.com>
Date: Tue, 17 Jun 2025 21:38:40 -0700
Subject: [PATCH v60 02/10] Document temporal update/delete
The FOR PORTION OF syntax will also be documented in the reference pages
for UPDATE and DELETE, but this commit adds a new section to the DML chapter,
called "Updating and Deleting Temporal Data," giving a conceptual description,
as well as a glossary term for "temporal leftovers". The SQL standard doesn't
give any term for the supplementary INSERTs after an UPDATE/DELETE FOR PORTION
OF, but it is really handy to have a name for them.
Author: Paul A. Jungwirth <pj@illuminatedcomputing.com>
---
doc/src/sgml/dml.sgml | 140 ++++++++++++++++++++++++
doc/src/sgml/glossary.sgml | 15 +++
doc/src/sgml/images/Makefile | 4 +-
doc/src/sgml/images/temporal-delete.svg | 41 +++++++
doc/src/sgml/images/temporal-delete.txt | 12 ++
doc/src/sgml/images/temporal-update.svg | 45 ++++++++
doc/src/sgml/images/temporal-update.txt | 12 ++
7 files changed, 268 insertions(+), 1 deletion(-)
create mode 100644 doc/src/sgml/images/temporal-delete.svg
create mode 100644 doc/src/sgml/images/temporal-delete.txt
create mode 100644 doc/src/sgml/images/temporal-update.svg
create mode 100644 doc/src/sgml/images/temporal-update.txt
diff --git a/doc/src/sgml/dml.sgml b/doc/src/sgml/dml.sgml
index 61c64cf6c49..b3792f2bf7a 100644
--- a/doc/src/sgml/dml.sgml
+++ b/doc/src/sgml/dml.sgml
@@ -261,6 +261,146 @@ DELETE FROM products;
</para>
</sect1>
+ <sect1 id="dml-application-time-update-delete">
+ <title>Updating and Deleting Temporal Data</title>
+
+ <para>
+ Special syntax is available to update and delete from <link
+ linkend="ddl-application-time">application-time temporal tables</link>. (No
+ extra syntax is required to insert into them: the user just
+ provides the application time like any other attribute.) When updating
+ or deleting, the user can target a specific portion of history. Only
+ rows overlapping that history are affected, and within those rows only
+ the targeted history is changed. If a row contains more history beyond
+ what is targeted, its application time is reduced to fit within the
+ targeted interval, and new rows are inserted to preserve the history
+ that was not targeted.
+ </para>
+
+ <para>
+ Recall the example table from <xref linkend="temporal-entities-figure" />,
+ containing this data:
+
+<programlisting>
+ id | price | valid_at
+----+-------+-------------------------
+ 5 | 5 | [2020-01-01,2022-01-01)
+ 5 | 8 | [2022-01-01,)
+ 6 | 9 | [2021-01-01,2024-01-01)
+</programlisting>
+
+ A temporal update might look like this:
+
+<programlisting>
+UPDATE products
+ FOR PORTION OF valid_at FROM '2023-09-01' TO '2025-03-01'
+ AS p
+ SET price = 12
+ WHERE id = 5;
+</programlisting>
+
+ That command will update the second record for product 5. It will set the
+ price to 12 and
+ the application time to <literal>[2023-09-01,2025-03-01)</literal>.
+ Then, since the row's application time was originally
+ <literal>[2022-01-01,)</literal>, the command must insert two
+ <glossterm linkend="glossary-temporal-leftovers">temporal
+ leftovers</glossterm>: one for history before September 1, 2023, and
+ another for history since March 1, 2025. After the update, the table
+ has four rows for product 5:
+
+<programlisting>
+ id | price | valid_at
+----+-------+-------------------------
+ 5 | 5 | [2020-01-01,2022-01-01)
+ 5 | 8 | [2022-01-01,2023-09-01)
+ 5 | 12 | [2023-09-01,2025-03-01)
+ 5 | 8 | [2025-03-01,)
+</programlisting>
+
+ The new history could be plotted as in <xref linkend="temporal-update-figure"/>.
+ </para>
+
+ <figure id="temporal-update-figure">
+ <title>Temporal Update Example</title>
+ <mediaobject>
+ <imageobject>
+ <imagedata fileref="images/temporal-update.svg" format="SVG" width="100%"/>
+ </imageobject>
+ </mediaobject>
+ </figure>
+
+ <para>
+ Similarly, a specific portion of history may be targeted when
+ deleting rows from a table. In that case, the original rows are
+ removed, but new
+ <glossterm linkend="glossary-temporal-leftovers">temporal leftovers</glossterm>
+ are inserted to preserve the untouched history. The syntax for a
+ temporal delete is:
+
+<programlisting>
+DELETE FROM products
+ FOR PORTION OF valid_at FROM '2021-08-01' TO '2023-09-01'
+ AS p
+WHERE id = 5;
+</programlisting>
+
+ Continuing the example, this command would delete two records. The
+ first record would yield a single temporal leftover, and the second
+ would be deleted entirely. The rows for product 5 would now be:
+
+<programlisting>
+ id | price | valid_at
+----+-------+-------------------------
+ 5 | 5 | [2020-01-01,2021-08-01)
+ 5 | 12 | [2023-09-01,2025-03-01)
+ 5 | 8 | [2025-03-01,)
+</programlisting>
+
+ The new history could be plotted as in <xref linkend="temporal-delete-figure"/>.
+ </para>
+
+ <figure id="temporal-delete-figure">
+ <title>Temporal Delete Example</title>
+ <mediaobject>
+ <imageobject>
+ <imagedata fileref="images/temporal-delete.svg" format="SVG" width="100%"/>
+ </imageobject>
+ </mediaobject>
+ </figure>
+
+ <para>
+ Instead of using the <literal>FROM ... TO ...</literal> syntax,
+ temporal update/delete commands can also give the targeted
+ range/multirange directly, inside parentheses. For example:
+ <literal>DELETE FROM products FOR PORTION OF valid_at ('[2028-01-01,)') ...</literal>.
+ This syntax is required when application time is stored
+ in a multirange column.
+ </para>
+
+ <para>
+ When application time is stored in a rangetype column, zero, one or
+ two temporal leftovers are produced by each row that is
+ updated/deleted. With a multirange column, only zero or one temporal
+ leftover is produced. The leftover bounds are computed using
+ <literal>range_minus_multi</literal> and
+ <literal>multirange_minus_multi</literal>
+ (see <xref linkend="functions-range"/>).
+ </para>
+
+ <para>
+ The bounds given to <literal>FOR PORTION OF</literal> must be
+ constant. Functions like <literal>NOW()</literal> are allowed, but
+ column references are not.
+ </para>
+
+ <para>
+ When temporal leftovers are inserted, all <literal>INSERT</literal>
+ triggers are fired, but permission checks for inserting rows are
+ skipped.
+ </para>
+ </sect1>
+
<sect1 id="dml-returning">
<title>Returning Data from Modified Rows</title>
diff --git a/doc/src/sgml/glossary.sgml b/doc/src/sgml/glossary.sgml
index a76cf5c383f..10429edbb52 100644
--- a/doc/src/sgml/glossary.sgml
+++ b/doc/src/sgml/glossary.sgml
@@ -1916,6 +1916,21 @@
</glossdef>
</glossentry>
+ <glossentry id="glossary-temporal-leftovers">
+ <glossterm>Temporal leftovers</glossterm>
+ <glossdef>
+ <para>
+ After a temporal update or delete, the portion of history that was not
+ updated/deleted. When using ranges to track application time, there may be
+ zero, one, or two stretches of history that were not updated/deleted
+ (before and/or after the portion that was updated/deleted). New rows are
+ automatically inserted into the table to preserve that history. A single
+ multirange can accommodate the untouched history before and after the
+ update/delete, so there will be only zero or one leftover.
+ </para>
+ </glossdef>
+ </glossentry>
+
<glossentry id="glossary-temporal-table">
<glossterm>Temporal table</glossterm>
<glossdef>
diff --git a/doc/src/sgml/images/Makefile b/doc/src/sgml/images/Makefile
index fd55b9ad23f..38f8869d78d 100644
--- a/doc/src/sgml/images/Makefile
+++ b/doc/src/sgml/images/Makefile
@@ -7,7 +7,9 @@ ALL_IMAGES = \
gin.svg \
pagelayout.svg \
temporal-entities.svg \
- temporal-references.svg
+ temporal-references.svg \
+ temporal-update.svg \
+ temporal-delete.svg
DITAA = ditaa
DOT = dot
diff --git a/doc/src/sgml/images/temporal-delete.svg b/doc/src/sgml/images/temporal-delete.svg
new file mode 100644
index 00000000000..2d8b1d6ec7b
--- /dev/null
+++ b/doc/src/sgml/images/temporal-delete.svg
@@ -0,0 +1,41 @@
+<?xml version="1.0"?>
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1330 224" width="1330" height="224" shape-rendering="geometricPrecision" version="1.0">
+ <defs>
+ <filter id="f2" x="0" y="0" width="200%" height="200%">
+ <feOffset result="offOut" in="SourceGraphic" dx="5" dy="5"/>
+ <feGaussianBlur result="blurOut" in="offOut" stdDeviation="3"/>
+ <feBlend in="SourceGraphic" in2="blurOut" mode="normal"/>
+ </filter>
+ </defs>
+ <g stroke-width="1" stroke-linecap="square" stroke-linejoin="round">
+ <rect x="0" y="0" width="1330" height="224" style="fill: #ffffff"/>
+ <path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="#99dd99" d="M685.0 63.0 L685.0 147.0 L1005.0 147.0 L1005.0 63.0 z"/>
+ <path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="#99dd99" d="M315.0 63.0 L315.0 147.0 L25.0 147.0 L25.0 63.0 z"/>
+ <path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="#99dd99" d="M1005.0 63.0 L1005.0 147.0 L1275.0 147.0 L1275.0 63.0 z"/>
+ <path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="none" d="M205.0 168.0 L205.0 181.0 "/>
+ <path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="none" d="M25.0 168.0 L25.0 181.0 "/>
+ <path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="none" d="M385.0 168.0 L385.0 181.0 "/>
+ <path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="none" d="M565.0 168.0 L565.0 181.0 "/>
+ <path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="none" d="M745.0 168.0 L745.0 181.0 "/>
+ <path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="none" d="M925.0 168.0 L925.0 181.0 "/>
+ <path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="none" d="M1105.0 168.0 L1105.0 181.0 "/>
+ <path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="none" d="M1285.0 168.0 L1285.0 181.0 "/>
+ <text x="46" y="96" font-family="Courier" font-size="15" stroke="none" fill="#000000">products</text>
+ <text x="40" y="110" font-family="Courier" font-size="15" stroke="none" fill="#000000">(5, 5.00,</text>
+ <text x="83" y="124" font-family="Courier" font-size="15" stroke="none" fill="#000000">[1 Jan 2020,1 Aug 2021))</text>
+ <text x="20" y="194" font-family="Courier" font-size="15" stroke="none" fill="#000000">2020</text>
+ <text x="200" y="194" font-family="Courier" font-size="15" stroke="none" fill="#000000">2021</text>
+ <text x="380" y="194" font-family="Courier" font-size="15" stroke="none" fill="#000000">2022</text>
+ <text x="560" y="194" font-family="Courier" font-size="15" stroke="none" fill="#000000">2023</text>
+ <text x="706" y="96" font-family="Courier" font-size="15" stroke="none" fill="#000000">products</text>
+ <text x="700" y="110" font-family="Courier" font-size="15" stroke="none" fill="#000000">(5, 12.00,</text>
+ <text x="743" y="124" font-family="Courier" font-size="15" stroke="none" fill="#000000">[1 Sep 2023,1 Mar 2025))</text>
+ <text x="740" y="194" font-family="Courier" font-size="15" stroke="none" fill="#000000">2024</text>
+ <text x="920" y="194" font-family="Courier" font-size="15" stroke="none" fill="#000000">2025</text>
+ <text x="1026" y="96" font-family="Courier" font-size="15" stroke="none" fill="#000000">products</text>
+ <text x="1020" y="110" font-family="Courier" font-size="15" stroke="none" fill="#000000">(5, 8.00,</text>
+ <text x="1056" y="124" font-family="Courier" font-size="15" stroke="none" fill="#000000">[1 Mar 2025,))</text>
+ <text x="1100" y="194" font-family="Courier" font-size="15" stroke="none" fill="#000000">2026</text>
+ <text x="1289" y="194" font-family="Courier" font-size="15" stroke="none" fill="#000000">...</text>
+ </g>
+</svg>
diff --git a/doc/src/sgml/images/temporal-delete.txt b/doc/src/sgml/images/temporal-delete.txt
new file mode 100644
index 00000000000..611df521569
--- /dev/null
+++ b/doc/src/sgml/images/temporal-delete.txt
@@ -0,0 +1,12 @@
+
+
++----------------------------+ +-------------------------------+--------------------------+
+| cGRE | | cGRE | cGRE |
+| products | | products | products |
+| (5, 5.00, | | (5, 12.00, | (5, 8.00, |
+| [1 Jan 2020,1 Aug 2021)) | | [1 Sep 2023,1 Mar 2025)) | [1 Mar 2025,)) |
+| | | | |
++----------------------------+ +-------------------------------+--------------------------+
+
+| | | | | | | |
+2020 2021 2022 2023 2024 2025 2026 ...
diff --git a/doc/src/sgml/images/temporal-update.svg b/doc/src/sgml/images/temporal-update.svg
new file mode 100644
index 00000000000..6c7c43c8d22
--- /dev/null
+++ b/doc/src/sgml/images/temporal-update.svg
@@ -0,0 +1,45 @@
+<?xml version="1.0"?>
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1330 224" width="1330" height="224" shape-rendering="geometricPrecision" version="1.0">
+ <defs>
+ <filter id="f2" x="0" y="0" width="200%" height="200%">
+ <feOffset result="offOut" in="SourceGraphic" dx="5" dy="5"/>
+ <feGaussianBlur result="blurOut" in="offOut" stdDeviation="3"/>
+ <feBlend in="SourceGraphic" in2="blurOut" mode="normal"/>
+ </filter>
+ </defs>
+ <g stroke-width="1" stroke-linecap="square" stroke-linejoin="round">
+ <rect x="0" y="0" width="1330" height="224" style="fill: #ffffff"/>
+ <path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="#99dd99" d="M385.0 63.0 L385.0 147.0 L25.0 147.0 L25.0 63.0 z"/>
+ <path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="#99dd99" d="M1285.0 63.0 L1285.0 147.0 L975.0 147.0 L975.0 63.0 z"/>
+ <path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="#99dd99" d="M385.0 147.0 L685.0 147.0 L685.0 63.0 L385.0 63.0 z"/>
+ <path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="#99dd99" d="M685.0 63.0 L685.0 147.0 L975.0 147.0 L975.0 63.0 z"/>
+ <path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="none" d="M205.0 168.0 L205.0 181.0 "/>
+ <path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="none" d="M25.0 168.0 L25.0 181.0 "/>
+ <path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="none" d="M385.0 168.0 L385.0 181.0 "/>
+ <path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="none" d="M565.0 168.0 L565.0 181.0 "/>
+ <path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="none" d="M745.0 168.0 L745.0 181.0 "/>
+ <path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="none" d="M925.0 168.0 L925.0 181.0 "/>
+ <path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="none" d="M1105.0 168.0 L1105.0 181.0 "/>
+ <path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="none" d="M1285.0 168.0 L1285.0 181.0 "/>
+ <text x="46" y="96" font-family="Courier" font-size="15" stroke="none" fill="#000000">products</text>
+ <text x="40" y="110" font-family="Courier" font-size="15" stroke="none" fill="#000000">(5, 5.00,</text>
+ <text x="86" y="124" font-family="Courier" font-size="15" stroke="none" fill="#000000">[1 Jan 2020,1 Jan 2022))</text>
+ <text x="20" y="194" font-family="Courier" font-size="15" stroke="none" fill="#000000">2020</text>
+ <text x="200" y="194" font-family="Courier" font-size="15" stroke="none" fill="#000000">2021</text>
+ <text x="406" y="96" font-family="Courier" font-size="15" stroke="none" fill="#000000">products</text>
+ <text x="400" y="110" font-family="Courier" font-size="15" stroke="none" fill="#000000">(5, 8.00,</text>
+ <text x="445" y="124" font-family="Courier" font-size="15" stroke="none" fill="#000000">[1 Jan 2022,1 Sep 2023))</text>
+ <text x="380" y="194" font-family="Courier" font-size="15" stroke="none" fill="#000000">2022</text>
+ <text x="560" y="194" font-family="Courier" font-size="15" stroke="none" fill="#000000">2023</text>
+ <text x="706" y="96" font-family="Courier" font-size="15" stroke="none" fill="#000000">products</text>
+ <text x="700" y="110" font-family="Courier" font-size="15" stroke="none" fill="#000000">(5, 12.00,</text>
+ <text x="743" y="124" font-family="Courier" font-size="15" stroke="none" fill="#000000">[1 Sep 2023,1 Mar 2025))</text>
+ <text x="740" y="194" font-family="Courier" font-size="15" stroke="none" fill="#000000">2024</text>
+ <text x="920" y="194" font-family="Courier" font-size="15" stroke="none" fill="#000000">2025</text>
+ <text x="996" y="96" font-family="Courier" font-size="15" stroke="none" fill="#000000">products</text>
+ <text x="990" y="110" font-family="Courier" font-size="15" stroke="none" fill="#000000">(5, 8.00,</text>
+ <text x="1026" y="124" font-family="Courier" font-size="15" stroke="none" fill="#000000">[1 Mar 2025,))</text>
+ <text x="1100" y="194" font-family="Courier" font-size="15" stroke="none" fill="#000000">2026</text>
+ <text x="1289" y="194" font-family="Courier" font-size="15" stroke="none" fill="#000000">...</text>
+ </g>
+</svg>
diff --git a/doc/src/sgml/images/temporal-update.txt b/doc/src/sgml/images/temporal-update.txt
new file mode 100644
index 00000000000..7e862d89437
--- /dev/null
+++ b/doc/src/sgml/images/temporal-update.txt
@@ -0,0 +1,12 @@
+
+
++-----------------------------------+-----------------------------+----------------------------+------------------------------+
+| cGRE | cGRE | cGRE | cGRE |
+| products | products | products | products |
+| (5, 5.00, | (5, 8.00, | (5, 12.00, | (5, 8.00, |
+| [1 Jan 2020,1 Jan 2022)) | [1 Jan 2022,1 Sep 2023)) | [1 Sep 2023,1 Mar 2025)) | [1 Mar 2025,)) |
+| | | | |
++-----------------------------------+-----------------------------+----------------------------+------------------------------+
+
+| | | | | | | |
+2020 2021 2022 2023 2024 2025 2026 ...
--
2.39.5