Repository: lightdash/lightdash (2025-08-20T17:21:44Z)

Issue:
Reference custom dimension in table calculation
We can't reference custom dimensions in table calculations at the moment.



Discussion:
This issue was mentioned by a user in slack: https://lightdash.slack.com/archives/C047QNA8YPR/p1725384324407429?thread_ts=1725384324.407429&cid=C047QNA8YPR
This issue was mentioned by a user in slack: https://lightdash.slack.com/archives/C04M74U0W12/p1732117445658749?thread_ts=1732117445.658749&cid=C04M74U0W12
Use case from customer: 

>hey, I am trying to calculate the % of total but partition by a custom dimension that I've created to get the % per that custom dimension group, so let's say that the metric is visits and my custom dimension is "months since signup", I am trying to get the column calculating the % of visits per each "months since signup" cohort

>this can be done without partitioning if I don't have other dimensions in place of course, but it's easier for me with other dimensions at the same time
This issue was mentioned by a user in slack: https://lightdash-community.slack.com/archives/C03MG2VTR08/p1741277465521499?thread_ts=1741277465.521499&cid=C03MG2VTR08
This issue was mentioned by a user in slack: https://lightdash.slack.com/archives/C06J189BQCR/p1754898602778059?thread_ts=1754898602.778059&cid=C06J189BQCR
This issue was mentioned by a user in slack: https://lightdash.slack.com/archives/C05VBJJ5UCC/p1755666517004939?thread_ts=1755666517.004939&cid=C05VBJJ5UCC
:tada: This issue has been resolved in version 0.1966.0 :tada:

