Skip to content

Commit 715370b

Browse files
committed
fix(schema-compiler): Preserve full time buckets in period-over-period queries
Switches from INNER JOIN to LEFT JOIN in outerMeasuresJoinFullKeyQueryAggregate to prevent dropped rows when previous-period data is missing. Adds fixture and tests for period-over-period measures.
1 parent 7b4e4ad commit 715370b

File tree

3 files changed

+201
-2
lines changed

3 files changed

+201
-2
lines changed

packages/cubejs-schema-compiler/src/adapter/BaseQuery.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1227,7 +1227,7 @@ export class BaseQuery {
12271227
const join = R.drop(1, toJoin)
12281228
.map(
12291229
(q, i) => (this.dimensionAliasNames().length ?
1230-
`INNER JOIN ${this.wrapInParenthesis((q))} as q_${i + 1} ON ${this.dimensionsJoinCondition(`q_${i}`, `q_${i + 1}`)}` :
1230+
`LEFT JOIN ${this.wrapInParenthesis((q))} as q_${i + 1} ON ${this.dimensionsJoinCondition(`q_${i}`, `q_${i + 1}`)}` :
12311231
`, ${this.wrapInParenthesis(q)} as q_${i + 1}`),
12321232
).join('\n');
12331233

packages/cubejs-schema-compiler/test/integration/postgres/PostgresDBRunner.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ export class PostgresDBRunner extends BaseDbRunner {
5454
tx.query('CREATE TEMPORARY TABLE right_table (id INT, total DOUBLE PRECISION, description character varying) ON COMMIT DROP'),
5555
tx.query('CREATE TEMPORARY TABLE mid_table (id INT, left_id INT, right_id INT) ON COMMIT DROP'),
5656
tx.query('CREATE TEMPORARY TABLE compound_key_cards (id_a INT, id_b INT, visitor_id INT, visitor_checkin_id INT, visit_rank INT) ON COMMIT DROP'),
57+
tx.query('CREATE TEMPORARY TABLE sales (id INT PRIMARY KEY, date DATE, category TEXT, region TEXT, amount DECIMAL) ON COMMIT DROP'),
5758
tx.query(`
5859
INSERT INTO
5960
visitors
@@ -126,6 +127,23 @@ export class PostgresDBRunner extends BaseDbRunner {
126127
(2, 2, 3, 6, 7),
127128
(2, 2, 2, 4, 8),
128129
(2, 3, 4, 5, 2);
130+
`),
131+
tx.query(`
132+
INSERT INTO
133+
sales
134+
(id, date, category, region, amount) VALUES
135+
(1, '2023-01-01', 'Electronics', 'North', 300),
136+
(2, '2023-01-01', 'Electronics', 'South', 200),
137+
(3, '2023-01-01', 'Clothing', 'North', 200),
138+
(4, '2023-01-01', 'Clothing', 'South', 100),
139+
(5, '2023-02-01', 'Electronics', 'North', 400),
140+
(6, '2023-02-01', 'Electronics', 'South', 200),
141+
(7, '2023-02-01', 'Clothing', 'North', 300),
142+
(8, '2023-02-01', 'Clothing', 'South', 100),
143+
(9, '2023-03-01', 'Electronics', 'North', 500),
144+
(10, '2023-07-01', 'Electronics', 'South', 300),
145+
(11, '2023-12-01', 'Clothing', 'North', 400),
146+
(12, '2023-12-01', 'Clothing', 'South', 200)
129147
`)
130148
]);
131149
}

packages/cubejs-schema-compiler/test/integration/postgres/sql-generation.test.ts

