(timeRange *runtimev1.TimeSeriesTimeRange, q *ColumnTimeseries, temporaryTableName, tsAlias, timezone string, dialect drivers.Dialect)
| 239 | } |
| 240 | |
| 241 | func timeSeriesClickHouseSQL(timeRange *runtimev1.TimeSeriesTimeRange, q *ColumnTimeseries, temporaryTableName, tsAlias, timezone string, dialect drivers.Dialect) (string, []any) { |
| 242 | dateTruncSpecifier := dialect.ConvertToDateTruncSpecifier(timeRange.Interval) |
| 243 | measures := normaliseMeasures(q.Measures, q.Pixels != 0) |
| 244 | filter := "" |
| 245 | |
| 246 | var args []any |
| 247 | var timeSQL, colSQL, unit string |
| 248 | var offset uint32 |
| 249 | if timeRange.Interval == runtimev1.TimeGrain_TIME_GRAIN_WEEK && q.FirstDayOfWeek > 1 { |
| 250 | offset = 8 - q.FirstDayOfWeek |
| 251 | unit = "day" |
| 252 | } else if timeRange.Interval == runtimev1.TimeGrain_TIME_GRAIN_YEAR && q.FirstMonthOfYear > 1 { |
| 253 | offset = 13 - q.FirstMonthOfYear |
| 254 | unit = "month" |
| 255 | } else { |
| 256 | unit = "day" // never mind since offset is zero |
| 257 | } |
| 258 | timeSQL = `date_sub(` + unit + `, ?, date_trunc(?, date_add(` + unit + `, ?, toTimeZone(?::DATETIME64, ?))))` |
| 259 | // start and end are not null else we would have an empty time range but column can still have null values |
| 260 | colSQL = `date_sub(` + unit + `, ?, date_trunc(?, date_add(` + unit + `, ?, toTimeZone(` + dialect.EscapeIdentifier(q.TimestampColumnName) + `::Nullable(DATETIME64), ?))))` |
| 261 | // nolint |
| 262 | args = append(args, offset, dateTruncSpecifier, offset, timeRange.Start.AsTime(), timezone) // compute start |
| 263 | args = append(args, offset, dateTruncSpecifier, offset, timeRange.End.AsTime(), timezone) // compute end |
| 264 | args = append(args, offset, dateTruncSpecifier, offset, timeRange.Start.AsTime(), timezone) // compute start again to generate series |
| 265 | args = append(args, offset, dateTruncSpecifier, offset, timezone) // convert column |
| 266 | args = append(args, timezone) |
| 267 | |
| 268 | return `CREATE TEMPORARY TABLE ` + temporaryTableName + ` AS ( |
| 269 | WITH time_range AS |
| 270 | ( |
| 271 | SELECT ` + timeSQL + ` AS start, |
| 272 | ` + timeSQL + ` AS end, |
| 273 | date_diff(` + dateTruncSpecifier + `, start, end) AS interval |
| 274 | ), |
| 275 | number_range AS ( |
| 276 | SELECT |
| 277 | arrayJoin(range(interval::UInt64)) AS number |
| 278 | FROM time_range |
| 279 | ), |
| 280 | -- generate a time series column that has the intended range |
| 281 | template AS ( |
| 282 | SELECT ` + timeSQL + ` AS start, |
| 283 | date_add(` + dateTruncSpecifier + `, number, start) AS ` + tsAlias + ` |
| 284 | FROM number_range |
| 285 | ), |
| 286 | -- transform the original data, and optionally sample it. |
| 287 | series AS ( |
| 288 | SELECT |
| 289 | ` + colSQL + ` AS ` + tsAlias + `,` + getExpressionColumnsFromMeasures(dialect, measures) + ` |
| 290 | FROM ` + dialect.EscapeTable(q.Database, q.DatabaseSchema, q.TableName) + ` ` + filter + ` |
| 291 | GROUP BY ` + tsAlias + ` ORDER BY ` + tsAlias + ` |
| 292 | ) |
| 293 | -- an additional grouping is required for time zone DST (see unit tests for examples) |
| 294 | SELECT ` + tsAlias + `,` + getCoalesceStatementsMeasuresLast(dialect, measures) + ` FROM ( |
| 295 | -- join the transformed data with the generated time series column, |
| 296 | -- coalescing the first value to get the 0-default when the rolled up data |
| 297 | -- does not have that value. |
| 298 | SELECT |
no test coverage detected