Skip to Content
Technical Articles
Author's profile photo Slava Lopatin

Flat file upload to SAC Planning model with analytic application and dataset

SAC provides extensive capabilities for integration with different SAP and non-SAP solutions. These capabilities can be used for scheduled or manual upload of master and transaction data into planning models, including loading from flat files.

But for specific use cases it is required to load transaction data from flat files to SAC planning model specifically by end users as opposed to administrators of the solution. That can be, for example, bulk upload of financial data by financial planners, as an addition to manual entering data in SAC’s tables widget. These users usually do not have extended authorizations for accessing model data management, as it can result in uncontrolled master / transaction data changes.

In this blog we will see how this can be solved using standard SAC capabilities. For this approach end users do not need to have an access to planning model data management. Keep in mind that this flat file upload solution is intended to be used for limited sets of data.

The solution logic

For creating the solution only with standard components of SAC, we will use a dataset and an analytic application.

Firstly, in step 1, the end user will load flat file or xls into a new or existing dataset object. Dataset has a standard capability of loading data from files and does not require master data and / or dimensions maintenance. Its interface is user friendly and does not require specific data modelling knowledge or extended authorizations (as it is in the case of planning model).In addition, data transformation is also possible on this step.

Once data is loaded in dataset, in step 2, we will use analytic application to review and copy data from dataset into the required planning model.


Let’s have a look at the technical logic of this solution in details.

Step 1. Loading data from flat file into dataset

Dataset is used for the initial data upload into SAC. It can be created by end users themselves or can be pre-created. End users can also create several datasets, for example, for different versions of data.


Importing / Re-importing data into Dataset

In addition, dataset has the following useful characteristics in the context of our task:

  • Can be created via drag & drop of csv or xls file to SAC interface.
  • No master data, dimensions / measures maintenance needed.
  • Can contain data transformation / conversion rules (to enable logic similar to transformation / conversion files in BPC Standard).
  • Re-usable: data can be re-loaded and all earlier created transformation rules will be applied.


Transformation rules in Dataset

As a result of this step, we have data from flat file loaded into the dataset.

Step 2. Loading data from dataset into planning model with Analytics application

We have data from flat file loaded into dataset and transformed. Now we need to load this data into our planning model. We will use the analytics application for that.

The interface of analytic application contains 2 parts, logically split into 2 tabs using Tab Strip widget:

Tab 1. Opening dataset and preview

Open dataset and preview data tab

In this interface we have data preview tab and a button to execute open dataset selection dialog.

Once we click Open Dataset button, the Open Dataset / Model dialog appears. In this dialog we select source dataset with uploaded in step 1 data. Once it is done, the data is shown in preview table and relevant filters are applied to result table in Tab 2 (to show in result table only part of planning model, relevant for data uploading).

Tab 2. Copying data from preview dataset table to planning model table

When we click button Next, the preview interface if shown. Here we need to click Load button. After that data from preview table tblPreview is being copied into result table tblResult, record by record. Here we can review the data, edit it in the table, and Publish or Revert using standard SAC interface.


Result preview and Publish / Revert tab

Result of step 2 will be data published into planning model.

And now let’s have a look at the code behind these interfaces. Keep in mind, that this code has illustrative purpose, hence, some parameters are hardcoded. In case needed, they can be derived dynamically via parameters or based on specific logic / mapping.

Tab 1 – Button “Open Dataset..” (btnOpenDataset), onClick event:

// Open dataset / model dialog to allow user to select the required source dataset

// Making all dataset dimensions visible in the table
var dimList = tblPreview.getDataSource().getDimensions();
for (var i=0; i< dimList.length; i++)

// Making Date hierarchy flat, as by default it opens YQM

// Showing preview table with dataset data

// Populating values from dataset into arrays for applying them as filters for the target table
var rsData = tblPreview.getDataSource().getResultSet();

var accMembers = ArrayUtils.create(Type.string);
var ccMembers = ArrayUtils.create(Type.string);
var dsMembers = ArrayUtils.create(Type.string);
var dateMembers = ArrayUtils.create(Type.string);
var baMembers = ArrayUtils.create(Type.string);

// Going through all records of dataset and assigning values to arrays
for (var k=0; k< rsData.length; k++)
	accMembers[k] = "[Account].[parentId].&[" + rsData[k]["Account"].id + "]";	
	ccMembers[k]= "[Cost_center].[CC_H].&["+  rsData[k]["Cost_Center"].id  + "]";
	dsMembers[k] = "Input";
	dateMembers[k] = rsData[k]["Date"].id.substring(0,4)+rsData[k]["Date"].id.substring(5,7);
	baMembers[k] = rsData[k]["Business_Area"].id;

// Setting filters of Result table on the second page to show only relevant PoV

// Showing result table with applied filters



Tab 2 – Button “Load” (btnLoad), onClick event:

// Copy lines & submit

// Get set of data from preview table with loaded dataset
var rsData = tblPreview.getDataSource().getResultSet();

