Skip to content

Commit ab02440

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 32f9b79 commit ab02440

File tree

3 files changed

+204
-5
lines changed

3 files changed

+204
-5
lines changed

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -1083,7 +1083,7 @@ export class BaseQuery {
10831083
const join = R.drop(1, toJoin)
10841084
.map(
10851085
(q, i) => (this.dimensionAliasNames().length ?
1086-
`INNER JOIN ${this.wrapInParenthesis((q))} as q_${i + 1} ON ${this.dimensionsJoinCondition(`q_${i}`, `q_${i + 1}`)}` :
1086+
`LEFT JOIN ${this.wrapInParenthesis((q))} as q_${i + 1} ON ${this.dimensionsJoinCondition(`q_${i}`, `q_${i + 1}`)}` :
10871087
`, ${this.wrapInParenthesis(q)} as q_${i + 1}`),
10881088
).join('\n');
10891089

@@ -4357,4 +4357,4 @@ export class BaseQuery {
43574357
}
43584358
));
43594359
}
4360-
}
4360+
}

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

+19-1
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
}
@@ -173,4 +191,4 @@ export const dbRunner = new PostgresDBRunner();
173191
// eslint-disable-next-line no-undef
174192
afterAll(async () => {
175193
await dbRunner.tearDown();
176-
});
194+
});

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

+183-2
Original file line numberDiff line numberDiff line change
@@ -752,7 +752,75 @@ SELECT 1 AS revenue, cast('2024-01-01' AS timestamp) as time UNION ALL
752752
join_path: 'UngroupedMeasureWithFilter1.UngroupedMeasureWithFilter2',
753753
includes: ['count']
754754
}]
755-
})
755+
});
756+
757+
cube(\`sales\`, {
758+
sql: \`SELECT * FROM sales\`,
759+
760+
dimensions: {
761+
date: {
762+
sql: \`date\`,
763+
type: \`time\`
764+
},
765+
id: {
766+
sql: \`id\`,
767+
type: \`number\`,
768+
primaryKey: true
769+
},
770+
category: {
771+
sql: \`category\`,
772+
type: \`string\`
773+
},
774+
region: {
775+
sql: \`region\`,
776+
type: \`string\`
777+
}
778+
},
779+
780+
measures: {
781+
current_month_sum: {
782+
sql: 'amount',
783+
type: 'sum',
784+
rolling_window: {
785+
trailing: '1 month',
786+
offset: 'end'
787+
}
788+
},
789+
previous_month_sum: {
790+
sql: 'amount',
791+
type: 'sum',
792+
rolling_window: {
793+
trailing: '1 month',
794+
offset: 'start'
795+
}
796+
},
797+
month_over_month_ratio: {
798+
type: \`number\`,
799+
sql: \`\${current_month_sum} / NULLIF(\${previous_month_sum}, 0)\`
800+
},
801+
current_year_sum: {
802+
sql: 'amount',
803+
type: 'sum',
804+
rolling_window: {
805+
trailing: '1 year',
806+
offset: 'end'
807+
}
808+
},
809+
previous_year_sum: {
810+
sql: 'amount',
811+
type: 'sum',
812+
rolling_window: {
813+
trailing: '1 year',
814+
offset: 'start'
815+
}
816+
},
817+
year_over_year_ratio: {
818+
type: \`number\`,
819+
sql: \`\${current_year_sum} / NULLIF(\${previous_year_sum}, 0)\`
820+
}
821+
}
822+
});
823+
756824
`);
757825

