diff --git a/packages/cubejs-schema-compiler/src/adapter/BaseQuery.js b/packages/cubejs-schema-compiler/src/adapter/BaseQuery.js index b87720927017a..15d1bc6747a68 100644 --- a/packages/cubejs-schema-compiler/src/adapter/BaseQuery.js +++ b/packages/cubejs-schema-compiler/src/adapter/BaseQuery.js @@ -1083,7 +1083,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}`)}` : `, ${this.wrapInParenthesis(q)} as q_${i + 1}`), ).join('\n'); diff --git a/packages/cubejs-schema-compiler/test/integration/postgres/PostgresDBRunner.js b/packages/cubejs-schema-compiler/test/integration/postgres/PostgresDBRunner.js index 9ac65fd8aa9da..1cdba41c00d5b 100644 --- a/packages/cubejs-schema-compiler/test/integration/postgres/PostgresDBRunner.js +++ b/packages/cubejs-schema-compiler/test/integration/postgres/PostgresDBRunner.js @@ -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 @@ -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) `) ]); } diff --git a/packages/cubejs-schema-compiler/test/integration/postgres/sql-generation.test.ts b/packages/cubejs-schema-compiler/test/integration/postgres/sql-generation.test.ts index 76f159e5b8856..73ef96ea11559 100644 --- a/packages/cubejs-schema-compiler/test/integration/postgres/sql-generation.test.ts +++ b/packages/cubejs-schema-compiler/test/integration/postgres/sql-generation.test.ts @@ -752,7 +752,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 () => { @@ -1343,6 +1411,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' } ])); @@ -3173,6 +3243,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', }, @@ -3284,6 +3356,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', }, @@ -3797,4 +3871,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 } + ])); });