Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/cubejs-schema-compiler/src/adapter/BaseQuery.js
Original file line number Diff line number Diff line change
Expand Up @@ -1227,7 +1227,7 @@ export class BaseQuery {
const join = R.drop(1, toJoin)
.map(
(q, i) => (this.dimensionAliasNames().length ?
`INNER JOIN ${this.wrapInParenthesis((q))} as q_${i + 1} ON ${this.dimensionsJoinCondition(`q_${i}`, `q_${i + 1}`)}` :
`LEFT JOIN ${this.wrapInParenthesis((q))} as q_${i + 1} ON ${this.dimensionsJoinCondition(`q_${i}`, `q_${i + 1}`)}` :
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if there are few subjoins? And it's probably possible that the result set with gaps may occur on the right side too... So maybe FULL OUTER JOIN should be used here?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right. I was thinking the root q_0 would always have the full time set, but FULL OUTER JOIN will work too. I can make the code and description updates.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After more analysis, FULL OUTER JOIN won't work here because it seems PostgreSQL requires merge-joinable or hash-joinable join conditions and dimensionsJoinCondition includes other null-handling logic that prevents that (found by re-running tests after just changing to FULL OUTER JOIN). LEFT JOIN still seems appropriate because we can assume the first query q_0 defines the full time range and that is what is preserved when data is missing from later subqueries. The same logic would apply in cases with few subjoins, q_0 is treated as the time source subquery and LEFT JOIN ensures we retain those rows when there is missing data on the right.

`, ${this.wrapInParenthesis(q)} as q_${i + 1}`),
).join('\n');

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export class PostgresDBRunner extends BaseDbRunner {
tx.query('CREATE TEMPORARY TABLE right_table (id INT, total DOUBLE PRECISION, description character varying) ON COMMIT DROP'),
tx.query('CREATE TEMPORARY TABLE mid_table (id INT, left_id INT, right_id INT) ON COMMIT DROP'),
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'),
tx.query('CREATE TEMPORARY TABLE sales (id INT PRIMARY KEY, date DATE, category TEXT, region TEXT, amount DECIMAL) ON COMMIT DROP'),
tx.query(`
INSERT INTO
visitors
Expand Down Expand Up @@ -126,6 +127,23 @@ export class PostgresDBRunner extends BaseDbRunner {
(2, 2, 3, 6, 7),
(2, 2, 2, 4, 8),
(2, 3, 4, 5, 2);
`),
tx.query(`
INSERT INTO
sales
(id, date, category, region, amount) VALUES
(1, '2023-01-01', 'Electronics', 'North', 300),
(2, '2023-01-01', 'Electronics', 'South', 200),
(3, '2023-01-01', 'Clothing', 'North', 200),
(4, '2023-01-01', 'Clothing', 'South', 100),
(5, '2023-02-01', 'Electronics', 'North', 400),
(6, '2023-02-01', 'Electronics', 'South', 200),
(7, '2023-02-01', 'Clothing', 'North', 300),
(8, '2023-02-01', 'Clothing', 'South', 100),
(9, '2023-03-01', 'Electronics', 'North', 500),
(10, '2023-07-01', 'Electronics', 'South', 300),
(11, '2023-12-01', 'Clothing', 'North', 400),
(12, '2023-12-01', 'Clothing', 'South', 200)
`)
]);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -815,7 +815,75 @@ SELECT 1 AS revenue, cast('2024-01-01' AS timestamp) as time UNION ALL
join_path: 'UngroupedMeasureWithFilter1.UngroupedMeasureWithFilter2',
includes: ['count']
}]
})
});