758826
it('simple join', async () => {
@@ -1343,6 +1411,8 @@ SELECT 1 AS revenue, cast('2024-01-01' AS timestamp) as time UNION ALL
13431411
}],
13441412
timezone: 'America/Los_Angeles'
13451413
}, [
1414+
{ visitors__created_at_day: '2017-01-02T00:00:00.000Z', visitors__cagr_day: null, visitors__revenue: '100', visitors__revenue_day_ago: null },
1415+
{ visitors__created_at_day: '2017-01-04T00:00:00.000Z', visitors__cagr_day: null, visitors__revenue: '200', visitors__revenue_day_ago: null },
13461416
{ visitors__created_at_day: '2017-01-05T00:00:00.000Z', visitors__cagr_day: '150', visitors__revenue: '300', visitors__revenue_day_ago: '200' },
13471417
{ visitors__created_at_day: '2017-01-06T00:00:00.000Z', visitors__cagr_day: '300', visitors__revenue: '900', visitors__revenue_day_ago: '300' }
13481418
]));
@@ -3173,6 +3243,8 @@ SELECT 1 AS revenue, cast('2024-01-01' AS timestamp) as time UNION ALL
31733243
],
31743244
order: [{
31753245
id: 'visitors.source'
3246+
}, {
3247+
id: 'visitors.updated_at'
31763248
}],
31773249
timezone: 'UTC',
31783250
},
@@ -3284,6 +3356,8 @@ SELECT 1 AS revenue, cast('2024-01-01' AS timestamp) as time UNION ALL
32843356
],
32853357
order: [{
32863358
id: 'visitors_multi_stage.source'
3359+
}, {
3360+
id: 'visitors_multi_stage.updated_at'
32873361
}],
32883362
timezone: 'UTC',
32893363
},
@@ -3797,4 +3871,111 @@ SELECT 1 AS revenue, cast('2024-01-01' AS timestamp) as time UNION ALL
37973871
}]
37983872
);
37993873
});
3800-
});
3874+
3875+
it('period over period time only', async () => runQueryTest({
3876+
measures: [
3877+
'sales.current_month_sum',
3878+
'sales.previous_month_sum',
3879+
'sales.month_over_month_ratio'
3880+
],
3881+
timeDimensions: [{
3882+
dimension: 'sales.date',
3883+
granularity: 'month',
3884+
dateRange: ['2023-01-01', '2023-12-31']
3885+
}],
3886+
order: [{
3887+
id: 'sales.date'
3888+
}],
3889+
timezone: 'UTC'
3890+
}, [
3891+
{ 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 },
3892+
{ 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' },
3893+
{ 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' },
3894+
{ 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 },
3895+
{ 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 },
3896+
{ 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 },
3897+
{ 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 },
3898+
{ 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 },
3899+
{ 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 },
3900+
{ 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 },
3901+
{ 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 },
3902+
{ 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 }
3903+
]));
3904+
3905+
it('period over period one dimension', async () => runQueryTest({
3906+
measures: [
3907+
'sales.current_month_sum',
3908+
'sales.previous_month_sum',
3909+
'sales.month_over_month_ratio'
3910+
],
3911+
dimensions: ['sales.category'],
3912+
timeDimensions: [{
3913+
dimension: 'sales.date',
3914+
granularity: 'month',
3915+
dateRange: ['2023-01-01', '2023-12-31']
3916+
}],
3917+
order: [{
3918+
id: 'sales.date'
3919+
}, {
3920+
id: 'sales.category'
3921+
}],
3922+
timezone: 'UTC'
3923+
}, [
3924+
{ 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 },
3925+
{ 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 },
3926+
{ 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' },
3927+
{ 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' },
3928+
{ 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' },
3929+
{ 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 },
3930+
{ 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 },
3931+
{ 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 },
3932+
{ 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 },
3933+
{ 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 },
3934+
{ 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 },
3935+
{ 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 },
3936+
{ 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 },
3937+
{ 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 }
3938+
]));
3939+
3940+
it('period over period two dimensions', async () => runQueryTest({
3941+
measures: [
3942+
'sales.current_month_sum',
3943+
'sales.previous_month_sum',
3944+
'sales.month_over_month_ratio'
3945+
],
3946+
dimensions: ['sales.category', 'sales.region'],
3947+
timeDimensions: [{
3948+
dimension: 'sales.date',
3949+
granularity: 'month',
3950+
dateRange: ['2023-01-01', '2023-12-31']
3951+
}],
3952+
order: [{
3953+
id: 'sales.date'
3954+
}, {
3955+
id: 'sales.category'
3956+
}, {
3957+
id: 'sales.region'
3958+
}],
3959+
timezone: 'UTC'
3960+
}, [
3961+
{ 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 },
3962+
{ 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 },
3963+
{ 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 },
3964+
{ 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 },
3965+
{ 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' },
3966+
{ 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' },
3967+
{ 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' },
3968+
{ 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' },
3969+
{ 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' },
3970+
{ 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 },
3971+
{ 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 },
3972+
{ 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 },
3973+
{ 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 },
3974+
{ 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 },
3975+
{ 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 },
3976+
{ 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 },
3977+
{ 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 },
3978+
{ 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 },
3979+
{ 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 }
3980+
]));
3981+
});

0 commit comments

Comments
 (0)