Lines changed: 182 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -815,7 +815,75 @@ SELECT 1 AS revenue, cast('2024-01-01' AS timestamp) as time UNION ALL
815815
join_path: 'UngroupedMeasureWithFilter1.UngroupedMeasureWithFilter2',
816816
includes: ['count']
817817
}]
818-
})
818+
});
819+
820+
cube(\`sales\`, {
821+
sql: \`SELECT * FROM sales\`,
822+
823+
dimensions: {
824+
date: {
825+
sql: \`date\`,
826+
type: \`time\`
827+
},
828+
id: {
829+
sql: \`id\`,
830+
type: \`number\`,
831+
primaryKey: true
832+
},
833+
category: {
834+
sql: \`category\`,
835+
type: \`string\`
836+
},
837+
region: {
838+
sql: \`region\`,
839+
type: \`string\`
840+
}
841+
},
842+
843+
measures: {
844+
current_month_sum: {
845+
sql: 'amount',
846+
type: 'sum',
847+
rolling_window: {
848+
trailing: '1 month',
849+
offset: 'end'
850+
}
851+
},
852+
previous_month_sum: {
853+
sql: 'amount',
854+
type: 'sum',
855+
rolling_window: {
856+
trailing: '1 month',
857+
offset: 'start'
858+
}
859+
},
860+
month_over_month_ratio: {
861+
type: \`number\`,
862+
sql: \`\${current_month_sum} / NULLIF(\${previous_month_sum}, 0)\`
863+
},
864+
current_year_sum: {
865+
sql: 'amount',
866+
type: 'sum',
867+
rolling_window: {
868+
trailing: '1 year',
869+
offset: 'end'
870+
}
871+
},
872+
previous_year_sum: {
873+
sql: 'amount',
874+
type: 'sum',
875+
rolling_window: {
876+
trailing: '1 year',
877+
offset: 'start'
878+
}
879+
},
880+
year_over_year_ratio: {
881+
type: \`number\`,
882+
sql: \`\${current_year_sum} / NULLIF(\${previous_year_sum}, 0)\`
883+
}
884+
}
885+
});
886+
819887
`);
820888

