diff --git a/docs-mintlify/docs.json b/docs-mintlify/docs.json index 8908be4c0b100..24d84115e3d61 100644 --- a/docs-mintlify/docs.json +++ b/docs-mintlify/docs.json @@ -583,7 +583,8 @@ "recipes/data-modeling/funnels", "recipes/data-modeling/cohort-retention", "recipes/data-modeling/xirr", - "recipes/data-modeling/dbt" + "recipes/data-modeling/dbt", + "recipes/data-modeling/custom-order" ] } ] diff --git a/docs-mintlify/recipes/data-modeling/custom-order.mdx b/docs-mintlify/recipes/data-modeling/custom-order.mdx new file mode 100644 index 0000000000000..5607f097a65fa --- /dev/null +++ b/docs-mintlify/recipes/data-modeling/custom-order.mdx @@ -0,0 +1,186 @@ +--- +title: Custom ordering for categorical values +description: "This recipe shows how to define a custom sort order for dimension values that don't follow alphabetical or numeric ordering." +--- + +## Use case + +When working with categorical dimensions like pipeline stages, priority levels, +or status values, you often need to sort them in a specific business-meaningful +order rather than alphabetically. For example, a sales pipeline might have +stages like *Pipeline*, *Best Case*, *Most Likely*, *Commit*, and *Closed* +that should always appear in that funnel order. + +Sometimes stages are prefixed with numbers (e.g., *1. Pipeline*, *2. Best +Case*) which makes alphabetical sorting work. But when they don't have +numbers, alphabetical order produces results that don't match the business +logic. + +There are two ways to solve this: + +- **At query time** — write a `CASE` expression directly in a [semantic +SQL][ref-sql-api] query. This is the fastest way to get results and works +great when you're exploring data in a [workbook][ref-workbooks] or asking AI +to build a query for you. +- **In the data model** — add a permanent dimension with the ordering logic. +This is the right choice when the same sort order is reused across many +queries, dashboards, or consumers. + +## Query-level approach + +You can define a custom ordering dimension directly in a semantic SQL query +without changing the data model. This is especially useful when working in +workbooks — you can ask AI to sort results in a specific order and it will +generate the appropriate `CASE` expression for you. + +```sql +SELECT + deals.forecast_category, + CASE + WHEN deals.forecast_category = 'Pipeline' THEN 1 + WHEN deals.forecast_category = 'Best Case' THEN 2 + WHEN deals.forecast_category = 'Most Likely' THEN 3 + WHEN deals.forecast_category = 'Commit' THEN 4 + WHEN deals.forecast_category = 'Closed' THEN 5 + ELSE 6 + END AS funnel_order, + MEASURE(total_amount) AS total_amount +FROM + deals +GROUP BY + 1, 2 +ORDER BY + 2 ASC +``` + +The `CASE` expression creates an inline `funnel_order` column that maps each +category to its position. The query then sorts by that column instead of by +the category name. + +This approach requires no changes to the data model and is ideal for ad-hoc +analysis. In a workbook, you can simply ask the AI assistant something like +*"sort forecast categories in pipeline order: Pipeline, Best Case, Most +Likely, Commit, Closed"* and it will generate a query like the one above. + +## Data model approach + +When the same custom order is needed across multiple queries, dashboards, or +BI tools, it's better to encode it as a dimension in the data model. This +way any consumer can sort by it without re-implementing the `CASE` logic. + +Consider the following data model with a `forecast_category` dimension that +has no inherent sort order: + + + +```yaml title="YAML" +cubes: + - name: deals + sql_table: deals + + dimensions: + - name: forecast_category + sql: forecast_category + type: string + + - name: forecast_category_order + sql: | + CASE + WHEN {forecast_category} = 'Pipeline' THEN 1 + WHEN {forecast_category} = 'Best Case' THEN 2 + WHEN {forecast_category} = 'Most Likely' THEN 3 + WHEN {forecast_category} = 'Commit' THEN 4 + WHEN {forecast_category} = 'Closed' THEN 5 + ELSE 6 + END + type: number + + measures: + - name: total_amount + sql: amount + type: sum +``` + +```javascript title="JavaScript" +cube(`deals`, { + sql_table: `deals`, + + dimensions: { + forecast_category: { + sql: `forecast_category`, + type: `string` + }, + + forecast_category_order: { + sql: ` + CASE + WHEN ${forecast_category} = 'Pipeline' THEN 1 + WHEN ${forecast_category} = 'Best Case' THEN 2 + WHEN ${forecast_category} = 'Most Likely' THEN 3 + WHEN ${forecast_category} = 'Commit' THEN 4 + WHEN ${forecast_category} = 'Closed' THEN 5 + ELSE 6 + END + `, + type: `number` + } + }, + + measures: { + total_amount: { + sql: `amount`, + type: `sum` + } + } +}) +``` + + + +The `forecast_category_order` dimension uses a `CASE` expression to assign a +numeric position to each category value. This dimension references the +`forecast_category` dimension so that the mapping stays consistent. + +The `ELSE 6` clause handles any unexpected values, placing them at the end +of the sort order. + +Once the dimension is in the data model, queries become straightforward: + +```sql +SELECT + forecast_category, + forecast_category_order, + MEASURE(total_amount) +FROM + deals +GROUP BY + 1, 2 +ORDER BY + 2 ASC +``` + +## Result + +Both approaches produce the same result — a business-meaningful funnel order +instead of alphabetical sorting: + +| Forecast Category | funnel_order | Total Amount | +| ----------------- | -----------: | -------------: | +| Pipeline | 1 | $17,830,500 | +| Best Case | 2 | $6,786,250 | +| Most Likely | 3 | $537,499.70 | +| Commit | 4 | $688,000 | +| Closed | 5 | $9,232,800.46 | + +This pattern works for any set of categorical values that need a custom order: +support ticket priorities, project phases, approval workflows, and so on. + +Use the **query-level approach** when you need a quick, one-off sort order +while exploring data. Use the **data model approach** when the ordering is a +stable business rule that should be available to all consumers. + + +[ref-data-apis]: /reference#data-apis +[ref-sql-api]: /reference/sql-api +[ref-custom-sorting]: /recipes/core-data-api/sorting +[ref-workbooks]: /docs/workspace/workbooks diff --git a/docs-mintlify/recipes/index.mdx b/docs-mintlify/recipes/index.mdx index 363f21c3bbee4..75c375671d11f 100644 --- a/docs-mintlify/recipes/index.mdx +++ b/docs-mintlify/recipes/index.mdx @@ -4,7 +4,7 @@ description: Step-by-step tutorials and best practices for getting the most out mode: wide --- -Explore **38 recipes** across data modeling, calculations, analytics patterns, +Explore **39 recipes** across data modeling, calculations, analytics patterns, pre-aggregations, configuration, APIs, and AI. ## Data Modeling @@ -28,6 +28,9 @@ pre-aggregations, configuration, APIs, and AI. Combine multiple database tables that relate to the same entity into a single cube. + + Define a custom sort order for categorical values like pipeline stages that don't sort alphabetically. + ## Calculations & Metrics