Entity Versioning und Temporal Data in ABAP Cloud

kategorie
ABAP Cloud
Veröffentlicht
autor
Johannes

Viele Geschaeftsprozesse erfordern die Nachverfolgung von Datenveraenderungen ueber die Zeit. Ob Preishistorie, Organisationsstrukturen mit Gueltigkeitszeitraeumen oder Vertragsbedingungen, die sich periodisch aendern - zeitabhaengige Daten sind ein zentrales Thema in Unternehmensanwendungen. ABAP Cloud bietet verschiedene Ansaetze, um temporale Daten und Versionierung sauber zu implementieren.

In diesem Artikel lernen Sie die Konzepte hinter Temporal Data, wie Sie zeitabhaengige CDS Views erstellen, verschiedene Strategien zur Historisierung und wie Sie versionierte Entities in RAP umsetzen.

Temporal Data Grundkonzepte

Temporal Data beschreibt Daten, deren Gueltigkeit an Zeitraeume gebunden ist. Man unterscheidet zwei Dimensionen:

DimensionBeschreibungBeispiel
Valid TimeWann sind die Daten gueltig (Geschaeftszeit)Preis gilt vom 01.01. bis 31.03.
Transaction TimeWann wurden die Daten erfasst (Systemzeit)Preis wurde am 15.12. eingetragen

Bitemporale Daten

Die Kombination beider Dimensionen nennt man bitemporale Daten. Sie ermoeglicht Fragen wie:

  • “Was war der Preis am 15.02. laut unserem Wissensstand vom 01.03.?”
  • “Wann haben wir erfahren, dass sich der Preis geaendert hat?”

Fuer die meisten Geschaeftsanwendungen reicht Valid Time aus. Transaction Time ist vor allem fuer Audit-Anforderungen relevant.

Temporale Patterns

Es gibt verschiedene Ansaetze zur Modellierung temporaler Daten:

PatternBeschreibungAnwendungsfall
SnapshotAktueller Stand ohne HistorieEinfache Stammdaten
Effective DatingGueltig-ab/bis ZeitraeumePreislisten, Konditionen
Event SourcingAlle Aenderungen als EventsVollstaendige Nachverfolgung
Slowly Changing DimensionVersionierung mit History-FlagData Warehouse

Tabellendesign fuer Temporal Data

Der erste Schritt ist ein geeignetes Tabellendesign. Hier ein Beispiel fuer eine zeitabhaengige Preistabelle:

@EndUserText.label : 'Produktpreise (zeitabhaengig)'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
define table ztproduct_price {
key client : abap.clnt not null;
key product_id : abap.char(10) not null;
key valid_from : abap.dats not null;
valid_to : abap.dats;
price : abap.dec(15,2);
currency : waers;
created_by : abap.uname;
created_at : timestampl;
last_changed_by : abap.uname;
last_changed_at : timestampl;
}

Wichtige Design-Entscheidungen:

  1. Composite Key: product_id + valid_from identifizieren den Preis eindeutig
  2. valid_to: Kann initial leer sein (= unbegrenzt gueltig)
  3. Audit-Felder: created_at und last_changed_at fuer Transaction Time

Variante mit Versionsnummer

Alternativ zur Datumsbasierung kann eine explizite Versionsnummer verwendet werden:

@EndUserText.label : 'Vertragsbedingungen (versioniert)'
define table ztcontract_terms {
key client : abap.clnt not null;
key contract_id : abap.char(10) not null;
key version : abap.numc(4) not null;
valid_from : abap.dats;
valid_to : abap.dats;
is_current : abap_boolean;
terms_text : abap.string(0);
approved_by : abap.uname;
approved_at : timestampl;
created_by : abap.uname;
created_at : timestampl;
}

Das Flag is_current ermoeglicht schnellen Zugriff auf die aktuelle Version ohne Datumsvergleiche.

Time-Dependent CDS Views

CDS Views koennen zeitabhaengige Daten elegant kapseln und den Zugriff vereinfachen.

Basis-View mit temporalen Daten

