Currency conversion objects
A currency conversion object is a specialized helper object within the Data Model. Like other helper objects, it is technical in nature, not business-facing, and sits outside the process flow. Its sole purpose is to provide reusable, time-dependent exchange rate logic that can be applied dynamically in KPIs and analyses across systems and processes.
Using the CURRENCY_CONVERT operator, monetary values can be converted dynamically during analysis rather than being stored as pre-converted values. The currency conversion object defines:
Source and target currencies
Exchange rates
Validity periods for those rates
Exchange rate types
System-specific configurations
Key capabilities of currency conversion objects
Supports multiple systems and multiple exchange rate types.
Allows time-dependent exchange rates.
Enables conversion into different target currencies.
Keeps currency conversion independent of process logic.
Lets analysts choose the target currency in Studio, within the set defined by the data engineer.
Using currency conversion objects
Currency conversion object allows Celonis to dynamically convert values from one currency to another using defined exchange rates and time frames. If a direct conversion is unavailable, intermediate currencies can be used to complete the calculation. This ensures consistent, flexible reporting across systems and regions, even when source currencies differ or conversion paths are complex.
When a direct conversion from a source currency to a target currency is unavailable, the Currency Conversion object can use an intermediate currency to complete the conversion:
Example 1: AUD → EUR
No direct AUD→EUR rate exists
AUD→USD and USD→EUR rates exist, so the conversion can use USD as an intermediary
Example 2: NOK → EUR
No available conversion path exists through any intermediate currency
In this case, additional data extraction or adjustments are required to provide a valid conversion path
When a currency does not have a direct conversion rate with another currency and the inverse of the combination is also not available, then the best strategy is to find an intermediary currency pair as described above. This solution is not used in the base CurrencyConversion object as it would inflate the data unnecessarily for most cases.
Below is an example of a second insert that would be added to a customer’s CurrencyConversion object when using OracleEBS to convert all currencies into AUD:
WITH "CTE_INTERMEDIARY" AS (
SELECT 'CurrencyConversion_' || "GL_DAILY_RATES"."FROM_CURRENCY" || '_' || "GL_DAILY_RATES"."TO_CURRENCY" || '_'
|| "GL_DAILY_RATES"."CONVERSION_DATE"
|| '_EBS' AS "ID", || '_EBS'
"GL_DAILY_RATES"."FROM_CURRENCY" AS "FromCurrency",
"GL_DAILY_RATES"."TO_CURRENCY" AS "ToCurrency",
CAST("GL_DAILY_RATES"."CONVERSION_DATE" AS TIMESTAMP) AS "FromDate",
CAST(CAST("GL_DAILY_RATES"."CONVERSION_DATE" AS DATE)
+ CAST('23:59:59' AS TIME) AS TIMESTAMP) AS "ToDate",
"GL_DAILY_RATES"."CONVERSION_RATE" AS "Rate",
'Oracle' AS "SourceSystemType",
NULL AS "SourceSystemInstance",
"GL_DAILY_RATES"."CONVERSION_TYPE" AS "ExchangeRateType"
FROM <%=DATASOURCE:ORACLE_ORACLE%>."GL_DAILY_RATES" AS "GL_DAILY_RATES"
WHERE "GL_DAILY_RATES"."CONVERSION_TYPE" = 'Corporate' -- TODO: Parameter (ExchangeRateType = 'Corporate')
AND "GL_DAILY_RATES"."TO_CURRENCY" = 'USD'
)
SELECT 'CurrencyConversion_' || "GL_DAILY_RATES"."FROM_CURRENCY" || '_' ||
"GL_DAILY_RATES"."TO_CURRENCY" || '_'
|| "GL_DAILY_RATES"."CONVERSION_DATE"
|| '_EBS' AS "ID",
"CTE_INTERMEDIARY"."FromCurrency" AS "FromCurrency",
"GL_DAILY_RATES"."TO_CURRENCY" AS "ToCurrency",
CAST("GL_DAILY_RATES"."CONVERSION_DATE" AS TIMESTAMP) AS "FromDate",
CAST(CAST("GL_DAILY_RATES"."CONVERSION_DATE" AS DATE)
+ CAST('23:59:59' AS TIME) AS TIMESTAMP) AS "ToDate",
"CTE_INTERMEDIARY"."Rate" * "GL_DAILY_RATES"."CONVERSION_RATE" AS "Rate",
'Oracle' AS "SourceSystemType",
NULL AS "SourceSystemInstance",
"GL_DAILY_RATES"."CONVERSION_TYPE" AS "ExchangeRateType"
FROM <%=DATASOURCE:ORACLE_ORACLE%>."GL_DAILY_RATES" AS "GL_DAILY_RATES"
LEFT JOIN "CTE_INTERMEDIARY"
ON "CTE_INTERMEDIARY"."FromDate" = CAST("GL_DAILY_RATES"."CONVERSION_DATE" AS TIMESTAMP)
AND "CTE_INTERMEDIARY"."ToCurrency" = "GL_DAILY_RATES"."FROM_CURRENCY"
WHERE "GL_DAILY_RATES"."CONVERSION_TYPE" = 'Corporate'
AND "GL_DAILY_RATES"."FROM_CURRENCY" = 'USD'
AND "GL_DAILY_RATES"."TO_CURRENCY" = 'AUD';Code explanation
Summarizing explanation of the object creation code.
The first part is specific per system, as it considers the raw system table as input. Output of the system specific section is the creation of two CTEs, CTE_DIRECT_CONVERSION and CTE_INVERSE_CONVERSION.
Required tables:
TCURR
TCURF
TCURX
Logic:
Self-join TCURF to get all Start and End times for it.
CTE_TCURF_TMP
Select * from TCURF
DENSE_RANK() for easier join instead of all entries
ROW_NUMBER() to do the offset by validity date
CTE_TCURF_CC
Just converts GDATU to a readable timestamp format and adds it as VALID_START
CTE_TCURF_CC_2
Handles the self-join based on the DENSE_RANK as key and ROW_NUMBER as the offset to get all valid start/end time pairs
CTE_TCURR_TMP
Select * from TCURR
DENSE_RANK() for easier join instead of all entries
ROW_NUMBER() to do the offset by validity date
CTE_TCURR_CC
Just converts GDATU to a readable timestamp format and adds it as VALID_START
CTE_TCURR_CC_2
Handles the self-join based on the DENSE_RANK as key and ROW_NUMBER as the offset to get all valid start/end time pairs
Build helper tables with all days since beginning of time until now() (TCURR/TCURF GDATU are not the same so this is needed).
CTE_DATE_RANGE
Set starting point, set end point (now()) and then timestamp every day
Get TDEC value to get true position of comma for converted values.
CTE_TCURX

