Oftentimes, Salesforce developers create object models that add complexity when building analytics solutions. Since there are certain limits and cost factors inherent to Salesforce, developers will structure their solutions to best align with their own needs — and Analytics developers cannot always influence those decisions upstream.

A common example is where a Salesforce object stores related metrics horizontally in columns rather than in rows. A typical case is a Survey Responses object where each row represents an entry (the attribute), the fields represent the survey questions (the measures), and the cells contain the score (the value). The challenge: how do you find which question has the highest or lowest score per entry, or calculate an average score per entry, without resorting to compare table formulas or complex filtering?

The simplest answer: transpose the columns into rows using a dataflow.

The dataset

This 3-step dataflow approach will transpose your dataset to enable clean row-level aggregations. The example below uses a five-measure dataset — but the pattern scales to any number of measures.

The dataset

The problem visualised: Computing the max survey score by question gives you one bar per question — five bars for five questions. There is no way to ask “which question scored highest for entry X?” because the data is not structured to support that aggregation. Transposing the data reshapes it so every score becomes its own row, with the question name and entry ID as dimensions — enabling any row-level analysis you need.

The dataset

Step 1
Create one computeExpression node per measure field

For a five-measure dataset you will create five computeExpression nodes — one for each survey question. Each node contains three computed fields. Keep the field names identical across all nodes — this is critical for the Append step to work cleanly.

Field name Type SAQL Expression Notes
Node 1 — Question 1
Entry Text 'Survey_Entry' Map to the attribute field name from your source dataset
Score Numeric 'Question1' Map to the first measure field. Set Precision and Scale to match source data.
Question Text "Question 1" Optional but recommended — stores the human-readable question label for use in charts.
Nodes 2–5 — repeat for each remaining measure
Entry Text 'Survey_Entry' Same as Node 1 — must be identical
Score Numeric 'Question2', 'Question3' Update the measure field name for each node
Question Text "Question 2", "Question 3" Update the label for each node
Video walkthrough — Step 1: Setting up five computeExpression nodes, creating the three computed fields in each, and verifying consistent field naming across all nodes. Originally published at einsteinplusanalytics.com.
Step 2
Append all computeExpression nodes into a single dataset

Use the Append transformation to combine all five computeExpression nodes into one dataset. The order of selection does not matter.

Because all computed field names are identical across every node, you do not need to enable “Apply disjoint schema” — the Append will align all fields automatically.

The result is a single dataset where every original column is now a separate row, each tagged with its Entry ID, Score value, and Question label.

Video walkthrough — Step 2: Connecting all five computeExpression nodes into the Append transformation and confirming the schema aligns without disjoint schema enabled. Originally published at einsteinplusanalytics.com.
Step 3 — Optional but recommended
Drop unused measure fields with sliceDataset

After the Append, your dataset still contains the original five measure columns (Question1, Question2…) alongside the new Entry, Score, and Question fields. These original columns are now redundant and will cause confusion in the Explorer.

Add a sliceDataset node after the Append to drop the original measure fields. Keep only: Entry, Score, and Question.

Register the dataset and explore. You can now visualise scores by entry, by question, or both — as grouped bars, stacked bars, or any row-level aggregation.

Video walkthrough — Step 3: Adding the sliceDataset node to drop the original measure fields, registering the transposed dataset, and exploring the final result in CRM Analytics. Originally published at einsteinplusanalytics.com.
Final result walkthrough: Demonstrating the transposed dataset in CRM Analytics — showing scores by entry and by question in a stacked bar chart, and confirming that row-level max and average aggregations now work as expected. Originally published at einsteinplusanalytics.com.

Bonus tip

If your use case requires only the minimum or maximum score per entry, add a computeRelative transformation after the sliceDataset node. This computes a field containing the minimum or maximum value across the entire dataset at the row level — giving you a single scalar per entry without any additional SAQL in the dashboard.

About the Author
Rashid Haq
Enterprise Analytics Architect · CRM Analytics Specialist

22+ years delivering enterprise analytics. CRM Analytics practitioner, Salesforce Analytics Champion 2020, Tableau CRM Ambassador 2021–23. Originally published at einsteinplusanalytics.com.

Back to all articles Resource Library