@AbapCatalog.viewEnhancementCategory: [#NONE]
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Produktpreise - Basis'
define view entity ZI_ProductPrice
as select from ztproduct_price
{
key product_id as ProductId,
key valid_from as ValidFrom,
valid_to as ValidTo,
price as Price,
currency as Currency,
created_by as CreatedBy,
created_at as CreatedAt,
last_changed_by as LastChangedBy,
last_changed_at as LastChangedAt,
-- Berechnete Felder
case
when valid_to is initial then 'X'
when valid_to >= $session.system_date then 'X'
else ''
end as IsCurrentlyValid,
case
when valid_to is initial then abap.dats'99991231'
else valid_to
end as EffectiveValidTo
}

View fuer aktuell gueltige Preise

@AbapCatalog.viewEnhancementCategory: [#NONE]
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Produktpreise - Aktuell gueltig'
define view entity ZI_ProductPriceCurrent
as select from ZI_ProductPrice
{
key ProductId,
ValidFrom,
ValidTo,
Price,
Currency,
LastChangedAt
}
where
ValidFrom <= $session.system_date
and ( ValidTo >= $session.system_date or ValidTo = '00000000' )

Die $session.system_date Variable liefert das aktuelle Systemdatum und ermoeglicht dynamische Filterung.

View mit Stichtagsabfrage

Fuer Abfragen zu einem bestimmten Stichtag eignet sich ein Parameter:

@AbapCatalog.viewEnhancementCategory: [#NONE]
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Produktpreise - Stichtagsabfrage'
define view entity ZI_ProductPriceAtDate
with parameters
p_key_date : abap.dats
as select from ztproduct_price
{
key product_id as ProductId,
valid_from as ValidFrom,
valid_to as ValidTo,
price as Price,
currency as Currency
}
where
valid_from <= $parameters.p_key_date
and ( valid_to >= $parameters.p_key_date or valid_to = '00000000' )

Verwendung in ABAP:

" Preis zum 15.03.2026 abfragen
SELECT *
FROM zi_productpriceatdate( p_key_date = '20260315' )
WHERE ProductId = 'PROD001'
INTO TABLE @DATA(lt_prices).

Historien-View mit Vorgaenger/Nachfolger

Eine erweiterte View kann Vorgaenger und Nachfolger eines Datensatzes anzeigen:

@AbapCatalog.viewEnhancementCategory: [#NONE]
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Produktpreis-Historie'
define view entity ZI_ProductPriceHistory
as select from ztproduct_price as current_price
left outer join ztproduct_price as previous_price
on current_price.product_id = previous_price.product_id
and previous_price.valid_to = dats_add_days( current_price.valid_from, -1, 'INITIAL' )
{
key current_price.product_id as ProductId,
key current_price.valid_from as ValidFrom,
current_price.valid_to as ValidTo,
current_price.price as CurrentPrice,
current_price.currency as Currency,
previous_price.price as PreviousPrice,
previous_price.valid_from as PreviousValidFrom,
-- Preisaenderung in Prozent
case
when previous_price.price is not initial and previous_price.price <> 0
then division(
( current_price.price - previous_price.price ) * 100,
previous_price.price,
2
)
else cast( 0 as abap.dec(5,2) )
end as PriceChangePercent
}

Historisierung mit RAP

In RAP-Anwendungen ist die Historisierung von Aenderungen ein wichtiges Thema. Es gibt mehrere Ansaetze.

Ansatz 1: Separate History-Tabelle

Bei diesem Ansatz wird bei jeder Aenderung der alte Stand in eine History-Tabelle kopiert:

-- Haupttabelle (aktueller Stand)
define table ztproduct {
key client : abap.clnt not null;
key product_id : abap.char(10) not null;
name : abap.char(40);
price : abap.dec(15,2);
currency : waers;
status : abap.char(2);
last_changed_at : timestampl;
}
-- History-Tabelle
define table ztproduct_history {
key client : abap.clnt not null;
key product_id : abap.char(10) not null;
key history_timestamp : timestampl not null;
name : abap.char(40);
price : abap.dec(15,2);
currency : waers;
status : abap.char(2);
changed_by : abap.uname;
change_type : abap.char(1); -- C=Create, U=Update, D=Delete
}

Die Historisierung erfolgt in einer Determination:

CLASS lhc_product DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
METHODS record_history FOR DETERMINE ON SAVE
IMPORTING keys FOR Product~recordHistory.
ENDCLASS.
CLASS lhc_product IMPLEMENTATION.
METHOD record_history.
" Aktuelle Daten lesen
READ ENTITIES OF zi_product IN LOCAL MODE
ENTITY Product
ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT DATA(lt_products).
" History-Eintraege erstellen
DATA lt_history TYPE STANDARD TABLE OF ztproduct_history.
LOOP AT lt_products INTO DATA(ls_product).
APPEND VALUE #(
product_id = ls_product-ProductId
history_timestamp = cl_abap_context_info=>get_system_time( )
name = ls_product-Name
price = ls_product-Price
currency = ls_product-Currency
status = ls_product-Status
changed_by = cl_abap_context_info=>get_user_technical_name( )
change_type = 'U' " Update
) TO lt_history.
ENDLOOP.
" In History-Tabelle schreiben
INSERT ztproduct_history FROM TABLE @lt_history.
ENDMETHOD.
ENDCLASS.

Ansatz 2: Event Sourcing mit RAP Business Events

Ein modernerer Ansatz speichert alle Aenderungen als Events:

managed implementation in class zbp_i_product unique;
strict ( 2 );
define behavior for ZI_Product alias Product
persistent table ztproduct
lock master total etag LastChangedAt
authorization master ( instance )
etag master LastChangedAt
{
create;
update;
delete;
-- Business Events fuer Audit
event ProductCreated parameter ZA_ProductEvent;
event ProductUpdated parameter ZA_ProductEvent;
event ProductDeleted parameter ZA_ProductEvent;
determination recordChanges on save { create; update; delete; }
}

Der Event-Parameter:

@EndUserText.label: 'Product Change Event'
define abstract entity ZA_ProductEvent
{
ProductId : abap.char(10);
ChangeType : abap.char(10);
OldValues : abap.string(0);
NewValues : abap.string(0);
ChangedBy : abap.uname;
ChangedAt : timestampl;
}

Event-Ausloesung in der Determination:

METHOD recordChanges.
" Update-Events
READ ENTITIES OF zi_product IN LOCAL MODE
ENTITY Product
ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT DATA(lt_products).
LOOP AT lt_products INTO DATA(ls_product).
DATA(lv_new_values) = /ui2/cl_json=>serialize( ls_product ).
RAISE ENTITY EVENT zi_product~ProductUpdated
FROM VALUE #( (
%key = ls_product-%key
ProductId = ls_product-ProductId
ChangeType = 'UPDATE'
NewValues = lv_new_values
ChangedBy = cl_abap_context_info=>get_user_technical_name( )
ChangedAt = cl_abap_context_info=>get_system_time( )
) ).
ENDLOOP.
ENDMETHOD.

Ansatz 3: Soft Delete mit Versionierung

Bei diesem Ansatz werden Datensaetze nie geloescht, sondern nur als ungueltig markiert:

define table ztcontract {
key client : abap.clnt not null;
key contract_id : abap.char(10) not null;
key version : abap.numc(4) not null;
is_current : abap_boolean;
is_deleted : abap_boolean;
valid_from : abap.dats;
valid_to : abap.dats;
customer_id : abap.char(10);
contract_type : abap.char(4);
annual_value : abap.dec(15,2);
currency : waers;
created_by : abap.uname;
created_at : timestampl;
}

Die CDS View filtert automatisch auf aktuelle, nicht geloeschte Versionen:

define view entity ZI_Contract
as select from ztcontract
{
key contract_id as ContractId,
version as Version,
valid_from as ValidFrom,
valid_to as ValidTo,
customer_id as CustomerId,
contract_type as ContractType,
annual_value as AnnualValue,
currency as Currency,
is_current as IsCurrent,
is_deleted as IsDeleted,
created_at as CreatedAt
}
where
is_current = 'X'
and is_deleted = ''

Versionierte Entity - Vollstaendiges Beispiel

Hier ein komplettes Beispiel fuer einen versionierten Vertrag mit RAP:

Datenmodell

-- Vertragsstamm (aktuelle Version)
@EndUserText.label: 'Vertraege'
define table ztcontract_main {
key client : abap.clnt not null;
key contract_id : abap.char(10) not null;
customer_id : abap.char(10);
contract_type : abap.char(4);
current_version : abap.numc(4);
status : abap.char(2);
created_by : abap.uname;
created_at : timestampl;
last_changed_at : timestampl;
}
-- Vertragsversionen
@EndUserText.label: 'Vertragsversionen'
define table ztcontract_version {
key client : abap.clnt not null;
key contract_id : abap.char(10) not null;
key version : abap.numc(4) not null;
valid_from : abap.dats;
valid_to : abap.dats;
annual_value : abap.dec(15,2);
currency : waers;
payment_terms : abap.char(4);
notice_period : abap.numc(3);
notice_unit : abap.char(1);
terms_text : abap.string(0);
version_reason : abap.char(60);
created_by : abap.uname;
created_at : timestampl;
}

Interface View mit Versionen

@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Vertrag mit Versionen'
define root view entity ZI_Contract
as select from ztcontract_main as contract
composition [0..*] of ZI_ContractVersion as _Versions
{
key contract_id as ContractId,
customer_id as CustomerId,
contract_type as ContractType,
current_version as CurrentVersion,
status as Status,
created_by as CreatedBy,
created_at as CreatedAt,
last_changed_at as LastChangedAt,
_Versions
}
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Vertragsversion'
define view entity ZI_ContractVersion
as select from ztcontract_version
association to parent ZI_Contract as _Contract
on $projection.ContractId = _Contract.ContractId
{
key contract_id as ContractId,
key version as Version,
valid_from as ValidFrom,
valid_to as ValidTo,
annual_value as AnnualValue,
currency as Currency,
payment_terms as PaymentTerms,
notice_period as NoticePeriod,
notice_unit as NoticeUnit,
terms_text as TermsText,
version_reason as VersionReason,
created_by as CreatedBy,
created_at as CreatedAt,
-- Berechnetes Feld: Ist aktuelle Version?
case
when version = _Contract.CurrentVersion then 'X'
else ''
end as IsCurrentVersion,
_Contract
}

Behavior Definition

managed implementation in class zbp_i_contract unique;
strict ( 2 );
with draft;
define behavior for ZI_Contract alias Contract
persistent table ztcontract_main
draft table zdraft_contract
lock master total etag LastChangedAt
authorization master ( instance )
etag master LastChangedAt
{
create;
update;
delete;
association _Versions { create; }
action createNewVersion result [1] $self;
draft action Edit;
draft action Activate optimized;
draft action Discard;
draft action Resume;
draft determine action Prepare;
}
define behavior for ZI_ContractVersion alias Version
persistent table ztcontract_version
draft table zdraft_contr_vers
lock dependent by _Contract
authorization dependent by _Contract
etag master CreatedAt
{
update;
delete;
field ( readonly ) ContractId, Version, CreatedBy, CreatedAt;
field ( readonly ) IsCurrentVersion;
association _Contract;
}

Behavior Implementation

CLASS lhc_contract DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
METHODS createNewVersion FOR MODIFY
IMPORTING keys FOR ACTION Contract~createNewVersion RESULT result.
METHODS get_instance_authorizations FOR INSTANCE AUTHORIZATION
IMPORTING keys REQUEST requested_authorizations FOR Contract RESULT result.
ENDCLASS.
CLASS lhc_contract IMPLEMENTATION.
METHOD createNewVersion.
" Aktuelle Vertragsdaten lesen
READ ENTITIES OF zi_contract IN LOCAL MODE
ENTITY Contract
ALL FIELDS WITH CORRESPONDING #( keys )
RESULT DATA(lt_contracts)
ENTITY Contract BY \_Versions
ALL FIELDS WITH CORRESPONDING #( keys )
RESULT DATA(lt_versions).
LOOP AT lt_contracts ASSIGNING FIELD-SYMBOL(<ls_contract>).
" Aktuelle Version ermitteln
DATA(lv_current_version) = <ls_contract>-CurrentVersion.
DATA(lv_new_version) = lv_current_version + 1.
" Letzte Versionsdaten als Vorlage holen
READ TABLE lt_versions INTO DATA(ls_latest_version)
WITH KEY ContractId = <ls_contract>-ContractId
Version = lv_current_version.
IF sy-subrc = 0.
" Alte Version abschliessen (ValidTo setzen)
MODIFY ENTITIES OF zi_contract IN LOCAL MODE
ENTITY Version
UPDATE FIELDS ( ValidTo )
WITH VALUE #( (
%tky = ls_latest_version-%tky
ValidTo = cl_abap_context_info=>get_system_date( )
) ).
" Neue Version erstellen
MODIFY ENTITIES OF zi_contract IN LOCAL MODE
ENTITY Contract
CREATE BY \_Versions
FIELDS ( Version ValidFrom AnnualValue Currency PaymentTerms
NoticePeriod NoticeUnit TermsText VersionReason )
WITH VALUE #( (
%tky = <ls_contract>-%tky
%target = VALUE #( (
%cid = |NEW_VERSION_{ <ls_contract>-ContractId }|
Version = lv_new_version
ValidFrom = cl_abap_context_info=>get_system_date( )
AnnualValue = ls_latest_version-AnnualValue
Currency = ls_latest_version-Currency
PaymentTerms = ls_latest_version-PaymentTerms
NoticePeriod = ls_latest_version-NoticePeriod
NoticeUnit = ls_latest_version-NoticeUnit
TermsText = ls_latest_version-TermsText
VersionReason = 'Neue Version erstellt'
) )
) )
MAPPED DATA(ls_mapped_version).
" Versionsnummer im Hauptvertrag aktualisieren
MODIFY ENTITIES OF zi_contract IN LOCAL MODE
ENTITY Contract
UPDATE FIELDS ( CurrentVersion )
WITH VALUE #( (
%tky = <ls_contract>-%tky
CurrentVersion = lv_new_version
) ).
ENDIF.
" Result zurueckgeben
APPEND VALUE #(
%tky = <ls_contract>-%tky
%param = <ls_contract>
) TO result.
ENDLOOP.
ENDMETHOD.
METHOD get_instance_authorizations.
result = VALUE #( FOR key IN keys (
%tky = key-%tky
%action-createNewVersion = COND #(
WHEN 1 = 1 THEN if_abap_behv=>auth-allowed
ELSE if_abap_behv=>auth-unauthorized
)
) ).
ENDMETHOD.
ENDCLASS.