// One by one copy values from source table into result (target) table
for (var i=0; i< tblPreview.getRowCount(); i++)
	// Logic to read records from source table and enter into the target table
	// Reading values of Account and Cost center and adjusting to include hierarchy elements
	// In case needed, the code can be enhanced with dynamic hier
	var Account = "[Account].[parentId].&[" + rsData[i]["Account"].id + "]" ;
	var CostCenter = "[Cost_center].[CC_H].&["+  rsData[i]["Cost_Center"].id  + "]";
	// Writing values into target table with planning model
		"Business_Area":rsData[i]["Business_Area"].id, // Business Area dimension
		"Data_source":"Input", 	   // Hardcoded for simplicity. In case needed, logic is the same as Account and Cost center
		"Version":"public.Actual", // Hardcoded for simplicity. In case needed, logic is the same as Account and Cost center
		"Date":rsData[i]["Date"].id.substring(0,4)+rsData[i]["Date"].id.substring(5,7), // Date dimension
		[Alias.MeasureDimension]:"SignedData"},rsData[i][Alias.MeasureDimension].rawValue); // Value

// Submitting the data to show it in table


Additional extensions and limitations

The logic and code above illustrate the basic scenario. For making the application dummy-proof or more advanced, the following pieces of logic can be added:

1. Checking of master data when opening data set and showing the missing master data members and/or adding them. Example of the code for checking Accounts / Cost centers can look in the following way, and needs to be added to btnOpenDataset – onClick event:

	// Forming Accounts array
	var arrDestAcctMembers = ArrayUtils.create(Type.MemberInfo);
	arrDestAcctMembers = tblResult.getDataSource().getMembers("Account");
	var strDestAcctMembers = ArrayUtils.create(Type.string);
	for (k=0; k< arrDestAcctMembers.length; k++)
			strDestAcctMembers[k] = arrDestAcctMembers[k].id;

	// Forming Cost Centers array
	var arrDestCCtMembers = ArrayUtils.create(Type.MemberInfo);
	arrDestCCtMembers = tblResult.getDataSource().getMembers("Cost_center");
	var strDestCCtMembers = ArrayUtils.create(Type.string);
	for (k=0; k< arrDestCCtMembers.length; k++)
			strDestCCtMembers[k] = arrDestCCtMembers[k].id;
	// Account / Cost centers check check
	for (k=0; k< rsData.length; k++)
				if (strDestAcctMembers.indexOf("[Account].[parentId].&[" + rsData[k]["Account"].id + "]") === -1)
						lbLog.addItem("A"+k.toString(), "Account " +  rsData[k]["Account"].id  + " does not exist in target model");
				if (strDestCCtMembers.indexOf("[Cost_center].[CC_H].&["+  rsData[k]["Cost_Center"].id  + "]") === -1)
						lbLog.addItem("C"+k.toString(), "Cost center " +  rsData[k]["Cost_Center"].id  + " does not exist in target model");
		lbLog.addItem("done", "Dataset check completed");

Listbox lbLog is used here to show the log of checked master data members.

2. The explained approach logic works via frontend, hence there are data limitations. The above solution is suitable for data set up to 500 records without any additional actions. In case number of records >500, in the first table we need to edit drill limitation of preview table:


Editing drill limitation

In case number of records exceeds ~1.5k records, the logic should be adjusted to perform copying of data from tabPreview to tabResult in batches.

3. In addition to master data check, the extended validation rules can be added, e.g. for loading only selected combinations of master data members.

4. Adding filters on the Tab 1 can also make possible to copy just selected data subset.


This approach shows how combined functionality of dataset and analytics application can be used to create a solution for flat file upload by end users, without giving them an access to data management of planning model and without involvement of additional solutions and components.

Additionally, it can be easily extended with any extra logic based on specific business requirements.


Assigned Tags

      You must be Logged on to comment or reply to a post.
      Author's profile photo Pedro Santiesteban
      Pedro Santiesteban

      Awesome blog Slava!

      Author's profile photo Nikhil Anand
      Nikhil Anand

      Great Blog ! The inclusion of data management step (Master Data for private dims and transaction data in Data Action with the latest QRC will make it more simpler for end users to do ad-hoc data loads.



      Author's profile photo Jef Baeyens
      Jef Baeyens

      Very smart, and well explained. Thank you!

      How is performance using this frontend script for a few thousand records (needing batches)?
      How is performance on small but very sparse data set (e.g. sales data)? Because I see challenges with the setUserInput() API that can only post to visible cells (unbooked data).

      Author's profile photo Slava Lopatin
      Slava Lopatin
      Blog Post Author

      hi Jef,
      thank you for the feedback and your questions.

      In fact, copying data from dataset into arrays and the subsequent copying from arrays into a table with planning model is almost instantaneous. Most of the time of the entire operation is spent on switching filters on target table.
      So for both cases you mentioned the performance will depend on number of batches (i.e., number of times you need to switch filters) and the models itself (i.e., number of dimensions, members, transaction data as it affects switching filters time).

      Author's profile photo Jef Baeyens
      Jef Baeyens

      Indeed. But not so much the setting of filters because you can pause refresh. The bottleneck really seems the unbooked cell combinations required by the setUserInput() API.
      Try this simple sparse dataset with just 10 rows (and 6 dimensions). It will blow up your browser, because it's actually generating 10^6 = 1M unbooked cells in the hidden table.
      So I hope SAP will work on a better solution or improved API that is scalable.

      Author's profile photo Sarah Kellermann
      Sarah Kellermann

      Very useful, thank you!!