The release is available on:
- `0.1966.0`
- [GitHub release](https://github.com/lightdash/lightdash/releases/tag/0.1966.0)

Your **[semantic-release](https://github.com/semantic-release/semantic-release)** bot :package::rocket:



Patch:
diff --git a/packages/backend/src/queryCompiler.ts b/packages/backend/src/queryCompiler.ts
index d8797c46700dd..bda9a3d7355c5 100644
--- a/packages/backend/src/queryCompiler.ts
+++ b/packages/backend/src/queryCompiler.ts
@@ -108,7 +108,12 @@ const compileTableCalculation = (
                 return `${quoteChar}${p1}${quoteChar}`;
             }
 
-            // Otherwise, treat it as a field reference
+            // If the field is already valid, return it
+            if (validFieldIds.includes(p1)) {
+                return `${quoteChar}${p1}${quoteChar}`;
+            }
+
+            // Otherwise, try to convert it as a field reference (table.field format)
             const fieldId = convertFieldRefToFieldId(p1);
             if (validFieldIds.includes(fieldId)) {
                 return `${quoteChar}${fieldId}${quoteChar}`;
@@ -206,11 +211,6 @@ export const compileMetricQuery = ({
     const fieldQuoteChar = warehouseSqlBuilder.getFieldQuoteChar();
     const validFieldIds = [...metricQuery.dimensions, ...metricQuery.metrics];
 
-    const compiledTableCalculations = compileTableCalculations(
-        metricQuery.tableCalculations,
-        validFieldIds,
-        fieldQuoteChar,
-    );
     const compiledAdditionalMetrics = (metricQuery.additionalMetrics || []).map(
         (additionalMetric) =>
             compileAdditionalMetric({
@@ -231,6 +231,12 @@ export const compileMetricQuery = ({
             ),
     );
 
+    const compiledTableCalculations = compileTableCalculations(
+        metricQuery.tableCalculations,
+        validFieldIds,
+        fieldQuoteChar,
+    );
+
     return {
         ...metricQuery,
         compiledTableCalculations,


Test patch:
diff --git a/packages/backend/src/queryCompiler.test.ts b/packages/backend/src/queryCompiler.test.ts
index 210ae44a19859..0dcd676195684 100644
--- a/packages/backend/src/queryCompiler.test.ts
+++ b/packages/backend/src/queryCompiler.test.ts
@@ -1,4 +1,10 @@
-import { CompiledMetricQuery, CompileError } from '@lightdash/common';
+import {
+    CompiledMetricQuery,
+    CompileError,
+    CustomDimensionType,
+    DimensionType,
+    MetricType,
+} from '@lightdash/common';
 import { compileMetricQuery } from './queryCompiler';
 import {
     EXPLORE,
@@ -342,3 +348,137 @@ test('Should handle table calculation referencing two other table calculations',
     // Combined calc should reference both from the most recent CTE that contains them
     expect(combinedCalc?.compiledSql).toBe('"base_calc_1" + "base_calc_2"');
 });
+
+test('Should compile table calculations that reference custom dimensions', () => {
+    const metricQueryWithCustomDimension = {
+        ...METRIC_QUERY_VALID_REFERENCES,
+        dimensions: [
+            ...METRIC_QUERY_VALID_REFERENCES.dimensions,
+            'custom_dim_1',
+        ],
+        customDimensions: [
+            {
+                id: 'custom_dim_1',
+                name: 'Custom Dim 1',
+                table: 'table1',
+                type: CustomDimensionType.SQL,
+                sql: 'LENGTH("table1"."dim_1")', // Use direct SQL that doesn't need field resolution
+                dimensionType: DimensionType.NUMBER,
+            } as const,
+        ],
+        tableCalculations: [
+            {
+                name: 'calc_with_custom_dim',
+                displayName: 'Calc with Custom Dimension',
+                sql: '${custom_dim_1} * 2',
+            },
+        ],
+    };
+
+    const result = compileMetricQuery({
+        explore: EXPLORE,
+        metricQuery: metricQueryWithCustomDimension,
+        warehouseSqlBuilder: warehouseClientMock,
+        availableParameters: [],
+    });
+
+    const calcWithCustomDim = result.compiledTableCalculations.find(
+        (c) => c.name === 'calc_with_custom_dim',
+    );
+
+    // Should successfully reference the custom dimension by its id
+    expect(calcWithCustomDim?.compiledSql).toBe('"custom_dim_1" * 2');
+});
+
+test('Should compile table calculations that reference additional metrics', () => {
+    const metricQueryWithAdditionalMetric = {
+        ...METRIC_QUERY_VALID_REFERENCES,
+        metrics: [
+            ...METRIC_QUERY_VALID_REFERENCES.metrics,
+            'table1_custom_metric_1',
+        ],
+        additionalMetrics: [
+            {
+                name: 'custom_metric_1',
+                table: 'table1',
+                type: MetricType.SUM,
+                sql: '${TABLE}.dim_1', // Use the existing dimension from mock
+            },
+        ],
+        tableCalculations: [
+            {
+                name: 'calc_with_custom_metric',
+                displayName: 'Calc with Custom Metric',
+                sql: '${table1_custom_metric_1} + 100',
+            },
+        ],
+    };
+
+    const result = compileMetricQuery({
+        explore: EXPLORE,
+        metricQuery: metricQueryWithAdditionalMetric,
+        warehouseSqlBuilder: warehouseClientMock,
+        availableParameters: [],
+    });
+
+    const calcWithCustomMetric = result.compiledTableCalculations.find(
+        (c) => c.name === 'calc_with_custom_metric',
+    );
+
+    // Should successfully reference the additional metric using table_name format
+    expect(calcWithCustomMetric?.compiledSql).toBe(
+        '"table1_custom_metric_1" + 100',
+    );
+});
+
+test('Should compile table calculations that reference both custom dimensions and additional metrics', () => {
+    const metricQueryWithBoth = {
+        ...METRIC_QUERY_VALID_REFERENCES,
+        dimensions: [...METRIC_QUERY_VALID_REFERENCES.dimensions, 'age_range'],
+        metrics: [
+            ...METRIC_QUERY_VALID_REFERENCES.metrics,
+            'table1_custom_count',
+        ],
+        customDimensions: [
+            {
+                id: 'age_range',
+                name: 'Age Range',
+                table: 'table1',
+                type: CustomDimensionType.SQL,
+                sql: 'CASE WHEN LENGTH("table1"."dim_1") < 5 THEN \'Young\' ELSE \'Old\' END', // Use direct SQL
+                dimensionType: DimensionType.STRING,
+            } as const,
+        ],
+        additionalMetrics: [
+            {
+                name: 'custom_count',
+                table: 'table1',
+                type: MetricType.COUNT,
+                sql: '${TABLE}.dim_1', // Use existing dimension
+            },
+        ],
+        tableCalculations: [
+            {
+                name: 'combined_calc',
+                displayName: 'Combined Calc',
+                sql: "CASE WHEN ${age_range} = 'Young' THEN ${table1_custom_count} * 2 ELSE ${table1_custom_count} END",
+            },
+        ],
+    };
+
+    const result = compileMetricQuery({
+        explore: EXPLORE,
+        metricQuery: metricQueryWithBoth,
+        warehouseSqlBuilder: warehouseClientMock,
+        availableParameters: [],
+    });
+
+    const combinedCalc = result.compiledTableCalculations.find(
+        (c) => c.name === 'combined_calc',
+    );
+
+    // Should successfully reference both custom dimension and additional metric
+    expect(combinedCalc?.compiledSql).toBe(
+        'CASE WHEN "age_range" = \'Young\' THEN "table1_custom_count" * 2 ELSE "table1_custom_count" END',
+    );
+});