Abfragen auf temporale Daten

Gueltige Version zu einem Stichtag

" Vertragskonditionen zum Stichtag 01.06.2026
DATA(lv_key_date) = CONV d( '20260601' ).
SELECT contract~ContractId,
contract~CustomerId,
version~Version,
version~ValidFrom,
version~ValidTo,
version~AnnualValue,
version~Currency
FROM zi_contract AS contract
INNER JOIN zi_contractversion AS version
ON contract~ContractId = version~ContractId
WHERE version~ValidFrom <= @lv_key_date
AND ( version~ValidTo >= @lv_key_date OR version~ValidTo = '00000000' )
INTO TABLE @DATA(lt_contracts_at_date).

Versionshistorie anzeigen

" Alle Versionen eines Vertrags
SELECT ContractId,
Version,
ValidFrom,
ValidTo,
AnnualValue,
VersionReason,
CreatedBy,
CreatedAt
FROM zi_contractversion
WHERE ContractId = 'C000000001'
ORDER BY Version DESCENDING
INTO TABLE @DATA(lt_version_history).
" Ausgabe der Historie
LOOP AT lt_version_history INTO DATA(ls_version).
WRITE: / ls_version-Version,
ls_version-ValidFrom,
ls_version-ValidTo,
ls_version-AnnualValue,
ls_version-VersionReason.
ENDLOOP.

