Entity Versioning et Temporal Data dans ABAP Cloud

Catégorie
ABAP Cloud
Publié
Auteur
Johannes

De nombreux processus métier nécessitent le suivi des modifications de données au fil du temps. Que ce soit pour l’historique des prix, les structures organisationnelles avec périodes de validité ou les conditions contractuelles qui changent périodiquement, les données temporelles constituent un thème central dans les applications d’entreprise. ABAP Cloud offre différentes approches pour implémenter proprement les données temporelles et le versioning.

Dans cet article, vous apprendrez les concepts derrière Temporal Data, comment créer des vues CDS temporelles, différentes stratégies d’historisation et comment implémenter des entités versionnées dans RAP.

Concepts fondamentaux de Temporal Data

Temporal Data décrit des données dont la validité est liée à des périodes temporelles. On distingue deux dimensions :

DimensionDescriptionExemple
Valid TimeQuand les données sont valides (temps métier)Le prix est valable du 01.01. au 31.03.
Transaction TimeQuand les données ont été saisies (temps système)Le prix a été saisi le 15.12.

Données bitemporelles

La combinaison des deux dimensions s’appelle données bitemporelles. Elle permet de répondre à des questions comme :

  • “Quel était le prix au 15.02. selon notre état de connaissance du 01.03. ?”
  • “Quand avons-nous appris que le prix a changé ?”

Pour la plupart des applications métier, Valid Time suffit. Transaction Time est surtout pertinent pour les exigences d’audit.

Patterns temporels

Il existe différentes approches pour modéliser les données temporelles :

PatternDescriptionCas d’usage
SnapshotÉtat actuel sans historiqueDonnées de base simples
Effective DatingPériodes de validité depuis/jusqu’àListes de prix, conditions
Event SourcingToutes les modifications comme événementsTraçabilité complète
Slowly Changing DimensionVersioning avec flag historiqueData Warehouse

Conception de table pour Temporal Data

La première étape est une conception de table appropriée. Voici un exemple de table de prix temporelle :

@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;
}

Décisions de conception importantes :

  1. Clé composite : product_id + valid_from identifient le prix de manière unique
  2. valid_to : Peut être initialement vide (= valable indéfiniment)
  3. Champs d’audit : created_at et last_changed_at pour Transaction Time

Variante avec numéro de version

Alternativement au datage, un numéro de version explicite peut être utilisé :

@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;
}

Le flag is_current permet un accès rapide à la version actuelle sans comparaisons de dates.

Time-Dependent CDS Views

Les vues CDS peuvent encapsuler élégamment les données temporelles et simplifier l’accès.

Vue de base avec données temporelles

@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
}

Vue pour les prix actuellement valables

@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' )

La variable $session.system_date fournit la date système actuelle et permet un filtrage dynamique.

Vue avec requête à date fixe

Pour les requêtes à une date fixe spécifique, un paramètre est approprié :

@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' )

Utilisation en ABAP :

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

Vue historique avec prédécesseur/successeur

Une vue étendue peut afficher le prédécesseur et le successeur d’un enregistrement :

@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
}

Historisation avec RAP

Dans les applications RAP, l’historisation des modifications est un sujet important. Il existe plusieurs approches.

Approche 1 : Table d’historique séparée

Dans cette approche, l’état précédent est copié dans une table d’historique à chaque modification :

-- 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
}

L’historisation s’effectue dans une détermination :

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.

Approche 2 : Event Sourcing avec RAP Business Events

Une approche plus moderne stocke toutes les modifications comme événements :

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; }
}

Le paramètre d’événement :

@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;
}

Déclenchement d’événement dans la détermination :

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.

Approche 3 : Soft Delete avec versioning

Dans cette approche, les enregistrements ne sont jamais supprimés, mais seulement marqués comme non valides :

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;
}

La vue CDS filtre automatiquement sur les versions actuelles non supprimées :

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 = '"

Entité versionnée - Exemple complet

Voici un exemple complet pour un contrat versionné avec RAP :

Modèle de données

-- 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;
}

Vue d’interface avec versions

@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.

Requêtes sur les données temporelles

Version valide à une date fixe

" 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).

Afficher l’historique des versions

" 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.

Déterminer les modifications entre versions

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.

Intégration UI

Pour l’affichage des versions dans Fiori Elements, définissez les annotations correspondantes :

@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;
}

Bonnes pratiques

1. Périodes cohérentes

Assurez-vous que les périodes sont sans lacunes et sans chevauchements :

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. Optimisation des performances

Pour de grandes quantités de données, indexez les champs de date :

-- 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. Archivage des anciennes versions

Planifiez l’archivage des anciennes versions :

" 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.

Résumé

Les données temporelles et le versioning sont des concepts essentiels pour les applications métier :

  • Temporal Data distingue Valid Time (validité métier) et Transaction Time (temps système)
  • Time-Dependent CDS Views encapsulent la logique temporelle et permettent des requêtes à date fixe
  • L’historisation peut être mise en œuvre via des tables d’historique séparées, Event Sourcing ou Soft Delete
  • Les entités versionnées dans RAP utilisent des relations de composition entre les données principales et les versions
  • Les bonnes pratiques incluent des périodes sans lacunes, l’optimisation des performances et l’archivage

Avec ces techniques, vous pouvez assurer la traçabilité complète de vos données métier tout en permettant des requêtes efficaces sur les états historiques.

Thèmes connexes