821889
it('simple join', async () => {
@@ -1412,6 +1480,8 @@ SELECT 1 AS revenue, cast('2024-01-01' AS timestamp) as time UNION ALL
14121480
}],
14131481
timezone: 'America/Los_Angeles'
14141482
}, [
1483+
{ visitors__created_at_day: '2017-01-02T00:00:00.000Z', visitors__cagr_day: null, visitors__revenue: '100', visitors__revenue_day_ago: null },
1484+
{ visitors__created_at_day: '2017-01-04T00:00:00.000Z', visitors__cagr_day: null, visitors__revenue: '200', visitors__revenue_day_ago: null },
14151485
{ visitors__created_at_day: '2017-01-05T00:00:00.000Z', visitors__cagr_day: '150', visitors__revenue: '300', visitors__revenue_day_ago: '200' },
14161486
{ visitors__created_at_day: '2017-01-06T00:00:00.000Z', visitors__cagr_day: '300', visitors__revenue: '900', visitors__revenue_day_ago: '300' }
14171487
]));
@@ -3367,6 +3437,8 @@ SELECT 1 AS revenue, cast('2024-01-01' AS timestamp) as time UNION ALL
33673437
],
33683438
order: [{
33693439
id: 'visitors.source'
3440+
}, {
3441+
id: 'visitors.updated_at'
33703442
}],
33713443
timezone: 'UTC',
33723444
},
@@ -3478,6 +3550,8 @@ SELECT 1 AS revenue, cast('2024-01-01' AS timestamp) as time UNION ALL
34783550
],
34793551
order: [{
34803552
id: 'visitors_multi_stage.source'
3553+
}, {
3554+
id: 'visitors_multi_stage.updated_at'
34813555
}],
34823556
timezone: 'UTC',
34833557
},
@@ -3991,4 +4065,111 @@ SELECT 1 AS revenue, cast('2024-01-01' AS timestamp) as time UNION ALL
39914065
}]
39924066
);
39934067
});
4068+
4069+
it('period over period time only', async () => runQueryTest({
4070+
measures: [
4071+
'sales.current_month_sum',
4072+
'sales.previous_month_sum',
4073+
'sales.month_over_month_ratio'
4074+
],
4075+
timeDimensions: [{
4076+
dimension: 'sales.date',
4077+
granularity: 'month',
4078+
dateRange: ['2023-01-01', '2023-12-31']
4079+
}],
4080+
order: [{
4081+
id: 'sales.date'
4082+
}],
4083+
timezone: 'UTC'
4084+
}, [
4085+
{ sales__date_month: '2023-01-01T00:00:00.000Z', sales__current_month_sum: '800', sales__previous_month_sum: null, sales__month_over_month_ratio: null },
4086+
{ sales__date_month: '2023-02-01T00:00:00.000Z', sales__current_month_sum: '1000', sales__previous_month_sum: '800', sales__month_over_month_ratio: '1.2500000000000000' },
4087+
{ sales__date_month: '2023-03-01T00:00:00.000Z', sales__current_month_sum: '500', sales__previous_month_sum: '1000', sales__month_over_month_ratio: '0.50000000000000000000' },
4088+
{ sales__date_month: '2023-04-01T00:00:00.000Z', sales__current_month_sum: null, sales__previous_month_sum: '500', sales__month_over_month_ratio: null },
4089+
{ sales__date_month: '2023-05-01T00:00:00.000Z', sales__current_month_sum: null, sales__previous_month_sum: null, sales__month_over_month_ratio: null },
4090+
{ sales__date_month: '2023-06-01T00:00:00.000Z', sales__current_month_sum: null, sales__previous_month_sum: null, sales__month_over_month_ratio: null },
4091+
{ sales__date_month: '2023-07-01T00:00:00.000Z', sales__current_month_sum: '300', sales__previous_month_sum: null, sales__month_over_month_ratio: null },
4092+
{ sales__date_month: '2023-08-01T00:00:00.000Z', sales__current_month_sum: null, sales__previous_month_sum: '300', sales__month_over_month_ratio: null },
4093+
{ sales__date_month: '2023-09-01T00:00:00.000Z', sales__current_month_sum: null, sales__previous_month_sum: null, sales__month_over_month_ratio: null },
4094+
{ sales__date_month: '2023-10-01T00:00:00.000Z', sales__current_month_sum: null, sales__previous_month_sum: null, sales__month_over_month_ratio: null },
4095+
{ sales__date_month: '2023-11-01T00:00:00.000Z', sales__current_month_sum: null, sales__previous_month_sum: null, sales__month_over_month_ratio: null },
4096+
{ sales__date_month: '2023-12-01T00:00:00.000Z', sales__current_month_sum: '600', sales__previous_month_sum: null, sales__month_over_month_ratio: null }
4097+
]));
4098+
4099+
it('period over period one dimension', async () => runQueryTest({
4100+
measures: [
4101+
'sales.current_month_sum',
4102+
'sales.previous_month_sum',
4103+
'sales.month_over_month_ratio'
4104+
],
4105+
dimensions: ['sales.category'],
4106+
timeDimensions: [{
4107+
dimension: 'sales.date',
4108+
granularity: 'month',
4109+
dateRange: ['2023-01-01', '2023-12-31']
4110+
}],
4111+
order: [{
4112+
id: 'sales.date'
4113+
}, {
4114+
id: 'sales.category'
4115+
}],
4116+
timezone: 'UTC'
4117+
}, [
4118+
{ sales__date_month: '2023-01-01T00:00:00.000Z', sales__category: 'Clothing', sales__current_month_sum: '300', sales__previous_month_sum: null, sales__month_over_month_ratio: null },
4119+
{ sales__date_month: '2023-01-01T00:00:00.000Z', sales__category: 'Electronics', sales__current_month_sum: '500', sales__previous_month_sum: null, sales__month_over_month_ratio: null },
4120+
{ sales__date_month: '2023-02-01T00:00:00.000Z', sales__category: 'Clothing', sales__current_month_sum: '400', sales__previous_month_sum: '300', sales__month_over_month_ratio: '1.3333333333333333' },
4121+
{ sales__date_month: '2023-02-01T00:00:00.000Z', sales__category: 'Electronics', sales__current_month_sum: '600', sales__previous_month_sum: '500', sales__month_over_month_ratio: '1.2000000000000000' },
4122+
{ sales__date_month: '2023-03-01T00:00:00.000Z', sales__category: 'Electronics', sales__current_month_sum: '500', sales__previous_month_sum: '600', sales__month_over_month_ratio: '0.83333333333333333333' },
4123+
{ sales__date_month: '2023-04-01T00:00:00.000Z', sales__category: null, sales__current_month_sum: null, sales__previous_month_sum: null, sales__month_over_month_ratio: null },
4124+
{ sales__date_month: '2023-05-01T00:00:00.000Z', sales__category: null, sales__current_month_sum: null, sales__previous_month_sum: null, sales__month_over_month_ratio: null },
4125+
{ sales__date_month: '2023-06-01T00:00:00.000Z', sales__category: null, sales__current_month_sum: null, sales__previous_month_sum: null, sales__month_over_month_ratio: null },
4126+
{ sales__date_month: '2023-07-01T00:00:00.000Z', sales__category: 'Electronics', sales__current_month_sum: '300', sales__previous_month_sum: null, sales__month_over_month_ratio: null },
4127+
{ sales__date_month: '2023-08-01T00:00:00.000Z', sales__category: null, sales__current_month_sum: null, sales__previous_month_sum: null, sales__month_over_month_ratio: null },
4128+
{ sales__date_month: '2023-09-01T00:00:00.000Z', sales__category: null, sales__current_month_sum: null, sales__previous_month_sum: null, sales__month_over_month_ratio: null },
4129+
{ sales__date_month: '2023-10-01T00:00:00.000Z', sales__category: null, sales__current_month_sum: null, sales__previous_month_sum: null, sales__month_over_month_ratio: null },
4130+
{ sales__date_month: '2023-11-01T00:00:00.000Z', sales__category: null, sales__current_month_sum: null, sales__previous_month_sum: null, sales__month_over_month_ratio: null },
4131+
{ sales__date_month: '2023-12-01T00:00:00.000Z', sales__category: 'Clothing', sales__current_month_sum: '600', sales__previous_month_sum: null, sales__month_over_month_ratio: null }
4132+
]));
4133+
4134+
it('period over period two dimensions', async () => runQueryTest({
4135+
measures: [
4136+
'sales.current_month_sum',
4137+
'sales.previous_month_sum',
4138+
'sales.month_over_month_ratio'
4139+
],
4140+
dimensions: ['sales.category', 'sales.region'],
4141+
timeDimensions: [{
4142+
dimension: 'sales.date',
4143+
granularity: 'month',
4144+
dateRange: ['2023-01-01', '2023-12-31']
4145+
}],
4146+
order: [{
4147+
id: 'sales.date'
4148+
}, {
4149+
id: 'sales.category'
4150+
}, {
4151+
id: 'sales.region'
4152+
}],
4153+
timezone: 'UTC'
4154+
}, [
4155+
{ sales__date_month: '2023-01-01T00:00:00.000Z', sales__category: 'Clothing', sales__region: 'North', sales__current_month_sum: '200', sales__previous_month_sum: null, sales__month_over_month_ratio: null },
4156+
{ sales__date_month: '2023-01-01T00:00:00.000Z', sales__category: 'Clothing', sales__region: 'South', sales__current_month_sum: '100', sales__previous_month_sum: null, sales__month_over_month_ratio: null },
4157+
{ sales__date_month: '2023-01-01T00:00:00.000Z', sales__category: 'Electronics', sales__region: 'North', sales__current_month_sum: '300', sales__previous_month_sum: null, sales__month_over_month_ratio: null },
4158+
{ sales__date_month: '2023-01-01T00:00:00.000Z', sales__category: 'Electronics', sales__region: 'South', sales__current_month_sum: '200', sales__previous_month_sum: null, sales__month_over_month_ratio: null },
4159+
{ sales__date_month: '2023-02-01T00:00:00.000Z', sales__category: 'Clothing', sales__region: 'North', sales__current_month_sum: '300', sales__previous_month_sum: '200', sales__month_over_month_ratio: '1.5000000000000000' },
4160+
{ sales__date_month: '2023-02-01T00:00:00.000Z', sales__category: 'Clothing', sales__region: 'South', sales__current_month_sum: '100', sales__previous_month_sum: '100', sales__month_over_month_ratio: '1.00000000000000000000' },
4161+
{ sales__date_month: '2023-02-01T00:00:00.000Z', sales__category: 'Electronics', sales__region: 'North', sales__current_month_sum: '400', sales__previous_month_sum: '300', sales__month_over_month_ratio: '1.3333333333333333' },
4162+
{ sales__date_month: '2023-02-01T00:00:00.000Z', sales__category: 'Electronics', sales__region: 'South', sales__current_month_sum: '200', sales__previous_month_sum: '200', sales__month_over_month_ratio: '1.00000000000000000000' },
4163+
{ sales__date_month: '2023-03-01T00:00:00.000Z', sales__category: 'Electronics', sales__region: 'North', sales__current_month_sum: '500', sales__previous_month_sum: '400', sales__month_over_month_ratio: '1.2500000000000000' },
4164+
{ sales__date_month: '2023-04-01T00:00:00.000Z', sales__category: null, sales__region: null, sales__current_month_sum: null, sales__previous_month_sum: null, sales__month_over_month_ratio: null },
4165+
{ sales__date_month: '2023-05-01T00:00:00.000Z', sales__category: null, sales__region: null, sales__current_month_sum: null, sales__previous_month_sum: null, sales__month_over_month_ratio: null },
4166+
{ sales__date_month: '2023-06-01T00:00:00.000Z', sales__category: null, sales__region: null, sales__current_month_sum: null, sales__previous_month_sum: null, sales__month_over_month_ratio: null },
4167+
{ sales__date_month: '2023-07-01T00:00:00.000Z', sales__category: 'Electronics', sales__region: 'South', sales__current_month_sum: '300', sales__previous_month_sum: null, sales__month_over_month_ratio: null },
4168+
{ sales__date_month: '2023-08-01T00:00:00.000Z', sales__category: null, sales__region: null, sales__current_month_sum: null, sales__previous_month_sum: null, sales__month_over_month_ratio: null },
4169+
{ sales__date_month: '2023-09-01T00:00:00.000Z', sales__category: null, sales__region: null, sales__current_month_sum: null, sales__previous_month_sum: null, sales__month_over_month_ratio: null },
4170+
{ sales__date_month: '2023-10-01T00:00:00.000Z', sales__category: null, sales__region: null, sales__current_month_sum: null, sales__previous_month_sum: null, sales__month_over_month_ratio: null },
4171+
{ sales__date_month: '2023-11-01T00:00:00.000Z', sales__category: null, sales__region: null, sales__current_month_sum: null, sales__previous_month_sum: null, sales__month_over_month_ratio: null },
4172+
{ sales__date_month: '2023-12-01T00:00:00.000Z', sales__category: 'Clothing', sales__region: 'North', sales__current_month_sum: '400', sales__previous_month_sum: null, sales__month_over_month_ratio: null },
4173+
{ sales__date_month: '2023-12-01T00:00:00.000Z', sales__category: 'Clothing', sales__region: 'South', sales__current_month_sum: '200', sales__previous_month_sum: null, sales__month_over_month_ratio: null }
4174+
]));
39944175
});

0 commit comments

Comments
 (0)