Aenderungen zwischen Versionen ermitteln

CLASS zcl_version_compare DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
TYPES:
BEGIN OF ty_field_change,
field_name TYPE string,
old_value TYPE string,
new_value TYPE string,
END OF ty_field_change,
tt_field_changes TYPE STANDARD TABLE OF ty_field_change WITH EMPTY KEY.
METHODS compare_versions
IMPORTING
iv_contract_id TYPE ztcontract_version-contract_id
iv_version_old TYPE ztcontract_version-version
iv_version_new TYPE ztcontract_version-version
RETURNING
VALUE(rt_changes) TYPE tt_field_changes.
ENDCLASS.
CLASS zcl_version_compare IMPLEMENTATION.
METHOD compare_versions.
" Beide Versionen laden
SELECT SINGLE *
FROM zi_contractversion
WHERE ContractId = @iv_contract_id
AND Version = @iv_version_old
INTO @DATA(ls_old).
SELECT SINGLE *
FROM zi_contractversion
WHERE ContractId = @iv_contract_id
AND Version = @iv_version_new
INTO @DATA(ls_new).
" Feldvergleich
IF ls_old-AnnualValue <> ls_new-AnnualValue.
APPEND VALUE #(
field_name = 'Jahreswert'
old_value = |{ ls_old-AnnualValue }|
new_value = |{ ls_new-AnnualValue }|
) TO rt_changes.
ENDIF.
IF ls_old-PaymentTerms <> ls_new-PaymentTerms.
APPEND VALUE #(
field_name = 'Zahlungsbedingungen'
old_value = ls_old-PaymentTerms
new_value = ls_new-PaymentTerms
) TO rt_changes.
ENDIF.
IF ls_old-NoticePeriod <> ls_new-NoticePeriod.
APPEND VALUE #(
field_name = 'Kuendigungsfrist'
old_value = |{ ls_old-NoticePeriod } { ls_old-NoticeUnit }|
new_value = |{ ls_new-NoticePeriod } { ls_new-NoticeUnit }|
) TO rt_changes.
ENDIF.
ENDMETHOD.
ENDCLASS.