cube(\`sales\`, {
sql: \`SELECT * FROM sales\`,

dimensions: {
date: {
sql: \`date\`,
type: \`time\`
},
id: {
sql: \`id\`,
type: \`number\`,
primaryKey: true
},
category: {
sql: \`category\`,
type: \`string\`
},
region: {
sql: \`region\`,
type: \`string\`
}
},

measures: {
current_month_sum: {
sql: 'amount',
type: 'sum',
rolling_window: {
trailing: '1 month',
offset: 'end'
}
},
previous_month_sum: {
sql: 'amount',
type: 'sum',
rolling_window: {
trailing: '1 month',
offset: 'start'
}
},
month_over_month_ratio: {
type: \`number\`,
sql: \`\${current_month_sum} / NULLIF(\${previous_month_sum}, 0)\`
},
current_year_sum: {
sql: 'amount',
type: 'sum',
rolling_window: {
trailing: '1 year',
offset: 'end'
}
},
previous_year_sum: {
sql: 'amount',
type: 'sum',
rolling_window: {
trailing: '1 year',
offset: 'start'
}
},
year_over_year_ratio: {
type: \`number\`,
sql: \`\${current_year_sum} / NULLIF(\${previous_year_sum}, 0)\`
}
}
});

`);

it('simple join', async () => {
Expand Down Expand Up @@ -1412,6 +1480,8 @@ SELECT 1 AS revenue, cast('2024-01-01' AS timestamp) as time UNION ALL
}],
timezone: 'America/Los_Angeles'
}, [
{ visitors__created_at_day: '2017-01-02T00:00:00.000Z', visitors__cagr_day: null, visitors__revenue: '100', visitors__revenue_day_ago: null },
{ visitors__created_at_day: '2017-01-04T00:00:00.000Z', visitors__cagr_day: null, visitors__revenue: '200', visitors__revenue_day_ago: null },
{ visitors__created_at_day: '2017-01-05T00:00:00.000Z', visitors__cagr_day: '150', visitors__revenue: '300', visitors__revenue_day_ago: '200' },
{ visitors__created_at_day: '2017-01-06T00:00:00.000Z', visitors__cagr_day: '300', visitors__revenue: '900', visitors__revenue_day_ago: '300' }
]));
Expand Down Expand Up @@ -3367,6 +3437,8 @@ SELECT 1 AS revenue, cast('2024-01-01' AS timestamp) as time UNION ALL
],
order: [{
id: 'visitors.source'
}, {
id: 'visitors.updated_at'
}],
timezone: 'UTC',
},
Expand Down Expand Up @@ -3478,6 +3550,8 @@ SELECT 1 AS revenue, cast('2024-01-01' AS timestamp) as time UNION ALL
],
order: [{
id: 'visitors_multi_stage.source'
}, {
id: 'visitors_multi_stage.updated_at'
}],
timezone: 'UTC',
},
Expand Down Expand Up @@ -3991,4 +4065,111 @@ SELECT 1 AS revenue, cast('2024-01-01' AS timestamp) as time UNION ALL
}]
);
});

it('period over period time only', async () => runQueryTest({
measures: [
'sales.current_month_sum',
'sales.previous_month_sum',
'sales.month_over_month_ratio'
],
timeDimensions: [{
dimension: 'sales.date',
granularity: 'month',
dateRange: ['2023-01-01', '2023-12-31']
}],
order: [{
id: 'sales.date'
}],
timezone: 'UTC'
}, [
{ 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 },
{ 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' },
{ 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' },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 }
]));

it('period over period one dimension', async () => runQueryTest({
measures: [
'sales.current_month_sum',
'sales.previous_month_sum',
'sales.month_over_month_ratio'
],
dimensions: ['sales.category'],
timeDimensions: [{
dimension: 'sales.date',
granularity: 'month',
dateRange: ['2023-01-01', '2023-12-31']
}],
order: [{
id: 'sales.date'
}, {
id: 'sales.category'
}],
timezone: 'UTC'
}, [
{ 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 },
{ 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 },
{ 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' },
{ 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' },
{ 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' },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 }
]));

it('period over period two dimensions', async () => runQueryTest({
measures: [
'sales.current_month_sum',
'sales.previous_month_sum',
'sales.month_over_month_ratio'
],
dimensions: ['sales.category', 'sales.region'],
timeDimensions: [{
dimension: 'sales.date',
granularity: 'month',
dateRange: ['2023-01-01', '2023-12-31']
}],
order: [{
id: 'sales.date'
}, {
id: 'sales.category'
}, {
id: 'sales.region'
}],
timezone: 'UTC'
}, [
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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' },
{ 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' },
{ 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' },
{ 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' },
{ 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' },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 }
]));
});
Loading