{"id":55000513,"date":"2026-06-01T00:00:00","date_gmt":"2026-07-28T15:07:28","guid":{"rendered":"http:\/\/access-im-unternehmen.aix-dev.de\/aiu\/?p=513"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-30T00:00:00","slug":"DatenbankZeitreisen_Temporal_Tables_im_SQL_Server","status":"publish","type":"post","link":"https:\/\/vbentwickler.de\/DatenbankZeitreisen_Temporal_Tables_im_SQL_Server\/","title":{"rendered":"Datenbank-Zeitreisen: Temporal Tables im SQL Server"},"content":{"rendered":"<p><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/vg08.met.vgwort.de\/na\/ac79efb76c9c46dd97e46ce1c732f7e0\" width=\"1\" height=\"1\" alt=\"\"><b>Wer hat den Preis ge&auml;ndert, und wie hoch war er eigentlich vor drei Wochen? Wann ist dieser Datensatz gel&ouml;scht worden, und wie sah er kurz davor aus? Sobald eine Datenbank produktiv l&auml;uft, tauchen solche Fragen auf &#8211; und oft steht man mit leeren H&auml;nden da, weil die alten Werte l&auml;ngst &uuml;berschrieben sind. Der klassische Ausweg ist eine selbstgebaute Historientabelle samt Triggern, die bei jedem <b>INSERT<\/b>, <b>UPDATE<\/b> und <b>DELETE<\/b> die alte Zeile wegschreiben. Das funktioniert, ist aber Handarbeit. Der SQL Server bringt seit der Version 2016 eine eingebaute L&ouml;sung mit: <b>System-Versioned Temporal Tables<\/b>. Die Datenbank f&uuml;hrt die komplette &Auml;nderungshistorie vollautomatisch mit, und Abfragen zu einem beliebigen Zeitpunkt der Vergangenheit sind eine Zeile Zusatz-<b>SQL<\/b>. Wie das gelingt, wie und wo die alten Daten landen und wie Du eine bestehende Tabelle nachtr&auml;glich versionierst, liest Du in diesem Beitrag &#8211; und auch, was Temporal Tables nicht k&ouml;nnen.<\/b><\/p>\n<h2>Was eine Temporal Table eigentlich ist<\/h2>\n<p>Eine systemversionierte temporale Tabelle besteht immer aus einem Paar: der aktuellen Tabelle, die &#8211; wie gewohnt &#8211; den jeweils g&uuml;ltigen Stand jeder Zeile enth&auml;lt, und einer zugeh&ouml;rigen Historientabelle, in der jede fr&uuml;here Version einer Zeile aufbewahrt wird. Beide zusammen ergeben eine l&uuml;ckenlose Zeitleiste.<\/p>\n<p>Damit der SQL Server wei&szlig;, ab wann und bis wann eine bestimmte Version einer Zeile g&uuml;ltig war, braucht die Tabelle zwei zus&auml;tzliche Spalten vom Typ <b>DATETIME2<\/b>, die sogenannten Periodenspalten. Die eine h&auml;lt den Startzeitpunkt (im Beispiel gleich <b>GueltigVon<\/b>), die andere den Endzeitpunkt (<b>GueltigBis<\/b>).<\/p>\n<p>Solange eine Zeile aktuell ist, steht in <b>GueltigBis<\/b> der Maximalwert <b>9999-12-31 23:59:59.9999999<\/b> &#8211; der SQL Server liest das als &#8220;gilt bis auf Weiteres&#8221;.<\/p>\n<p>&Auml;ndern wir die Zeile, passiert im Hintergrund Folgendes: Die bisherige Version wandert mit ihrem echten Endzeitpunkt in die Historientabelle, und in der aktuellen Tabelle steht die neue Version mit frischem Startzeitpunkt. Wir m&uuml;ssen daf&uuml;r nichts programmieren &#8211; kein Trigger, keine Prozedur.<\/p>\n<p>Ein Punkt vorweg, der sp&auml;ter noch wichtig wird: Der SQL Server speichert diese Zeitstempel in <b>UTC<\/b>, nicht in lokaler Zeit.<\/p>\n<p>F&uuml;r uns in Deutschland hei&szlig;t das ein bis zwei Stunden Versatz zur Uhr an der Wand. Darauf kommen wir bei den Abfragen zur&uuml;ck.<\/p>\n<h2>Was Temporal Tables nicht k&ouml;nnen<\/h2>\n<p>Temporal Tables erstellen nur eine Kopie eines ge&auml;nderten oder gel&ouml;schten Datensatzes und tragen ein, von wann bis wann dieser Datensatz g&uuml;ltig war. Wir k&ouml;nnen damit nicht automatisch eine Information hinterlegen, wer die neue Version des Datensatzes angelegt hat.<\/p>\n<p>Es wird f&uuml;r eine historisierte Version eines Datensatze auch nicht eingetragen, ob dieser Datensatz durch einen ge&auml;nderten Datensatz ersetzt oder ob dieser gel&ouml;scht wurde &#8211; es wird lediglich jede ge&auml;nderte oder gel&ouml;schte Version eines Datensatzes gespeichert.<\/p>\n<h2>Die Beispieldatenbank anlegen<\/h2>\n<p>Bauen wir zun&auml;chst eine ganz normale Tabelle ohne jede Versionierung auf und f&uuml;llen sie mit ein paar Datens&auml;tzen &#8211; so, wie eine bestehende Anwendung sie mitbringen k&ouml;nnte.<\/p>\n<p>Erst im n&auml;chsten Schritt r&uuml;sten wir die Historie nach. Das entspricht dem h&auml;ufigsten Fall aus der Praxis: Die Tabelle ist l&auml;ngst da, die Historie soll dazu.<\/p>\n<p>Das erledigt Listing 1. Es legt die Datenbank <b>TemporalDemo<\/b> an, darin die Tabelle <b>tblProdukte<\/b> mit den Feldern <b>ProduktID<\/b>, <b>Bezeichnung<\/b> und <b>Preis<\/b> &#8211; und f&uuml;llt drei Zeilen ein.<\/p>\n<pre>CREATE DATABASE TemporalDemo;\r\nGO\r\nUSE TemporalDemo;\r\nGO\r\nCREATE TABLE dbo.tblProdukte\r\n(\r\n    ProduktID   INT IDENTITY(1,1) NOT NULL CONSTRAINT PK_Produkt PRIMARY KEY CLUSTERED,\r\n    Bezeichnung NVARCHAR(100) NOT NULL,\r\n    Preis       DECIMAL(10,2) NOT NULL\r\n);\r\nGO\r\nINSERT INTO dbo.tblProdukte (Bezeichnung, Preis)\r\nVALUES (N'Kaffeebohnen Brasil',   12.90),\r\n       (N'Teekanne Gusseisen',    34.50),\r\n       (N'Espressotassen-Set',    19.99);\r\nGO<\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 1: Beispieldatenbank und Tabelle tblProdukte anlegen und mit Daten f&uuml;llen<\/span><\/b><\/p>\n<p>Ein Detail ist keine K&uuml;r, sondern Voraussetzung f&uuml;r alles Weitere: Die Tabelle <b>tblProdukte<\/b> hat einen Prim&auml;rschl&uuml;ssel (<b>PK_Produkt<\/b> auf <b>ProduktID<\/b>). Ohne Prim&auml;rschl&uuml;ssel l&auml;sst sich sp&auml;ter keine Systemversionierung einschalten &#8211; der SQL Server braucht ihn, um eine Zeile &uuml;ber ihre Versionen hinweg eindeutig zu verketten. Wer hier eine Tabelle ohne Schl&uuml;ssel hat, muss also zuerst einen nachr&uuml;sten.<\/p>\n<h2>Versionierung an einer bestehenden Tabelle nachr&uuml;sten<\/h2>\n<p>Jetzt kommt der eigentliche Schritt: Wir erweitern die bestehende Tabelle <b>tblProdukte<\/b> um die beiden Periodenspalten und schalten die Systemversionierung ein.<\/p>\n<p>Das geschieht in Listing 2 in zwei getrennten <b>ALTER TABLE<\/b>-Anweisungen &#8211; erst die Spalten und die Periode, dann die Versionierung selbst.<\/p>\n<pre>USE TemporalDemo;\r\nGO\r\nALTER TABLE dbo.tblProdukte ADD\r\n    GueltigVon DATETIME2(7) GENERATED ALWAYS AS ROW START NOT NULL\r\n        CONSTRAINT DF_Produkt_GueltigVon DEFAULT SYSUTCDATETIME(),\r\n    GueltigBis DATETIME2(7) GENERATED ALWAYS AS ROW END NOT NULL\r\n        CONSTRAINT DF_Produkt_GueltigBis\r\n        DEFAULT CONVERT(DATETIME2(7), '9999-12-31 23:59:59.9999999'),\r\n    PERIOD FOR SYSTEM_TIME (GueltigVon, GueltigBis);\r\nGO\r\nALTER TABLE dbo.tblProdukte\r\n    SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.tblProdukteHistory));\r\nGO<\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 2: Die bestehende Tabelle tblProdukte um Periodenspalten erweitern und die Systemversionierung einschalten<\/span><\/b><\/p>\n<p>Das ist zugleich das allgemeine Rezept f&uuml;r jede Tabelle, die es in Deiner Datenbank schon gibt &#8211; einzige harte Voraussetzung bleibt der Prim&auml;rschl&uuml;ssel aus dem vorigen Abschnitt.<\/p>\n<p>Gehen wir das durch, denn hier lauern gleich mehrere Stolpersteine. Die beiden neuen Spalten sind mit <b>GENERATED ALWAYS AS ROW START<\/b> bzw. <b>ROW END<\/b> gekennzeichnet &#8211; das sagt dem SQL Server, dass er diese Werte setzt und wir sie niemals von Hand bef&uuml;llen.<\/p>\n<p>Genau daraus ergibt sich das erste Problem: Die Spalten sind <b>NOT NULL<\/b>, aber unsere Tabelle enth&auml;lt bereits Daten. Woher soll der SQL Server die Zeitstempel f&uuml;r die drei vorhandenen Zeilen nehmen? Wir w&uuml;rden die Werte nun gern selbst eintragen &#8211; aber das ist bei <b>GENERATED ALWAYS<\/b>-Spalten nicht m&ouml;glich.<\/p>\n<p>Die L&ouml;sung sind die beiden <b>DEFAULT<\/b>-Constraints: Sie liefern die Startwerte f&uuml;r die schon vorhandenen Zeilen. Ohne diese Defaults bricht das <b>ALTER TABLE<\/b> bei einer gef&uuml;llten Tabelle mit einer Fehlermeldung ab.<\/p>\n<p>Der zweite Stolperstein steckt in der Wahl dieser Defaults. F&uuml;r <b>GueltigVon<\/b> nehmen wir <b>SYSUTCDATETIME()<\/b>, also den aktuellen Zeitpunkt in <b>UTC<\/b>. F&uuml;r <b>GueltigBis<\/b> muss es zwingend der Maximalwert <b>9999-12-31 23:59:59.9999999<\/b> sein &#8211; nicht etwa ebenfalls die aktuelle Zeit.<\/p>\n<p>Der Endzeitpunkt markiert bei einer aktuellen Zeile das &#8220;gilt bis auf Weiteres&#8221;, und er muss gr&ouml;&szlig;er sein als der Startzeitpunkt. Wer hier versehentlich auch <b>SYSUTCDATETIME()<\/b> einsetzt, erntet einen Fehler.<\/p>\n<p>Und der dritte Punkt ist weniger ein Fehler als eine ehrliche Einordnung: Die Historie beginnt in dem Moment, in dem wir die Versionierung einschalten. Unsere drei Bestandszeilen bekommen alle denselben Startzeitpunkt &#8211; den der Umstellung -, nicht ihr echtes Anlagedatum. R&uuml;ckwirkend entsteht keine Historie, die vorher nie aufgezeichnet wurde. Das ist keine Schw&auml;che des Verfahrens, aber gut zu wissen, bevor man sich &uuml;ber gleiche Zeitstempel wundert.<\/p>\n<p>Die zweite Anweisung schaltet mit <b>SET (SYSTEM_VERSIONING = ON &#8230;)<\/b> die Versionierung scharf und legt dabei die Historientabelle <b>dbo.tblProdukteHistory<\/b> an. Den Namen geben wir bewusst selbst an. L&auml;sst man ihn weg, erzeugt der SQL Server eine automatisch benannte Tabelle nach dem Muster <b>MSSQL_TemporalHistoryFor_&#8230;<\/b> samt Objekt-ID &#8211; funktioniert, ist aber unhandlich.<\/p>\n<p>Im Objekt-Explorer des SQL Server Management Studios erkennst Du das Ergebnis sofort: Die Tabelle <b>tblProdukte<\/b> tr&auml;gt jetzt ein kleines Uhr-Symbol, und darunter h&auml;ngt als Unterknoten die zugeh&ouml;rige <b>tblProdukteHistory<\/b> (siehe Bild 1). Im Kontextmen&uuml; sehen wir auch gleich, dass wir die Datens&auml;tze dieser Tabelle nicht bearbeiten k&ouml;nnen &#8211; es fehlt der Eintrag <b>Oberste 200 Eintr&auml;ge bearbeiten<\/b>.<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2026_03\/pic_513_001.png\" alt=\"Der Objekt-Explorer zeigt die versionierte Tabelle Produkt mit Uhr-Symbol und der einger&uuml;ckten Historientabelle ProduktHistory\" width=\"549,6265\" height=\"602,3165\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 1: Der Objekt-Explorer zeigt die versionierte Tabelle Produkt mit Uhr-Symbol und der einger&uuml;ckten Historientabelle ProduktHistory<\/span><\/b><\/p>\n<p>Das war der Weg f&uuml;r eine Tabelle, die es schon gibt &#8211; der h&auml;ufigste Fall in der Praxis. Steht die Tabelle dagegen noch gar nicht, gibt es einen k&uuml;rzeren Weg, den wir uns weiter unten im Abschnitt zum direkten Anlegen ansehen.<\/p>\n<h2>Wo die alten Daten landen<\/h2>\n<p>Werfen wir einen Blick auf die Verteilung, bevor &uuml;berhaupt etwas ge&auml;ndert wurde. Die aktuelle Tabelle <b>tblProdukte<\/b> enth&auml;lt unsere drei Zeilen, die Historientabelle <b>tblProdukteHistory<\/b> ist noch leer &#8211; logisch, denn es gibt ja noch keine fr&uuml;heren Versionen. Erst eine &Auml;nderung f&uuml;llt die Historie.<\/p>\n<p>Wichtig ist die Rollenverteilung: In der aktuellen Tabelle steht immer nur der jeweils g&uuml;ltige Stand. Jede &uuml;berschriebene oder gel&ouml;schte Version zieht in die Historientabelle um.<\/p>\n<p>Diese ist eine ganz normale Tabelle, die Du direkt abfragen kannst &#8211; nur &auml;ndern darfst Du sie nicht, solange die Versionierung eingeschaltet ist. Der SQL Server verwaltet ihren Inhalt allein.<\/p>\n<p>Und noch einmal der Hinweis auf die Zeitzone, weil er gleich praktisch wird: Die Werte in <b>GueltigVon<\/b> und <b>GueltigBis<\/b> stehen in <b>UTC<\/b>. Ein Datensatz, der um 14:00 Uhr deutscher Sommerzeit ge&auml;ndert wird, tr&auml;gt in der Historie den Zeitstempel 12:00 Uhr.<\/p>\n<h2>Daten ver&auml;ndern und die Historie beobachten<\/h2>\n<p>Damit die Historie etwas zu zeigen hat, spielen wir ein kleines Szenario durch: Der Kaffee wird teurer, die Teekanne kommt in den Ausverkauf, und das Espressotassen-Set fliegt aus dem Sortiment.<\/p>\n<p>Listing 3 setzt genau das um. Die <b>WAITFOR DELAY<\/b>-Zeilen sind nur f&uuml;r unser Beispiel gedacht: Sie sorgen daf&uuml;r, dass die drei &Auml;nderungen deutlich auseinanderliegende Zeitstempel bekommen, damit die Zeitpunkt-Abfragen gleich anschaulich werden. Im echten Betrieb entstehen diese Abst&auml;nde von selbst.<\/p>\n<pre>USE TemporalDemo;\r\nGO\r\nUPDATE dbo.tblProdukte SET Preis = 13.90 WHERE Bezeichnung = N'Kaffeebohnen Brasil';\r\nWAITFOR DELAY '00:00:03';\r\nUPDATE dbo.tblProdukte SET Preis = 29.90 WHERE Bezeichnung = N'Teekanne Gusseisen';\r\nWAITFOR DELAY '00:00:03';\r\nDELETE FROM dbo.tblProdukte WHERE Bezeichnung = N'Espressotassen-Set';\r\nGO<\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 3: Datens&auml;tze &auml;ndern und l&ouml;schen, damit die Historie sich f&uuml;llt<\/span><\/b><\/p>\n<p>Nach diesem Durchlauf zeigt eine gew&ouml;hnliche Abfrage auf <b>tblProdukte<\/b> nur noch zwei Zeilen &#8211; den Kaffee zu <b>13,90<\/b> und die Teekanne zu <b>29,90<\/b>. Das Espressotassen-Set ist verschwunden. Aus der aktuellen Tabelle, wohlgemerkt: In der Historie lebt es weiter.<\/p>\n<h2>Abfragen zu einem Zeitpunkt mit FOR SYSTEM_TIME<\/h2>\n<p>Jetzt kommt der Lohn der M&uuml;he. Um an die Historie zu kommen, h&auml;ngen wir an die Tabelle die Klausel <b>FOR SYSTEM_TIME<\/b> an. Verschaffen wir uns zuerst einen Gesamt&uuml;berblick &uuml;ber alle Versionen &#8211; aktuelle und historische zusammen &#8211; mit <b>FOR SYSTEM_TIME ALL<\/b>. Das zeigt Listing 4.<\/p>\n<pre>USE TemporalDemo;\r\nGO\r\n-- Nur der aktuelle Stand (ganz normale Abfrage):\r\nSELECT ProduktID, Bezeichnung, Preis, GueltigVon, GueltigBis\r\nFROM dbo.tblProdukte\r\nORDER BY ProduktID;\r\n-- Alle Versionen, aktuell und historisch:\r\nSELECT ProduktID, Bezeichnung, Preis, GueltigVon, GueltigBis\r\nFROM dbo.tblProdukte FOR SYSTEM_TIME ALL\r\nORDER BY ProduktID, GueltigVon;\r\nGO<\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 4: Aktueller Stand und komplette Zeitleiste mit FOR SYSTEM_TIME ALL<\/span><\/b><\/p>\n<p>Die zweite Abfrage liefert die vollst&auml;ndige Zeitleiste: F&uuml;r den Kaffee erscheinen zwei Zeilen (<b>12,90<\/b> mit einem geschlossenen G&uuml;ltigkeitsfenster, <b>13,90<\/b> mit offenem Ende <b>9999-12-31 &#8230;<\/b>), f&uuml;r die Teekanne ebenso, und das gel&ouml;schte Espressotassen-Set taucht mit einem abgeschlossenen Fenster wieder auf &#8211; obwohl es in der aktuellen Tabelle gar nicht mehr existiert. An den Werten in <b>GueltigVon<\/b> und <b>GueltigBis<\/b> l&auml;sst sich ablesen, welche Version wann galt (siehe Bild 2).<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2026_03\/pic_513_002.png\" alt=\"FOR SYSTEM_TIME ALL zeigt zu jedem Produkt s&auml;mtliche Versionen mit ihren G&uuml;ltigkeitsfenstern in den Spalten GueltigVon und GueltigBis\" width=\"649,627\" height=\"365,9869\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 2: FOR SYSTEM_TIME ALL zeigt zu jedem Produkt s&auml;mtliche Versionen mit ihren G&uuml;ltigkeitsfenstern in den Spalten GueltigVon und GueltigBis<\/span><\/b><\/p>\n<p>F&uuml;r die eigentliche Zeitpunkt-Frage &#8211; &#8220;Wie sah das Sortiment zu einem bestimmten Moment aus?&#8221; &#8211; gibt es <b>FOR SYSTEM_TIME AS OF<\/b>. Der SQL Server liefert dann von jeder Zeile genau die Version zur&uuml;ck, die zum angegebenen Zeitpunkt g&uuml;ltig war. Listing 5 zeigt die Varianten.<\/p>\n<pre>USE TemporalDemo;\r\nGO\r\n-- Stand zu einem bestimmten Zeitpunkt (Wert in UTC!):\r\nSELECT ProduktID, Bezeichnung, Preis\r\nFROM dbo.tblProdukte\r\nFOR SYSTEM_TIME AS OF '2026-07-22 09:00:00'\r\nORDER BY ProduktID;\r\n-- Alle Versionen, die in einem Zeitraum aktiv waren:\r\nSELECT ProduktID, Bezeichnung, Preis, GueltigVon, GueltigBis\r\nFROM dbo.tblProdukte\r\nFOR SYSTEM_TIME BETWEEN '2026-07-01' AND '2026-07-31'\r\nORDER BY ProduktID, GueltigVon;\r\n-- Nur Versionen, die komplett innerhalb des Zeitraums liegen:\r\nSELECT ProduktID, Bezeichnung, Preis, GueltigVon, GueltigBis\r\nFROM dbo.tblProdukte\r\nFOR SYSTEM_TIME CONTAINED IN ('2026-07-01', '2026-07-31')\r\nORDER BY ProduktID, GueltigVon;\r\nGO<\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 5: Abfragen zu einem Zeitpunkt und &uuml;ber einen Zeitraum mit FOR SYSTEM_TIME<\/span><\/b><\/p>\n<p>Der Zeitpunkt in der <b>AS OF<\/b>-Abfrage ist frei erfunden &#8211; Deine eigenen Zeitstempel liegen anders, je nachdem, wann Du die Listings ausf&uuml;hrst.<\/p>\n<p>Am einfachsten nimmst Du einen der Werte, die Dir die <b>ALL<\/b>-Abfrage aus dem vorherigen Listing in <b>GueltigVon<\/b> oder <b>GueltigBis<\/b> angezeigt hat, und setzt einen Moment davor oder dazwischen ein.<\/p>\n<p>Fragst Du einen Zeitpunkt vor allen &Auml;nderungen ab, liefert der SQL Server den Ursprungsstand &#8211; Kaffee zu <b>12,90<\/b>, Teekanne zu <b>34,50<\/b> und das Espressotassen-Set noch im Sortiment (siehe Bild 3).<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2026_03\/pic_513_003.png\" alt=\"Dieselbe Tabelle, per FOR SYSTEM_TIME AS OF zu einem Zeitpunkt vor den &Auml;nderungen abgefragt - die alten Preise und das gel&ouml;schte Produkt sind wieder da\" width=\"549,6265\" height=\"324,7296\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 3: Dieselbe Tabelle, per FOR SYSTEM_TIME AS OF zu einem Zeitpunkt vor den &Auml;nderungen abgefragt &#8211; die alten Preise und das gel&ouml;schte Produkt sind wieder da<\/span><\/b><\/p>\n<p>Und hier schl&auml;gt der eingangs erw&auml;hnte Zeitzonen-Stolperstein zu: Der Wert hinter <b>AS OF<\/b> wird als <b>UTC<\/b> interpretiert. Fragst Du im deutschen Sommer <b>AS OF &#8216;2026-07-22 14:00:00&#8217;<\/b> ab, meinst Du vielleicht 14 Uhr Ortszeit &#8211; der SQL Server versteht aber 14 Uhr UTC, also 16 Uhr bei uns.<\/p>\n<p>Zwei Stunden daneben, und schon bekommst Du eine andere Version zur&uuml;ck oder gar keine. Wer sauber arbeiten will, denkt bei diesen Abfragen konsequent in UTC oder rechnet den Versatz bewusst heraus.<\/p>\n<p>Neben <b>AS OF<\/b>, <b>BETWEEN &#8230; AND<\/b> und <b>CONTAINED IN<\/b> gibt es noch <b>FROM &#8230; TO<\/b> mit leicht anderem Randverhalten an den Intervallgrenzen; f&uuml;r den Alltag sind <b>AS OF<\/b> (ein Zeitpunkt) und <b>ALL<\/b> (die ganze Geschichte) die beiden Arbeitspferde.<\/p>\n<h2>Und wenn die Tabelle noch gar nicht existiert?<\/h2>\n<p>Bisher sind wir den Nachr&uuml;st-Weg gegangen: erst die fertige Tabelle <b>tblProdukte<\/b>, dann die Versionierung obendrauf.<\/p>\n<p>Das ist der h&auml;ufigste Fall, weil die Tabellen meist schon da sind. Legst Du eine Tabelle dagegen frisch an, sparst Du Dir das zweistufige <b>ALTER TABLE<\/b> und machst sie gleich beim Anlegen temporal. Die Periodenspalten und die <b>WITH (SYSTEM_VERSIONING = ON &#8230;)<\/b>-Klausel wandern dann direkt ins <b>CREATE TABLE<\/b>, wie Listing 6 an einer neuen Tabelle <b>tblKunden<\/b> zeigt.<\/p>\n<pre>USE TemporalDemo;\r\nGO\r\nCREATE TABLE dbo.tblKunden\r\n(\r\n    KundeID    INT IDENTITY(1,1) NOT NULL\r\n               CONSTRAINT PK_Kunde PRIMARY KEY CLUSTERED,\r\n    Name       NVARCHAR(100) NOT NULL,\r\n    Ort        NVARCHAR(100) NULL,\r\n    GueltigVon DATETIME2(7) GENERATED ALWAYS AS ROW START NOT NULL,\r\n    GueltigBis DATETIME2(7) GENERATED ALWAYS AS ROW END NOT NULL,\r\n    PERIOD FOR SYSTEM_TIME (GueltigVon, GueltigBis)\r\n)\r\nWITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.tblKundenHistory));\r\nGO<\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 6: Die neue Tabelle tblKunden gleich temporal anlegen<\/span><\/b><\/p>\n<p>Der Unterschied zum Nachr&uuml;sten f&auml;llt angenehm aus: Weil die Tabelle <b>tblKunden<\/b> von Anfang an leer ist, brauchen wir keine <b>DEFAULT<\/b>-Constraints f&uuml;r vorhandene Zeilen &#8211; die beiden Stolpersteine von vorne entfallen komplett.<\/p>\n<p>Die Periodenspalten stehen einfach mit in der Spaltenliste, und die <b>WITH<\/b>-Klausel schaltet die Versionierung im selben Rutsch ein. Alles &Uuml;brige &#8211; die Historientabelle <b>tblKundenHistory<\/b>, das automatische Wegschreiben, die Abfragen mit <b>FOR SYSTEM_TIME<\/b> &#8211; funktioniert danach genau wie bei <b>tblProdukte<\/b>.<\/p>\n<h2>Feinschliff: HIDDEN und Versionierung abschalten<\/h2>\n<p>Die beiden Periodenspalten sind im Alltag selten interessant &#8211; bei einem gewohnten <b>SELECT *<\/b> liefern sie nur zwei technische Zeitstempel mit, nach denen niemand gefragt hat. Der SQL Server kann sie deshalb verbergen: Das Schl&uuml;sselwort <b>HIDDEN<\/b> blendet eine Periodenspalte aus, ohne sie abzuschaffen.<\/p>\n<p>Sie bleibt voll funktionsf&auml;hig, verschwindet aber aus dem Ergebnis von <b>SELECT *<\/b> und taucht nur noch auf, wenn wir sie ausdr&uuml;cklich benennen.<\/p>\n<p>An unserer bestehenden Tabelle <b>tblProdukte<\/b> schalten wir das mit <b>ALTER COLUMN &#8230; ADD HIDDEN<\/b> nachtr&auml;glich ein. Das zeigt Listing 7 samt einem Vorher-Nachher, an dem der Effekt sichtbar wird.<\/p>\n<pre>USE TemporalDemo;\r\nGO\r\nALTER TABLE dbo.Produkt ALTER COLUMN GueltigVon ADD HIDDEN;\r\nALTER TABLE dbo.Produkt ALTER COLUMN GueltigBis ADD HIDDEN;\r\nGO\r\n-- Liefert jetzt nur noch ProduktID, Bezeichnung und Preis:\r\nSELECT * FROM dbo.Produkt;\r\n-- Die Periodenspalten sind weiterhin da - nur auf ausdr&uuml;ckliche Anfrage:\r\nSELECT ProduktID, Bezeichnung, Preis, GueltigVon, GueltigBis\r\nFROM dbo.Produkt;\r\nGO<\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 7: Die Periodenspalten mit HIDDEN aus SELECT * ausblenden<\/span><\/b><\/p>\n<p>Nach dem Ausf&uuml;hren f&auml;llt der Unterschied sofort auf: Das erste <b>SELECT *<\/b> gibt nur noch die drei fachlichen Spalten <b>ProduktID<\/b>, <b>Bezeichnung<\/b> und <b>Preis<\/b> zur&uuml;ck, w&auml;hrend die zweite Abfrage die Zeitstempel klaglos weiterliefert. Die Daten sind also nicht weg, sondern nur aus dem Weg. M&ouml;chtest Du die Spalten wieder einblenden, drehst Du das mit <b>DROP HIDDEN<\/b> um:<\/p>\n<pre>ALTER TABLE dbo.Produkt ALTER COLUMN GueltigVon DROP HIDDEN;<\/pre>\n<p>Wer eine Tabelle ohnehin neu anlegt, spart sich diesen Umweg und schreibt <b>HIDDEN<\/b> direkt hinter <b>ROW START<\/b> beziehungsweise <b>ROW END<\/b> in die Spaltendefinition. Allerdings eine ehrliche Einordnung: <b>SELECT *<\/b> ist im Produktivcode ohnehin verp&ouml;nt, und so ist <b>HIDDEN<\/b> eher Komfort f&uuml;r die interaktive Arbeit in SSMS als eine echte Notwendigkeit. Bleibt der Fall, dass Du am Schema etwas &auml;ndern musst, das der SQL Server bei eingeschalteter Versionierung blockiert. Dann schaltest Du sie kurz ab:<\/p>\n<pre>ALTER TABLE dbo.Produkt SET (SYSTEM_VERSIONING = OFF);<\/pre>\n<p>Danach sind <b>tblProdukte<\/b> und <b>tblProdukteHistory <\/b>wieder zwei ganz normale, entkoppelte Tabellen &#8211; die Historie bleibt erhalten, wird aber bis zum Wiedereinschalten nicht mehr fortgeschrieben. Anschlie&szlig;end schaltest Du mit der <b>ON<\/b>-Variante wieder ein. Genau so l&ouml;scht man eine temporale Tabelle auch: erst <b>OFF<\/b>, dann beide Tabellen einzeln entfernen.<\/p>\n<h2>Zusammenfassung und Ausblick<\/h2>\n<p>Systemversionierte temporale Tabellen nehmen uns die komplette Handarbeit der Historienf&uuml;hrung ab. Wir haben eine bestehende Tabelle <b>tblProdukte<\/b> um zwei Periodenspalten erweitert, die Versionierung mit einer zugeh&ouml;rigen Historientabelle <b>tblProdukteHistory<\/b> eingeschaltet und gesehen, wie der SQL Server bei jeder &Auml;nderung die alte Version automatisch wegschreibt.<\/p>\n<p>Dabei haben wir beide Wege gesehen: das Nachr&uuml;sten an einer schon vorhandenen Tabelle per <b>ALTER TABLE<\/b> und das direkte temporale Anlegen einer neuen Tabelle im <b>CREATE TABLE<\/b>. Mit <b>FOR SYSTEM_TIME<\/b> &#8211; vor allem <b>AS OF<\/b> f&uuml;r einen Zeitpunkt und <b>ALL<\/b> f&uuml;r die ganze Zeitleiste &#8211; l&auml;sst sich der Datenbestand zu jedem vergangenen Moment abfragen, und mit <b>HIDDEN<\/b> halten wir die technischen Periodenspalten bei Bedarf aus dem Blick.<\/p>\n<p>Die beiden wichtigsten Fallstricke: der Prim&auml;rschl&uuml;ssel als Voraussetzung und die <b>UTC<\/b>-Speicherung der Zeitstempel.<\/p>\n<p>Damit sind einige Themen nur angerissen, die eigene Beitr&auml;ge verdienen. Wie h&auml;lt man die Historientabelle langfristig klein, ohne von Hand aufzur&auml;umen? Der SQL Server kann Verlaufsdaten &uuml;ber einen <b>HISTORY_RETENTION_PERIOD<\/b> automatisch nach Ablauf l&ouml;schen &#8211; ein Folgeartikel wert.<\/p>\n<p>Ebenso spannend: Wie greift man aus einem Access-Frontend auf diese Historie zu und zeigt sie in einem Formular oder Bericht an &#8211; hier verbinden sich die Access- und die SQL-Welt auf nat&uuml;rliche Weise. Und wer es f&auml;lschungssicher braucht, findet in den <b>Ledger Tables<\/b> der neueren SQL-Server-Versionen die konsequente Weiterentwicklung des Gedankens. Wie Du die Historie aus Access heraus nutzbar machst, zeigt einer der n&auml;chsten Beitr&auml;ge.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Wer hat den Preis ge&auml;ndert, und wie hoch war er eigentlich vor drei Wochen? Wann ist dieser Datensatz gel&ouml;scht worden, und wie sah er kurz davor aus? Sobald eine Datenbank produktiv l&auml;uft, tauchen solche Fragen auf &#8211; und oft steht man mit leeren H&auml;nden da, weil die alten Werte l&auml;ngst &uuml;berschrieben sind. Der klassische Ausweg ist eine selbstgebaute Historientabelle samt Triggern, die bei jedem INSERT, UPDATE und DELETE die alte Zeile wegschreiben. Das funktioniert, ist aber Handarbeit. Der SQL Server bringt seit der Version 2016 eine eingebaute L&ouml;sung mit: System-Versioned Temporal Tables. Die Datenbank f&uuml;hrt die komplette &Auml;nderungshistorie vollautomatisch mit, und Abfragen zu einem beliebigen Zeitpunkt der Vergangenheit sind eine Zeile Zusatz-SQL. Wie das gelingt, wie und wo die alten Daten landen und wie Du eine bestehende Tabelle nachtr&auml;glich versionierst, liest Du in diesem Beitrag &#8211; und auch, was Temporal Tables nicht k&ouml;nnen.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"om_disable_all_campaigns":false,"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"_uf_show_specific_survey":0,"_uf_disable_surveys":false,"footnotes":""},"categories":[662026,66032026,44000006],"tags":[],"yst_prominent_words":[],"class_list":["post-55000513","post","type-post","status-publish","format-standard","hentry","category-662026","category-66032026","category-SQL_Server_und_Co"],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 4.9.10 - aioseo.com -->\n\t<meta name=\"description\" content=\"Wer hat den Preis ge\u00e4ndert, und wie hoch war er eigentlich vor drei Wochen? Wann ist dieser Datensatz gel\u00f6scht worden, und wie sah er kurz davor aus? Sobald eine Datenbank produktiv l\u00e4uft, tauchen solche Fragen auf - und oft steht man mit leeren H\u00e4nden da, weil die alten Werte l\u00e4ngst \u00fcberschrieben sind. Der klassische Ausweg ist eine selbstgebaute Historientabelle samt Triggern, die bei jedem INSERT, UPDATE und DELETE die alte Zeile wegschreiben. Das funktioniert, ist aber Handarbeit. Der SQL Server bringt seit der Version 2016 eine eingebaute L\u00f6sung mit: System-Versioned Temporal Tables. Die Datenbank f\u00fchrt die komplette \u00c4nderungshistorie vollautomatisch mit, und Abfragen zu einem beliebigen Zeitpunkt der Vergangenheit sind eine Zeile Zusatz-SQL. Wie das gelingt, wie und wo die alten Daten landen und wie Du eine bestehende Tabelle nachtr\u00e4glich versionierst, liest Du in diesem Beitrag - und auch, was Temporal Tables nicht k\u00f6nnen.\" \/>\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"author\" content=\"Andr\u00e9 Minhorst\"\/>\n\t<link rel=\"canonical\" href=\"https:\/\/vbentwickler.de\/DatenbankZeitreisen_Temporal_Tables_im_SQL_Server\/\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 4.9.10\" \/>\n\t\t<meta property=\"og:locale\" content=\"de_DE\" \/>\n\t\t<meta property=\"og:site_name\" content=\"Visual Basic Entwickler - Das Magazin f\u00fcr Datenbankentwickler auf Basis von Visual Studio und Co.\" \/>\n\t\t<meta property=\"og:type\" content=\"article\" \/>\n\t\t<meta property=\"og:title\" content=\"Datenbank-Zeitreisen: Temporal Tables im SQL Server - Visual Basic Entwickler\" \/>\n\t\t<meta property=\"og:description\" content=\"Wer hat den Preis ge\u00e4ndert, und wie hoch war er eigentlich vor drei Wochen? Wann ist dieser Datensatz gel\u00f6scht worden, und wie sah er kurz davor aus? Sobald eine Datenbank produktiv l\u00e4uft, tauchen solche Fragen auf - und oft steht man mit leeren H\u00e4nden da, weil die alten Werte l\u00e4ngst \u00fcberschrieben sind. Der klassische Ausweg ist eine selbstgebaute Historientabelle samt Triggern, die bei jedem INSERT, UPDATE und DELETE die alte Zeile wegschreiben. Das funktioniert, ist aber Handarbeit. Der SQL Server bringt seit der Version 2016 eine eingebaute L\u00f6sung mit: System-Versioned Temporal Tables. Die Datenbank f\u00fchrt die komplette \u00c4nderungshistorie vollautomatisch mit, und Abfragen zu einem beliebigen Zeitpunkt der Vergangenheit sind eine Zeile Zusatz-SQL. Wie das gelingt, wie und wo die alten Daten landen und wie Du eine bestehende Tabelle nachtr\u00e4glich versionierst, liest Du in diesem Beitrag - und auch, was Temporal Tables nicht k\u00f6nnen.\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/vbentwickler.de\/DatenbankZeitreisen_Temporal_Tables_im_SQL_Server\/\" \/>\n\t\t<meta property=\"og:image\" content=\"https:\/\/datenbankentwickler.net\/wp-content\/uploads\/2022\/08\/cropped-header_vbe-1.png\" \/>\n\t\t<meta property=\"og:image:secure_url\" content=\"https:\/\/datenbankentwickler.net\/wp-content\/uploads\/2022\/08\/cropped-header_vbe-1.png\" \/>\n\t\t<meta property=\"article:published_time\" content=\"2026-07-28T15:07:28+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"-001-11-30T00:00:00+00:00\" \/>\n\t\t<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n\t\t<meta name=\"twitter:title\" content=\"Datenbank-Zeitreisen: Temporal Tables im SQL Server - Visual Basic Entwickler\" \/>\n\t\t<meta name=\"twitter:description\" content=\"Wer hat den Preis ge\u00e4ndert, und wie hoch war er eigentlich vor drei Wochen? Wann ist dieser Datensatz gel\u00f6scht worden, und wie sah er kurz davor aus? Sobald eine Datenbank produktiv l\u00e4uft, tauchen solche Fragen auf - und oft steht man mit leeren H\u00e4nden da, weil die alten Werte l\u00e4ngst \u00fcberschrieben sind. Der klassische Ausweg ist eine selbstgebaute Historientabelle samt Triggern, die bei jedem INSERT, UPDATE und DELETE die alte Zeile wegschreiben. Das funktioniert, ist aber Handarbeit. Der SQL Server bringt seit der Version 2016 eine eingebaute L\u00f6sung mit: System-Versioned Temporal Tables. Die Datenbank f\u00fchrt die komplette \u00c4nderungshistorie vollautomatisch mit, und Abfragen zu einem beliebigen Zeitpunkt der Vergangenheit sind eine Zeile Zusatz-SQL. Wie das gelingt, wie und wo die alten Daten landen und wie Du eine bestehende Tabelle nachtr\u00e4glich versionierst, liest Du in diesem Beitrag - und auch, was Temporal Tables nicht k\u00f6nnen.\" \/>\n\t\t<meta name=\"twitter:image\" content=\"https:\/\/datenbankentwickler.net\/wp-content\/uploads\/2022\/08\/cropped-header_vbe-1.png\" \/>\n\t\t<script type=\"application\/ld+json\" class=\"aioseo-schema\">\n\t\t\t{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"BlogPosting\",\"@id\":\"https:\\\/\\\/vbentwickler.de\\\/DatenbankZeitreisen_Temporal_Tables_im_SQL_Server\\\/#blogposting\",\"name\":\"Datenbank-Zeitreisen: Temporal Tables im SQL Server - Visual Basic Entwickler\",\"headline\":\"Datenbank-Zeitreisen: Temporal Tables im SQL Server\",\"author\":{\"@id\":\"https:\\\/\\\/vbentwickler.de\\\/author\\\/andreminhorst-com\\\/#author\"},\"publisher\":{\"@id\":\"https:\\\/\\\/vbentwickler.de\\\/#organization\"},\"image\":{\"@type\":\"ImageObject\",\"url\":\"http:\\\/\\\/vg08.met.vgwort.de\\\/na\\\/ac79efb76c9c46dd97e46ce1c732f7e0\",\"@id\":\"https:\\\/\\\/vbentwickler.de\\\/DatenbankZeitreisen_Temporal_Tables_im_SQL_Server\\\/#articleImage\"},\"datePublished\":\"2026-06-01T00:00:00+00:00\",\"dateModified\":\"-0001-11-30T00:00:00+00:00\",\"inLanguage\":\"de-DE\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/vbentwickler.de\\\/DatenbankZeitreisen_Temporal_Tables_im_SQL_Server\\\/#webpage\"},\"isPartOf\":{\"@id\":\"https:\\\/\\\/vbentwickler.de\\\/DatenbankZeitreisen_Temporal_Tables_im_SQL_Server\\\/#webpage\"},\"articleSection\":\"2026, 3\\\/2026, SQL Server und Co.\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/vbentwickler.de\\\/DatenbankZeitreisen_Temporal_Tables_im_SQL_Server\\\/#breadcrumblist\",\"itemListElement\":[{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/vbentwickler.de#listItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/vbentwickler.de\",\"nextItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/vbentwickler.de\\\/category\\\/2026\\\/#listItem\",\"name\":\"2026\"}},{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/vbentwickler.de\\\/category\\\/2026\\\/#listItem\",\"position\":2,\"name\":\"2026\",\"item\":\"https:\\\/\\\/vbentwickler.de\\\/category\\\/2026\\\/\",\"nextItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/vbentwickler.de\\\/category\\\/2026\\\/3\\\/2026\\\/#listItem\",\"name\":\"3\\\/2026\"},\"previousItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/vbentwickler.de#listItem\",\"name\":\"Home\"}},{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/vbentwickler.de\\\/category\\\/2026\\\/3\\\/2026\\\/#listItem\",\"position\":3,\"name\":\"3\\\/2026\",\"item\":\"https:\\\/\\\/vbentwickler.de\\\/category\\\/2026\\\/3\\\/2026\\\/\",\"nextItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/vbentwickler.de\\\/DatenbankZeitreisen_Temporal_Tables_im_SQL_Server\\\/#listItem\",\"name\":\"Datenbank-Zeitreisen: Temporal Tables im SQL Server\"},\"previousItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/vbentwickler.de\\\/category\\\/2026\\\/#listItem\",\"name\":\"2026\"}},{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/vbentwickler.de\\\/DatenbankZeitreisen_Temporal_Tables_im_SQL_Server\\\/#listItem\",\"position\":4,\"name\":\"Datenbank-Zeitreisen: Temporal Tables im SQL Server\",\"previousItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/vbentwickler.de\\\/category\\\/2026\\\/3\\\/2026\\\/#listItem\",\"name\":\"3\\\/2026\"}}]},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/vbentwickler.de\\\/#organization\",\"name\":\"Datenbankentwickler\",\"description\":\"Das Magazin f\\u00fcr Datenbankentwickler auf Basis von Visual Studio und Co.\",\"url\":\"https:\\\/\\\/vbentwickler.de\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"url\":\"https:\\\/\\\/vbentwickler.de\\\/wp-content\\\/uploads\\\/2022\\\/08\\\/cropped-header_vbe-1.png\",\"@id\":\"https:\\\/\\\/vbentwickler.de\\\/DatenbankZeitreisen_Temporal_Tables_im_SQL_Server\\\/#organizationLogo\",\"width\":664,\"height\":225},\"image\":{\"@id\":\"https:\\\/\\\/vbentwickler.de\\\/DatenbankZeitreisen_Temporal_Tables_im_SQL_Server\\\/#organizationLogo\"}},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/vbentwickler.de\\\/author\\\/andreminhorst-com\\\/#author\",\"url\":\"https:\\\/\\\/vbentwickler.de\\\/author\\\/andreminhorst-com\\\/\",\"name\":\"Andr\\u00e9 Minhorst\",\"image\":{\"@type\":\"ImageObject\",\"@id\":\"https:\\\/\\\/vbentwickler.de\\\/DatenbankZeitreisen_Temporal_Tables_im_SQL_Server\\\/#authorImage\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/1b9d010cf1716692cb9c34f21554e07d17d461acaea5b61b8cb21cbec678d48a?s=96&d=mm&r=g\",\"width\":96,\"height\":96,\"caption\":\"Andr\\u00e9 Minhorst\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/vbentwickler.de\\\/DatenbankZeitreisen_Temporal_Tables_im_SQL_Server\\\/#webpage\",\"url\":\"https:\\\/\\\/vbentwickler.de\\\/DatenbankZeitreisen_Temporal_Tables_im_SQL_Server\\\/\",\"name\":\"Datenbank-Zeitreisen: Temporal Tables im SQL Server - Visual Basic Entwickler\",\"description\":\"Wer hat den Preis ge\\u00e4ndert, und wie hoch war er eigentlich vor drei Wochen? Wann ist dieser Datensatz gel\\u00f6scht worden, und wie sah er kurz davor aus? Sobald eine Datenbank produktiv l\\u00e4uft, tauchen solche Fragen auf - und oft steht man mit leeren H\\u00e4nden da, weil die alten Werte l\\u00e4ngst \\u00fcberschrieben sind. Der klassische Ausweg ist eine selbstgebaute Historientabelle samt Triggern, die bei jedem INSERT, UPDATE und DELETE die alte Zeile wegschreiben. Das funktioniert, ist aber Handarbeit. Der SQL Server bringt seit der Version 2016 eine eingebaute L\\u00f6sung mit: System-Versioned Temporal Tables. Die Datenbank f\\u00fchrt die komplette \\u00c4nderungshistorie vollautomatisch mit, und Abfragen zu einem beliebigen Zeitpunkt der Vergangenheit sind eine Zeile Zusatz-SQL. Wie das gelingt, wie und wo die alten Daten landen und wie Du eine bestehende Tabelle nachtr\\u00e4glich versionierst, liest Du in diesem Beitrag - und auch, was Temporal Tables nicht k\\u00f6nnen.\",\"inLanguage\":\"de-DE\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/vbentwickler.de\\\/#website\"},\"breadcrumb\":{\"@id\":\"https:\\\/\\\/vbentwickler.de\\\/DatenbankZeitreisen_Temporal_Tables_im_SQL_Server\\\/#breadcrumblist\"},\"author\":{\"@id\":\"https:\\\/\\\/vbentwickler.de\\\/author\\\/andreminhorst-com\\\/#author\"},\"creator\":{\"@id\":\"https:\\\/\\\/vbentwickler.de\\\/author\\\/andreminhorst-com\\\/#author\"},\"datePublished\":\"2026-06-01T00:00:00+00:00\",\"dateModified\":\"-0001-11-30T00:00:00+00:00\"},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/vbentwickler.de\\\/#website\",\"url\":\"https:\\\/\\\/vbentwickler.de\\\/\",\"name\":\"Datenbankentwickler\",\"description\":\"Das Magazin f\\u00fcr Datenbankentwickler auf Basis von Visual Studio und Co.\",\"inLanguage\":\"de-DE\",\"publisher\":{\"@id\":\"https:\\\/\\\/vbentwickler.de\\\/#organization\"}}]}\n\t\t<\/script>\n\t\t<!-- All in One SEO -->\n\n","aioseo_head_json":{"title":"Datenbank-Zeitreisen: Temporal Tables im SQL Server - Visual Basic Entwickler","description":"Wer hat den Preis ge\u00e4ndert, und wie hoch war er eigentlich vor drei Wochen? Wann ist dieser Datensatz gel\u00f6scht worden, und wie sah er kurz davor aus? Sobald eine Datenbank produktiv l\u00e4uft, tauchen solche Fragen auf - und oft steht man mit leeren H\u00e4nden da, weil die alten Werte l\u00e4ngst \u00fcberschrieben sind. Der klassische Ausweg ist eine selbstgebaute Historientabelle samt Triggern, die bei jedem INSERT, UPDATE und DELETE die alte Zeile wegschreiben. Das funktioniert, ist aber Handarbeit. Der SQL Server bringt seit der Version 2016 eine eingebaute L\u00f6sung mit: System-Versioned Temporal Tables. Die Datenbank f\u00fchrt die komplette \u00c4nderungshistorie vollautomatisch mit, und Abfragen zu einem beliebigen Zeitpunkt der Vergangenheit sind eine Zeile Zusatz-SQL. Wie das gelingt, wie und wo die alten Daten landen und wie Du eine bestehende Tabelle nachtr\u00e4glich versionierst, liest Du in diesem Beitrag - und auch, was Temporal Tables nicht k\u00f6nnen.","canonical_url":"https:\/\/vbentwickler.de\/DatenbankZeitreisen_Temporal_Tables_im_SQL_Server\/","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"BlogPosting","@id":"https:\/\/vbentwickler.de\/DatenbankZeitreisen_Temporal_Tables_im_SQL_Server\/#blogposting","name":"Datenbank-Zeitreisen: Temporal Tables im SQL Server - Visual Basic Entwickler","headline":"Datenbank-Zeitreisen: Temporal Tables im SQL Server","author":{"@id":"https:\/\/vbentwickler.de\/author\/andreminhorst-com\/#author"},"publisher":{"@id":"https:\/\/vbentwickler.de\/#organization"},"image":{"@type":"ImageObject","url":"http:\/\/vg08.met.vgwort.de\/na\/ac79efb76c9c46dd97e46ce1c732f7e0","@id":"https:\/\/vbentwickler.de\/DatenbankZeitreisen_Temporal_Tables_im_SQL_Server\/#articleImage"},"datePublished":"2026-06-01T00:00:00+00:00","dateModified":"-0001-11-30T00:00:00+00:00","inLanguage":"de-DE","mainEntityOfPage":{"@id":"https:\/\/vbentwickler.de\/DatenbankZeitreisen_Temporal_Tables_im_SQL_Server\/#webpage"},"isPartOf":{"@id":"https:\/\/vbentwickler.de\/DatenbankZeitreisen_Temporal_Tables_im_SQL_Server\/#webpage"},"articleSection":"2026, 3\/2026, SQL Server und Co."},{"@type":"BreadcrumbList","@id":"https:\/\/vbentwickler.de\/DatenbankZeitreisen_Temporal_Tables_im_SQL_Server\/#breadcrumblist","itemListElement":[{"@type":"ListItem","@id":"https:\/\/vbentwickler.de#listItem","position":1,"name":"Home","item":"https:\/\/vbentwickler.de","nextItem":{"@type":"ListItem","@id":"https:\/\/vbentwickler.de\/category\/2026\/#listItem","name":"2026"}},{"@type":"ListItem","@id":"https:\/\/vbentwickler.de\/category\/2026\/#listItem","position":2,"name":"2026","item":"https:\/\/vbentwickler.de\/category\/2026\/","nextItem":{"@type":"ListItem","@id":"https:\/\/vbentwickler.de\/category\/2026\/3\/2026\/#listItem","name":"3\/2026"},"previousItem":{"@type":"ListItem","@id":"https:\/\/vbentwickler.de#listItem","name":"Home"}},{"@type":"ListItem","@id":"https:\/\/vbentwickler.de\/category\/2026\/3\/2026\/#listItem","position":3,"name":"3\/2026","item":"https:\/\/vbentwickler.de\/category\/2026\/3\/2026\/","nextItem":{"@type":"ListItem","@id":"https:\/\/vbentwickler.de\/DatenbankZeitreisen_Temporal_Tables_im_SQL_Server\/#listItem","name":"Datenbank-Zeitreisen: Temporal Tables im SQL Server"},"previousItem":{"@type":"ListItem","@id":"https:\/\/vbentwickler.de\/category\/2026\/#listItem","name":"2026"}},{"@type":"ListItem","@id":"https:\/\/vbentwickler.de\/DatenbankZeitreisen_Temporal_Tables_im_SQL_Server\/#listItem","position":4,"name":"Datenbank-Zeitreisen: Temporal Tables im SQL Server","previousItem":{"@type":"ListItem","@id":"https:\/\/vbentwickler.de\/category\/2026\/3\/2026\/#listItem","name":"3\/2026"}}]},{"@type":"Organization","@id":"https:\/\/vbentwickler.de\/#organization","name":"Datenbankentwickler","description":"Das Magazin f\u00fcr Datenbankentwickler auf Basis von Visual Studio und Co.","url":"https:\/\/vbentwickler.de\/","logo":{"@type":"ImageObject","url":"https:\/\/vbentwickler.de\/wp-content\/uploads\/2022\/08\/cropped-header_vbe-1.png","@id":"https:\/\/vbentwickler.de\/DatenbankZeitreisen_Temporal_Tables_im_SQL_Server\/#organizationLogo","width":664,"height":225},"image":{"@id":"https:\/\/vbentwickler.de\/DatenbankZeitreisen_Temporal_Tables_im_SQL_Server\/#organizationLogo"}},{"@type":"Person","@id":"https:\/\/vbentwickler.de\/author\/andreminhorst-com\/#author","url":"https:\/\/vbentwickler.de\/author\/andreminhorst-com\/","name":"Andr\u00e9 Minhorst","image":{"@type":"ImageObject","@id":"https:\/\/vbentwickler.de\/DatenbankZeitreisen_Temporal_Tables_im_SQL_Server\/#authorImage","url":"https:\/\/secure.gravatar.com\/avatar\/1b9d010cf1716692cb9c34f21554e07d17d461acaea5b61b8cb21cbec678d48a?s=96&d=mm&r=g","width":96,"height":96,"caption":"Andr\u00e9 Minhorst"}},{"@type":"WebPage","@id":"https:\/\/vbentwickler.de\/DatenbankZeitreisen_Temporal_Tables_im_SQL_Server\/#webpage","url":"https:\/\/vbentwickler.de\/DatenbankZeitreisen_Temporal_Tables_im_SQL_Server\/","name":"Datenbank-Zeitreisen: Temporal Tables im SQL Server - Visual Basic Entwickler","description":"Wer hat den Preis ge\u00e4ndert, und wie hoch war er eigentlich vor drei Wochen? Wann ist dieser Datensatz gel\u00f6scht worden, und wie sah er kurz davor aus? Sobald eine Datenbank produktiv l\u00e4uft, tauchen solche Fragen auf - und oft steht man mit leeren H\u00e4nden da, weil die alten Werte l\u00e4ngst \u00fcberschrieben sind. Der klassische Ausweg ist eine selbstgebaute Historientabelle samt Triggern, die bei jedem INSERT, UPDATE und DELETE die alte Zeile wegschreiben. Das funktioniert, ist aber Handarbeit. Der SQL Server bringt seit der Version 2016 eine eingebaute L\u00f6sung mit: System-Versioned Temporal Tables. Die Datenbank f\u00fchrt die komplette \u00c4nderungshistorie vollautomatisch mit, und Abfragen zu einem beliebigen Zeitpunkt der Vergangenheit sind eine Zeile Zusatz-SQL. Wie das gelingt, wie und wo die alten Daten landen und wie Du eine bestehende Tabelle nachtr\u00e4glich versionierst, liest Du in diesem Beitrag - und auch, was Temporal Tables nicht k\u00f6nnen.","inLanguage":"de-DE","isPartOf":{"@id":"https:\/\/vbentwickler.de\/#website"},"breadcrumb":{"@id":"https:\/\/vbentwickler.de\/DatenbankZeitreisen_Temporal_Tables_im_SQL_Server\/#breadcrumblist"},"author":{"@id":"https:\/\/vbentwickler.de\/author\/andreminhorst-com\/#author"},"creator":{"@id":"https:\/\/vbentwickler.de\/author\/andreminhorst-com\/#author"},"datePublished":"2026-06-01T00:00:00+00:00","dateModified":"-0001-11-30T00:00:00+00:00"},{"@type":"WebSite","@id":"https:\/\/vbentwickler.de\/#website","url":"https:\/\/vbentwickler.de\/","name":"Datenbankentwickler","description":"Das Magazin f\u00fcr Datenbankentwickler auf Basis von Visual Studio und Co.","inLanguage":"de-DE","publisher":{"@id":"https:\/\/vbentwickler.de\/#organization"}}]},"og:locale":"de_DE","og:site_name":"Visual Basic Entwickler - Das Magazin f\u00fcr Datenbankentwickler auf Basis von Visual Studio und Co.","og:type":"article","og:title":"Datenbank-Zeitreisen: Temporal Tables im SQL Server - Visual Basic Entwickler","og:description":"Wer hat den Preis ge\u00e4ndert, und wie hoch war er eigentlich vor drei Wochen? Wann ist dieser Datensatz gel\u00f6scht worden, und wie sah er kurz davor aus? Sobald eine Datenbank produktiv l\u00e4uft, tauchen solche Fragen auf - und oft steht man mit leeren H\u00e4nden da, weil die alten Werte l\u00e4ngst \u00fcberschrieben sind. Der klassische Ausweg ist eine selbstgebaute Historientabelle samt Triggern, die bei jedem INSERT, UPDATE und DELETE die alte Zeile wegschreiben. Das funktioniert, ist aber Handarbeit. Der SQL Server bringt seit der Version 2016 eine eingebaute L\u00f6sung mit: System-Versioned Temporal Tables. Die Datenbank f\u00fchrt die komplette \u00c4nderungshistorie vollautomatisch mit, und Abfragen zu einem beliebigen Zeitpunkt der Vergangenheit sind eine Zeile Zusatz-SQL. Wie das gelingt, wie und wo die alten Daten landen und wie Du eine bestehende Tabelle nachtr\u00e4glich versionierst, liest Du in diesem Beitrag - und auch, was Temporal Tables nicht k\u00f6nnen.","og:url":"https:\/\/vbentwickler.de\/DatenbankZeitreisen_Temporal_Tables_im_SQL_Server\/","og:image":"https:\/\/datenbankentwickler.net\/wp-content\/uploads\/2022\/08\/cropped-header_vbe-1.png","og:image:secure_url":"https:\/\/datenbankentwickler.net\/wp-content\/uploads\/2022\/08\/cropped-header_vbe-1.png","article:published_time":"2026-07-28T15:07:28+00:00","article:modified_time":"-001-11-30T00:00:00+00:00","twitter:card":"summary_large_image","twitter:title":"Datenbank-Zeitreisen: Temporal Tables im SQL Server - Visual Basic Entwickler","twitter:description":"Wer hat den Preis ge\u00e4ndert, und wie hoch war er eigentlich vor drei Wochen? Wann ist dieser Datensatz gel\u00f6scht worden, und wie sah er kurz davor aus? Sobald eine Datenbank produktiv l\u00e4uft, tauchen solche Fragen auf - und oft steht man mit leeren H\u00e4nden da, weil die alten Werte l\u00e4ngst \u00fcberschrieben sind. Der klassische Ausweg ist eine selbstgebaute Historientabelle samt Triggern, die bei jedem INSERT, UPDATE und DELETE die alte Zeile wegschreiben. Das funktioniert, ist aber Handarbeit. Der SQL Server bringt seit der Version 2016 eine eingebaute L\u00f6sung mit: System-Versioned Temporal Tables. Die Datenbank f\u00fchrt die komplette \u00c4nderungshistorie vollautomatisch mit, und Abfragen zu einem beliebigen Zeitpunkt der Vergangenheit sind eine Zeile Zusatz-SQL. Wie das gelingt, wie und wo die alten Daten landen und wie Du eine bestehende Tabelle nachtr\u00e4glich versionierst, liest Du in diesem Beitrag - und auch, was Temporal Tables nicht k\u00f6nnen.","twitter:image":"https:\/\/datenbankentwickler.net\/wp-content\/uploads\/2022\/08\/cropped-header_vbe-1.png"},"aioseo_meta_data":{"post_id":"55000513","title":null,"description":null,"keywords":null,"keyphrases":null,"primary_term":null,"canonical_url":null,"og_title":null,"og_description":null,"og_object_type":"default","og_image_type":"default","og_image_url":null,"og_image_width":null,"og_image_height":null,"og_image_custom_url":null,"og_image_custom_fields":null,"og_video":null,"og_custom_url":null,"og_article_section":null,"og_article_tags":null,"twitter_use_og":false,"twitter_card":"default","twitter_image_type":"default","twitter_image_url":null,"twitter_image_custom_url":null,"twitter_image_custom_fields":null,"twitter_title":null,"twitter_description":null,"schema":{"blockGraphs":[],"customGraphs":[],"default":{"data":{"Article":[],"Course":[],"Dataset":[],"FAQPage":[],"Movie":[],"Person":[],"Product":[],"ProductReview":[],"Car":[],"Recipe":[],"Service":[],"SoftwareApplication":[],"WebPage":[]},"graphName":"","isEnabled":true},"graphs":[]},"schema_type":"default","schema_type_options":null,"pillar_content":false,"robots_default":true,"robots_noindex":false,"robots_noarchive":false,"robots_nosnippet":false,"robots_nofollow":false,"robots_noimageindex":false,"robots_noodp":false,"robots_notranslate":false,"robots_max_snippet":null,"robots_max_videopreview":null,"robots_max_imagepreview":"large","priority":null,"frequency":null,"local_seo":null,"limit_modified_date":false,"created":"2026-07-28 14:01:47","updated":"2026-07-28 14:01:47","ai":null,"breadcrumb_settings":null,"seo_analyzer_scan_date":null},"aioseo_breadcrumb":"<div class=\"aioseo-breadcrumbs\"><span class=\"aioseo-breadcrumb\">\n\t\t\t<a href=\"https:\/\/vbentwickler.de\" title=\"Home\">Home<\/a>\n\t\t<\/span><span class=\"aioseo-breadcrumb-separator\">&raquo;<\/span><span class=\"aioseo-breadcrumb\">\n\t\t\t<a href=\"https:\/\/vbentwickler.de\/category\/2026\/\" title=\"2026\">2026<\/a>\n\t\t<\/span><span class=\"aioseo-breadcrumb-separator\">&raquo;<\/span><span class=\"aioseo-breadcrumb\">\n\t\t\t<a href=\"https:\/\/vbentwickler.de\/category\/2026\/3\/2026\/\" title=\"3\/2026\">3\/2026<\/a>\n\t\t<\/span><span class=\"aioseo-breadcrumb-separator\">&raquo;<\/span><span class=\"aioseo-breadcrumb\">\n\t\t\tDatenbank-Zeitreisen: Temporal Tables im SQL Server\n\t\t<\/span><\/div>","aioseo_breadcrumb_json":[{"label":"Home","link":"https:\/\/vbentwickler.de"},{"label":"2026","link":"https:\/\/vbentwickler.de\/category\/2026\/"},{"label":"3\/2026","link":"https:\/\/vbentwickler.de\/category\/2026\/3\/2026\/"},{"label":"Datenbank-Zeitreisen: Temporal Tables im SQL Server","link":"https:\/\/vbentwickler.de\/DatenbankZeitreisen_Temporal_Tables_im_SQL_Server\/"}],"_links":{"self":[{"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/posts\/55000513","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/comments?post=55000513"}],"version-history":[{"count":0,"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/posts\/55000513\/revisions"}],"wp:attachment":[{"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/media?parent=55000513"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/categories?post=55000513"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/tags?post=55000513"},{"taxonomy":"yst_prominent_words","embeddable":true,"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/yst_prominent_words?post=55000513"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}