UI-Integration

Fuer die Anzeige von Versionen in Fiori Elements definieren Sie entsprechende Annotations:

@Metadata.layer: #CORE
annotate view ZC_ContractVersion with
{
@UI.facet: [
{
id: 'VersionDetails',
purpose: #STANDARD,
type: #IDENTIFICATION_REFERENCE,
label: 'Versionsdetails',
position: 10
}
]
@UI.lineItem: [{ position: 10, importance: #HIGH }]
@UI.identification: [{ position: 10 }]
Version;
@UI.lineItem: [{ position: 20, importance: #HIGH }]
@UI.identification: [{ position: 20 }]
ValidFrom;
@UI.lineItem: [{ position: 30, importance: #MEDIUM }]
@UI.identification: [{ position: 30 }]
ValidTo;
@UI.lineItem: [{ position: 40, importance: #HIGH }]
@UI.identification: [{ position: 40 }]
AnnualValue;
@UI.lineItem: [{ position: 50, importance: #LOW }]
VersionReason;
@UI.lineItem: [{ position: 60, importance: #LOW,
criticality: 'Criticality',
criticalityRepresentation: #WITH_ICON }]
IsCurrentVersion;
}

Best Practices

1. Konsistente Zeitraeume

Stellen Sie sicher, dass Zeitraeume lueckenlos und ueberschneidungsfrei sind:

METHOD validate_no_gaps.
" Alle Versionen eines Vertrags laden
SELECT ValidFrom, ValidTo
FROM ztcontract_version
WHERE contract_id = @iv_contract_id
ORDER BY ValidFrom
INTO TABLE @DATA(lt_periods).
" Auf Luecken pruefen
DATA(lv_expected_start) = VALUE d( ).
LOOP AT lt_periods INTO DATA(ls_period).
IF sy-tabix > 1 AND ls_period-ValidFrom <> lv_expected_start.
" Luecke gefunden
RAISE EXCEPTION TYPE zcx_validation
EXPORTING
textid = zcx_validation=>gap_in_validity.
ENDIF.
lv_expected_start = ls_period-ValidTo + 1.
ENDLOOP.
ENDMETHOD.

2. Performance-Optimierung

Bei grossen Datenmengen indizieren Sie die Datumsfelder:

-- Sekundaerindex fuer zeitbasierte Abfragen
@AbapCatalog.sqlViewAppendName: 'ZIDX_PRICE_DATE'
define table index ztproduct_price_idx
on ztproduct_price
primary index (product_id, valid_from, valid_to)

3. Archivierung alter Versionen

Planen Sie die Archivierung alter Versionen:

" Versionen aelter als 7 Jahre archivieren
DATA(lv_archive_date) = cl_abap_context_info=>get_system_date( ) - 365 * 7.
SELECT *
FROM ztcontract_version
WHERE valid_to < @lv_archive_date
AND valid_to <> '00000000'
INTO TABLE @DATA(lt_to_archive).
" In Archivtabelle verschieben
INSERT ztcontract_vers_arch FROM TABLE @lt_to_archive.
DELETE ztcontract_version FROM TABLE @lt_to_archive.

Zusammenfassung

Temporale Daten und Versionierung sind essenzielle Konzepte fuer Geschaeftsanwendungen:

  • Temporal Data unterscheidet Valid Time (Geschaeftsgueltigkeit) und Transaction Time (Systemzeit)
  • Time-Dependent CDS Views kapseln temporale Logik und ermoeglichen Stichtagsabfragen
  • Historisierung kann ueber separate History-Tabellen, Event Sourcing oder Soft Delete umgesetzt werden
  • Versionierte Entities in RAP nutzen Composition-Beziehungen zwischen Stamm und Versionen
  • Best Practices umfassen lueckenlose Zeitraeume, Performance-Optimierung und Archivierung

Mit diesen Techniken koennen Sie die vollstaendige Nachverfolgbarkeit Ihrer Geschaeftsdaten sicherstellen und gleichzeitig effiziente Abfragen auf historische Staende ermoeglichen.

Weiterfuehrende Themen