Standard is 2 so the offset is then based off of it
Build the whole thing.
Currency_Conversion
ID: Mix of everything
FromCurrency: TCURR.FCURR
ToCurrency: TCURR.TCURR
FromDate: TCURX ._DATE
ToDate: TCURX ._DATE
Rate: please find explanation below
Rate calculation
UKURS
UKURS < 0 means we need inverse calculation (1/ABS(UKURS))
UKURS > 0 means we can directly use it
Multiply the TCURX.TDEC offset
Divide FromCurrency Ratio TCURF.FFACT
Multiply ToCurrency Ratio TCURF.TFACT
CTE_DIRECT_CONVERSION
ID composed based on order: "TCURR_CC_2"."FCURR" || '_' || "TCURR_CC_2"."TCURR" || '_' || "_DATE" || '_' || 'SAP' || '_' || "TCURR_CC_2"."MANDT"
FromCurrency filled with actual FromCurrency value in raw table: "TCURR_CC_2"."FCURR"
ToCurrency filled with actual ToCurrency value in raw table: "TCURR_CC_2"."TCURR"
Rate calculation (direct - formula in point 5)
Flag inserted in ‘ConversionType’ column with value ‘Direct’
Filter applied: MANDT IS NOT NULL
CTE_INVERSE_CONVERSION
ID composed based on order: "TCURR_CC_2"."TCURR" || '_' || "TCURR_CC_2"."FCURR" || '_' || "_DATE" || '_' || 'SAP' || '_' || "TCURR_CC_2"."MANDT"
FromCurrency filled with actual ToCurrency value in raw table: "TCURR_CC_2"."TCURR"
ToCurrency filled with actual FromCurrency value in raw table: "TCURR_CC_2"."FCURR"
Inverse rate calculation ( 1/ formula in point 5)
Flag inserted in ‘ConversionType’ column with value ‘Inverse’
Filter applied: MANDT IS NOT NULL
Required tables:
GL_DAILY_RATES
Logic:
CTE_DIRECT_CONVERSION
ID composed based on order: "GL_DAILY_RATES"."FROM_CURRENCY" || '_' || "GL_DAILY_RATES"."TO_CURRENCY" || '_' || "GL_DAILY_RATES"."CONVERSION_DATE" || '_EBS'
FromCurrency filled with actual FromCurrency value in raw table: "GL_DAILY_RATES"."FROM_CURRENCY"
ToCurrency filled with actual ToCurrency value in raw table: "GL_DAILY_RATES"."TO_CURRENCY"
Rate - Direct: "GL_DAILY_RATES"."CONVERSION_RATE"
Flag inserted in ‘ConversionType’ column with value ‘Direct’
No filter applied
CTE_INVERSE_CONVERSION
ID composed based on order: "GL_DAILY_RATES"."TO_CURRENCY" || '_' || "GL_DAILY_RATES"."FROM_CURRENCY" || '_' || "GL_DAILY_RATES"."CONVERSION_DATE" || '_EBS'
FromCurrency filled with actual ToCurrency value in raw table: "GL_DAILY_RATES"."TO_CURRENCY"
ToCurrency filled with actual FromCurrency value in raw table: "GL_DAILY_RATES"."FROM_CURRENCY"
Rate - Inverse: 1 / "GL_DAILY_RATES"."CONVERSION_RATE"
Flag inserted in ‘ConversionType’ column with value ‘Inverse’
No filter applied
Required tables:
FscmTopModelAM_FinExtractAM_GlBiccExtractAM_DailyRateExtractPVO
Logic:
CTE_DIRECT_CONVERSION
ID composed based on order: "DailyRateExtractPVO"."DailyRateFromCurrency" || '_' || "DailyRateExtractPVO"."DailyRateToCurrency" || '_' || "DailyRateExtractPVO"."DailyRateConversionDate" || '_FUSION'
FromCurrency filled with actual FromCurrency value in raw table: "DailyRateExtractPVO"."DailyRateFromCurrency"
ToCurrency filled with actual ToCurrency value in raw table: "DailyRateExtractPVO"."DailyRateToCurrency"
Rate - Direct: "DailyRateExtractPVO"."DailyRateConversionRate"
Flag inserted in ‘ConversionType’ column with value ‘Direct’
No filter applied
CTE_INVERSE_CONVERSION
ID composed based on order: "DailyRateExtractPVO"."DailyRateToCurrency" || '_' || "DailyRateExtractPVO"."DailyRateFromCurrency" || '_' || "DailyRateExtractPVO"."DailyRateConversionDate" || '_FUSION'
FromCurrency filled with actual ToCurrency value in raw table: "DailyRateExtractPVO"."DailyRateToCurrency"
ToCurrency filled with actual FromCurrency value in raw table: "DailyRateExtractPVO"."DailyRateFromCurrency"
Rate - Inverse: 1 / "DailyRateExtractPVO"."DailyRateConversionRate"
Flag inserted in ‘ConversionType’ column with value ‘Inverse’
No filter applied
This part of the code is following the previously explained one per system, and it is in common for all the systems.
Required CTEs previously created:
CTE_DIRECT_CONVERSION
CTE_INVERSE_CONVERSION
Logic:
CTE_DIRECT_CONVERSION_TO
Select * from CTE_DIRECT_CONVERSION
Filter on ToCurrency for combination (ExchangeRateType, CurrencyKey) OR (ExchangeRateType_2, CurrencyKey_2)
CTE_DIRECT_CONVERSION_FROM
Select * from CTE_DIRECT_CONVERSION
Filter on FromCurrency for combination (ExchangeRateType, CurrencyKey) OR (ExchangeRateType_2, CurrencyKey_2)
CTE_INVERSE_CONVERSION_TO
Select * from CTE_INVERSE_CONVERSION
Filter on ToCurrency for combination (ExchangeRateType, CurrencyKey) OR (ExchangeRateType_2, CurrencyKey_2)
CTE_INVERSE_CONVERSION_FROM
Select * from CTE_INVERSE_CONVERSION
Filter on FromCurrency for combination (ExchangeRateType, CurrencyKey) OR (ExchangeRateType_2, CurrencyKey_2)
CTE_CONVERSION_ALL
Unioning following selects:
Select * from CTE_DIRECT_CONVERSION_TO
Select * from CTE_DIRECT_CONVERSION_FROM excluding combinations already in CTE_DIRECT_CONVERSION_TO (ExchangeRateType not considered when excluding - in case of overlap ExchangeRateType in previous select predominate)
Select * from INVERSE_CONVERSION_FROM excluding cases in CTE_DIRECT_CONVERSION_TO and CTE_DIRECT_CONVERSION_FROM (ExchangeRateType not considered when excluding - in case of overlap ExchangeRateType in previous select predominate)
Select * from INVERSE_CONVERSION_TO excluding cases in CTE_DIRECT_CONVERSION_TO and CTE_DIRECT_CONVERSION_FROM and INVERSE_CONVERSION_FROM (ExchangeRateType not considered when excluding - in case of overlap ExchangeRateType in previous select predominate)