Unify handling of cyclic time groupings
Remove generic cyclic grouping
This commit is contained in:
@@ -119,83 +119,13 @@ const aggregateColumnDefs = ctx => {
|
||||
const aggregateDimensions = ctx => ctx.dimensions || {};
|
||||
|
||||
const timeDimensionParameters = definition => {
|
||||
let group_by_count = definition.step || 1;
|
||||
let group_by_units;
|
||||
let group_by_cycle;
|
||||
switch (definition.group_by) {
|
||||
case 'second':
|
||||
case 'minute':
|
||||
case 'hour':
|
||||
case 'day':
|
||||
case 'week':
|
||||
case 'month':
|
||||
case 'quarter':
|
||||
case 'year':
|
||||
case 'century':
|
||||
case 'millenium':
|
||||
group_by_units = definition.group_by;
|
||||
break;
|
||||
case 'semester':
|
||||
group_by_units = 'month';
|
||||
group_by_count *= 6;
|
||||
break;
|
||||
case 'trimester':
|
||||
group_by_units = 'month';
|
||||
group_by_count *= 4;
|
||||
break;
|
||||
case 'minuteOfHour':
|
||||
group_by_units = 'minute';
|
||||
group_by_cycle = 'hour';
|
||||
break;
|
||||
case 'hourOfDay':
|
||||
group_by_units = 'hour';
|
||||
group_by_cycle = 'day';
|
||||
break;
|
||||
case 'dayOfWeek':
|
||||
group_by_units = 'day';
|
||||
group_by_cycle = 'week';
|
||||
break;
|
||||
case 'dayOfMonth':
|
||||
group_by_units = 'day';
|
||||
group_by_cycle = 'month';
|
||||
break;
|
||||
case 'dayOfYear':
|
||||
group_by_units = 'day';
|
||||
group_by_cycle = 'year';
|
||||
break;
|
||||
case 'weekOfYear':
|
||||
group_by_units = 'week';
|
||||
group_by_cycle = 'year';
|
||||
break;
|
||||
case 'monthOfYear':
|
||||
group_by_units = 'month';
|
||||
group_by_cycle = 'year';
|
||||
break;
|
||||
case 'quarterOfYear':
|
||||
group_by_units = 'quarter';
|
||||
group_by_cycle = 'year';
|
||||
break;
|
||||
case 'trimesterOfYear':
|
||||
group_by_units = 'month';
|
||||
group_by_count *= 4;
|
||||
group_by_cycle = 'year';
|
||||
break;
|
||||
case 'semesterOfYear':
|
||||
group_by_units = 'month';
|
||||
group_by_count *= 6;
|
||||
group_by_cycle = 'year';
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Invalid time grouping ${definition.group_by}`);
|
||||
}
|
||||
// definition.column should correspond to a wrapped date column
|
||||
return {
|
||||
time: `to_timestamp("${definition.column}")`,
|
||||
timezone: definition.timezone || 'utc',
|
||||
group_by_units,
|
||||
group_by_count,
|
||||
group_by_cycle,
|
||||
epoch: definition.starting
|
||||
group_by: definition.group_by,
|
||||
group_by_count: definition.group_by_count || 1,
|
||||
starting: definition.starting
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,24 +1,3 @@
|
||||
const MONTH_SECONDS = 365.2425 / 12 * 24 * 3600; // PG intervals use 30 * 24 * 3600
|
||||
const YEAR_SECONDS = 12 * MONTH_SECONDS;
|
||||
|
||||
// time unit durations
|
||||
const usecs = {
|
||||
second: 1,
|
||||
minute: 60,
|
||||
hour: 3600,
|
||||
day: 24 * 3600,
|
||||
week: 7 * 24 * 3600,
|
||||
month: MONTH_SECONDS,
|
||||
year: YEAR_SECONDS,
|
||||
|
||||
quarter: 3 * MONTH_SECONDS,
|
||||
semester: 6 * MONTH_SECONDS,
|
||||
trimester: 4 * MONTH_SECONDS,
|
||||
decade: 12 * YEAR_SECONDS,
|
||||
century: 100 * YEAR_SECONDS,
|
||||
millennium: 1000 * YEAR_SECONDS
|
||||
};
|
||||
|
||||
const YEARSPAN = "(date_part('year', $t)-date_part('year', $epoch))";
|
||||
// Note that SECONDSPAN is not a UTC epoch, but an epoch in the specified TZ,
|
||||
// so we can use it to compute any multiple of seconds with it without using date_part or date_trunc
|
||||
@@ -125,14 +104,14 @@ function serialNormalize(u, m) {
|
||||
}
|
||||
|
||||
function cyclicNormalize(u, m, c) {
|
||||
if (u === 'month' && m === 3) {
|
||||
u = 'quarter';
|
||||
if (u === 'monthOfYear' && m === 3) {
|
||||
u = 'quarterOfYear';
|
||||
m = 1;
|
||||
} else if (u === 'month' && m === 6) {
|
||||
u = 'semester';
|
||||
} else if (u === 'monthOfYear' && m === 6) {
|
||||
u = 'semesterOfYear';
|
||||
m = 1;
|
||||
} else if (u === 'month' && m === 4) {
|
||||
u = 'trimester';
|
||||
} else if (u === 'monthOfYear' && m === 4) {
|
||||
u = 'trimesterOfYear';
|
||||
m = 1;
|
||||
}
|
||||
if (m !== 1) {
|
||||
@@ -185,96 +164,89 @@ function epochExpression(epoch) {
|
||||
return `TIMESTAMP '${epoch}'`;
|
||||
}
|
||||
|
||||
function cyclicSqlExpr(t, tz, u, c, m = 1) {
|
||||
function cyclicSqlExpr(t, tz, u, m = 1) {
|
||||
[u, m, c] = cyclicNormalize(u, m, c);
|
||||
const comb = `${u}/${c}`;
|
||||
const column = timeExpression(t, tz);
|
||||
|
||||
if (m === 1) {
|
||||
switch (comb) {
|
||||
case 'day/week':
|
||||
switch (u) {
|
||||
case 'dayOfWeek':
|
||||
// 1 = monday; 7 = sunday;
|
||||
return `date_part('isodow', ${column})`;
|
||||
|
||||
case 'day/month':
|
||||
case 'dayOfMonth':
|
||||
// result: 1-31
|
||||
return `date_part('day', ${column})`;
|
||||
|
||||
case 'day/year':
|
||||
case 'dayOfYear':
|
||||
// result: 1-366
|
||||
return `date_part('doy', ${column})`;
|
||||
|
||||
case 'hour/day':
|
||||
case 'hourOfDay':
|
||||
// result: 0-23
|
||||
return `date_part('hour', ${column})`;
|
||||
|
||||
case 'month/year':
|
||||
case 'monthOfYear':
|
||||
// result 1-12
|
||||
return `date_part('month', ${column})`;
|
||||
|
||||
case 'quarter/year':
|
||||
case 'quarterOfYear':
|
||||
// result 1-4
|
||||
return `date_part('quarter', ${column})`;
|
||||
|
||||
case 'semester/year':
|
||||
case 'semesterOfYear':
|
||||
// result 1-2
|
||||
return `FLOOR((date_part('month', ${column})-1)/6.0) + 1`;
|
||||
|
||||
case 'trimester/year':
|
||||
case 'trimesterOfYear':
|
||||
// result 1-3
|
||||
return `FLOOR((date_part('month', ${column})-1)/4.0) + 1`;
|
||||
|
||||
case 'week/year':
|
||||
case 'weekOfYear':
|
||||
// result 1-53
|
||||
return `date_part('week', ${column})`;
|
||||
|
||||
case 'minute/hour':
|
||||
case 'minuteOfHour':
|
||||
// result 0-59
|
||||
return `date_part('minute', ${column})`;
|
||||
}
|
||||
}
|
||||
// TODO: remove generic expression, reaching here should error
|
||||
return genericCyclicSqlExpr(t, u, c, 0, m);
|
||||
}
|
||||
|
||||
function genericCyclicSqlExpr(t, tz, u, c, c_offset = 0, m = 1) {
|
||||
const usec = usecs[u];
|
||||
const csec = usecs[c];
|
||||
const column = timeExpression(t, tz);
|
||||
return `((FLOOR(date_part('epoch', ${column})/(${usec*m}))*(${usec*m})+${c_offset}) % ${csec})/${usec*m}`;
|
||||
throw new Error(`Invalid cyclic time grouping ${u}`)
|
||||
}
|
||||
|
||||
function validateParameters(_params) {
|
||||
return true;
|
||||
}
|
||||
|
||||
function isCyclic(groupBy) {
|
||||
return groupBy.match(/.+By.+/);
|
||||
}
|
||||
|
||||
function classificationSql(params) {
|
||||
validateParameters(params);
|
||||
if (params.group_by_cycle) {
|
||||
if (isCyclic(params.group_by)) {
|
||||
// TODO: validate group_by_count === 1, No epoch
|
||||
return cyclicSqlExpr(
|
||||
params.time,
|
||||
params.timezone || 'utc',
|
||||
params.group_by_units,
|
||||
params.group_by_cycle,
|
||||
0,
|
||||
params.group_by_count || 1
|
||||
params.timezone,
|
||||
params.group_by,
|
||||
params.group_by_count
|
||||
);
|
||||
} else if (params.format === 'iso') {
|
||||
// TODO: validate group_by_count === 1, No epoch
|
||||
return isoSqlExpr(
|
||||
params.time,
|
||||
params.timezone || 'utc',
|
||||
params.group_by_units,
|
||||
params.group_by_count || 1
|
||||
params.timezone,
|
||||
params.group_by,
|
||||
params.group_by_count
|
||||
);
|
||||
} else {
|
||||
return serialSqlExpr(
|
||||
params.time,
|
||||
params.timezone || 'utc',
|
||||
params.group_by_units,
|
||||
params.group_by_count || 1,
|
||||
params.epoch
|
||||
params.timezone,
|
||||
params.group_by,
|
||||
params.group_by_count,
|
||||
params.starting
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user