mirror of
https://github.com/ClickHouse/ClickHouse.git
synced 2024-11-10 09:32:06 +00:00
Merge pull request #35349 from yakov-olkhovskiy/interpolate-feature
Interpolate feature
This commit is contained in:
commit
155a2a0d42
@ -22,7 +22,7 @@ SELECT [DISTINCT [ON (column1, column2, ...)]] expr_list
|
||||
[WHERE expr]
|
||||
[GROUP BY expr_list] [WITH ROLLUP|WITH CUBE] [WITH TOTALS]
|
||||
[HAVING expr]
|
||||
[ORDER BY expr_list] [WITH FILL] [FROM expr] [TO expr] [STEP expr]
|
||||
[ORDER BY expr_list] [WITH FILL] [FROM expr] [TO expr] [STEP expr] [INTERPOLATE [(expr_list)]]
|
||||
[LIMIT [offset_value, ]n BY columns]
|
||||
[LIMIT [n, ]m] [WITH TIES]
|
||||
[SETTINGS ...]
|
||||
|
@ -280,6 +280,7 @@ To fill multiple columns, add `WITH FILL` modifier with optional parameters afte
|
||||
|
||||
``` sql
|
||||
ORDER BY expr [WITH FILL] [FROM const_expr] [TO const_expr] [STEP const_numeric_expr], ... exprN [WITH FILL] [FROM expr] [TO expr] [STEP numeric_expr]
|
||||
[INTERPOLATE [(col [AS expr], ... colN [AS exprN])]]
|
||||
```
|
||||
|
||||
`WITH FILL` can be applied for fields with Numeric (all kinds of float, decimal, int) or Date/DateTime types. When applied for `String` fields, missed values are filled with empty strings.
|
||||
@ -287,6 +288,7 @@ When `FROM const_expr` not defined sequence of filling use minimal `expr` field
|
||||
When `TO const_expr` not defined sequence of filling use maximum `expr` field value from `ORDER BY`.
|
||||
When `STEP const_numeric_expr` defined then `const_numeric_expr` interprets `as is` for numeric types, as `days` for Date type, as `seconds` for DateTime type. It also supports [INTERVAL](https://clickhouse.com/docs/en/sql-reference/data-types/special-data-types/interval/) data type representing time and date intervals.
|
||||
When `STEP const_numeric_expr` omitted then sequence of filling use `1.0` for numeric type, `1 day` for Date type and `1 second` for DateTime type.
|
||||
`INTERPOLATE` can be applied to columns not participating in `ORDER BY WITH FILL`. Such columns are filled based on previous fields values by applying `expr`. If `expr` is not present will repeate previous value. Omitted list will result in including all allowed columns.
|
||||
|
||||
Example of a query without `WITH FILL`:
|
||||
|
||||
@ -483,4 +485,62 @@ Result:
|
||||
└────────────┴────────────┴──────────┘
|
||||
```
|
||||
|
||||
Example of a query without `INTERPOLATE`:
|
||||
|
||||
``` sql
|
||||
SELECT n, source, inter FROM (
|
||||
SELECT toFloat32(number % 10) AS n, 'original' AS source, number as inter
|
||||
FROM numbers(10) WHERE number % 3 = 1
|
||||
) ORDER BY n WITH FILL FROM 0 TO 5.51 STEP 0.5;
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
``` text
|
||||
┌───n─┬─source───┬─inter─┐
|
||||
│ 0 │ │ 0 │
|
||||
│ 0.5 │ │ 0 │
|
||||
│ 1 │ original │ 1 │
|
||||
│ 1.5 │ │ 0 │
|
||||
│ 2 │ │ 0 │
|
||||
│ 2.5 │ │ 0 │
|
||||
│ 3 │ │ 0 │
|
||||
│ 3.5 │ │ 0 │
|
||||
│ 4 │ original │ 4 │
|
||||
│ 4.5 │ │ 0 │
|
||||
│ 5 │ │ 0 │
|
||||
│ 5.5 │ │ 0 │
|
||||
│ 7 │ original │ 7 │
|
||||
└─────┴──────────┴───────┘
|
||||
```
|
||||
|
||||
Same query after applying `INTERPOLATE`:
|
||||
|
||||
``` sql
|
||||
SELECT n, source, inter FROM (
|
||||
SELECT toFloat32(number % 10) AS n, 'original' AS source, number as inter
|
||||
FROM numbers(10) WHERE number % 3 = 1
|
||||
) ORDER BY n WITH FILL FROM 0 TO 5.51 STEP 0.5 INTERPOLATE (inter AS inter + 1);
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
``` text
|
||||
┌───n─┬─source───┬─inter─┐
|
||||
│ 0 │ │ 0 │
|
||||
│ 0.5 │ │ 0 │
|
||||
│ 1 │ original │ 1 │
|
||||
│ 1.5 │ │ 2 │
|
||||
│ 2 │ │ 3 │
|
||||
│ 2.5 │ │ 4 │
|
||||
│ 3 │ │ 5 │
|
||||
│ 3.5 │ │ 6 │
|
||||
│ 4 │ original │ 4 │
|
||||
│ 4.5 │ │ 5 │
|
||||
│ 5 │ │ 6 │
|
||||
│ 5.5 │ │ 7 │
|
||||
│ 7 │ original │ 7 │
|
||||
└─────┴──────────┴───────┘
|
||||
```
|
||||
|
||||
[Original article](https://clickhouse.com/docs/en/sql-reference/statements/select/order-by/) <!--hide-->
|
||||
|
@ -20,7 +20,7 @@ SELECT [DISTINCT [ON (column1, column2, ...)]] expr_list
|
||||
[WHERE expr]
|
||||
[GROUP BY expr_list] [WITH ROLLUP|WITH CUBE] [WITH TOTALS]
|
||||
[HAVING expr]
|
||||
[ORDER BY expr_list] [WITH FILL] [FROM expr] [TO expr] [STEP expr]
|
||||
[ORDER BY expr_list] [WITH FILL] [FROM expr] [TO expr] [STEP expr] [INTERPOLATE [(expr_list)]]
|
||||
[LIMIT [offset_value, ]n BY columns]
|
||||
[LIMIT [n, ]m] [WITH TIES]
|
||||
[SETTINGS ...]
|
||||
|
@ -280,6 +280,7 @@ SELECT * FROM collate_test ORDER BY s ASC COLLATE 'en';
|
||||
|
||||
```sql
|
||||
ORDER BY expr [WITH FILL] [FROM const_expr] [TO const_expr] [STEP const_numeric_expr], ... exprN [WITH FILL] [FROM expr] [TO expr] [STEP numeric_expr]
|
||||
[INTERPOLATE [(col [AS expr], ... colN [AS exprN])]]
|
||||
```
|
||||
|
||||
`WITH FILL` может быть применен к полям с числовыми (все разновидности float, int, decimal) или временными (все разновидности Date, DateTime) типами. В случае применения к полям типа `String` недостающие значения заполняются пустой строкой.
|
||||
@ -289,6 +290,8 @@ ORDER BY expr [WITH FILL] [FROM const_expr] [TO const_expr] [STEP const_numeric_
|
||||
|
||||
Когда `STEP const_numeric_expr` не указан, тогда используется `1.0` для числовых типов, `1 день` для типа Date и `1 секунда` для типа DateTime.
|
||||
|
||||
`INTERPOLATE` может быть применен к колонкам, не участвующим в `ORDER BY WITH FILL`. Такие колонки заполняются значениями, вычисляемыми применением `expr` к предыдущему значению. Если `expr` опущен, то колонка заполняется предыдущим значением. Если список колонок не указан, то включаются все разрешенные колонки.
|
||||
|
||||
Пример запроса без использования `WITH FILL`:
|
||||
```sql
|
||||
SELECT n, source FROM (
|
||||
@ -395,3 +398,58 @@ ORDER BY
|
||||
│ 1970-03-12 │ 1970-01-08 │ original │
|
||||
└────────────┴────────────┴──────────┘
|
||||
```
|
||||
|
||||
Пример запроса без `INTERPOLATE`:
|
||||
|
||||
``` sql
|
||||
SELECT n, source, inter FROM (
|
||||
SELECT toFloat32(number % 10) AS n, 'original' AS source, number as inter
|
||||
FROM numbers(10) WHERE number % 3 = 1
|
||||
) ORDER BY n WITH FILL FROM 0 TO 5.51 STEP 0.5;
|
||||
```
|
||||
|
||||
Результат:
|
||||
``` text
|
||||
┌───n─┬─source───┬─inter─┐
|
||||
│ 0 │ │ 0 │
|
||||
│ 0.5 │ │ 0 │
|
||||
│ 1 │ original │ 1 │
|
||||
│ 1.5 │ │ 0 │
|
||||
│ 2 │ │ 0 │
|
||||
│ 2.5 │ │ 0 │
|
||||
│ 3 │ │ 0 │
|
||||
│ 3.5 │ │ 0 │
|
||||
│ 4 │ original │ 4 │
|
||||
│ 4.5 │ │ 0 │
|
||||
│ 5 │ │ 0 │
|
||||
│ 5.5 │ │ 0 │
|
||||
│ 7 │ original │ 7 │
|
||||
└─────┴──────────┴───────┘
|
||||
```
|
||||
|
||||
Тот же запрос с `INTERPOLATE`:
|
||||
|
||||
``` sql
|
||||
SELECT n, source, inter FROM (
|
||||
SELECT toFloat32(number % 10) AS n, 'original' AS source, number as inter
|
||||
FROM numbers(10) WHERE number % 3 = 1
|
||||
) ORDER BY n WITH FILL FROM 0 TO 5.51 STEP 0.5 INTERPOLATE (inter AS inter + 1);
|
||||
```
|
||||
|
||||
Результат:
|
||||
``` text
|
||||
┌───n─┬─source───┬─inter─┐
|
||||
│ 0 │ │ 0 │
|
||||
│ 0.5 │ │ 0 │
|
||||
│ 1 │ original │ 1 │
|
||||
│ 1.5 │ │ 2 │
|
||||
│ 2 │ │ 3 │
|
||||
│ 2.5 │ │ 4 │
|
||||
│ 3 │ │ 5 │
|
||||
│ 3.5 │ │ 6 │
|
||||
│ 4 │ original │ 4 │
|
||||
│ 4.5 │ │ 5 │
|
||||
│ 5 │ │ 6 │
|
||||
│ 5.5 │ │ 7 │
|
||||
│ 7 │ original │ 7 │
|
||||
└─────┴──────────┴───────┘
|
||||
|
32
src/Core/InterpolateDescription.cpp
Normal file
32
src/Core/InterpolateDescription.cpp
Normal file
@ -0,0 +1,32 @@
|
||||
#include <Core/Block.h>
|
||||
#include <IO/Operators.h>
|
||||
#include <Common/JSONBuilder.h>
|
||||
#include <Core/InterpolateDescription.h>
|
||||
#include <Interpreters/convertFieldToType.h>
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
||||
InterpolateDescription::InterpolateDescription(ActionsDAGPtr actions_, const Aliases & aliases)
|
||||
: actions(actions_)
|
||||
{
|
||||
for (const auto & name_type : actions->getRequiredColumns())
|
||||
{
|
||||
if (const auto & p = aliases.find(name_type.name); p != aliases.end())
|
||||
required_columns_map[p->second->getColumnName()] = name_type;
|
||||
else
|
||||
required_columns_map[name_type.name] = name_type;
|
||||
}
|
||||
|
||||
for (const ColumnWithTypeAndName & column : actions->getResultColumns())
|
||||
{
|
||||
std::string name = column.name;
|
||||
if (const auto & p = aliases.find(name); p != aliases.end())
|
||||
name = p->second->getColumnName();
|
||||
|
||||
result_columns_set.insert(name);
|
||||
result_columns_order.push_back(name);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
33
src/Core/InterpolateDescription.h
Normal file
33
src/Core/InterpolateDescription.h
Normal file
@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include <unordered_map>
|
||||
#include <memory>
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <Core/Field.h>
|
||||
#include <Core/SettingsEnums.h>
|
||||
#include <Common/IntervalKind.h>
|
||||
#include <Parsers/ASTOrderByElement.h>
|
||||
#include <Parsers/ASTInterpolateElement.h>
|
||||
#include <Functions/FunctionsMiscellaneous.h>
|
||||
#include <Interpreters/Aliases.h>
|
||||
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
||||
/// Interpolate description
|
||||
struct InterpolateDescription
|
||||
{
|
||||
explicit InterpolateDescription(ActionsDAGPtr actions, const Aliases & aliases);
|
||||
|
||||
ActionsDAGPtr actions;
|
||||
|
||||
std::unordered_map<std::string, NameAndTypePair> required_columns_map; /// input column name -> {alias, type}
|
||||
std::unordered_set<std::string> result_columns_set; /// result block columns
|
||||
std::vector<std::string> result_columns_order; /// result block columns order
|
||||
};
|
||||
|
||||
using InterpolateDescriptionPtr = std::shared_ptr<InterpolateDescription>;
|
||||
|
||||
}
|
@ -7,6 +7,7 @@
|
||||
#include <Core/Field.h>
|
||||
#include <Core/SettingsEnums.h>
|
||||
#include <Common/IntervalKind.h>
|
||||
#include <DataTypes/IDataType.h>
|
||||
|
||||
class Collator;
|
||||
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include <Parsers/ASTSubquery.h>
|
||||
#include <Parsers/ASTWindowDefinition.h>
|
||||
#include <Parsers/DumpASTNode.h>
|
||||
#include <Parsers/ASTInterpolateElement.h>
|
||||
|
||||
#include <DataTypes/DataTypeNullable.h>
|
||||
#include <Columns/IColumn.h>
|
||||
@ -1333,6 +1334,38 @@ ActionsDAGPtr SelectQueryExpressionAnalyzer::appendOrderBy(ExpressionActionsChai
|
||||
with_fill = true;
|
||||
}
|
||||
|
||||
if (auto interpolate_list = select_query->interpolate())
|
||||
{
|
||||
|
||||
NameSet select;
|
||||
for (const auto & child : select_query->select()->children)
|
||||
select.insert(child->getAliasOrColumnName());
|
||||
|
||||
/// collect columns required for interpolate expressions -
|
||||
/// interpolate expression can use any available column
|
||||
auto find_columns = [&step, &select](IAST * function)
|
||||
{
|
||||
auto f_impl = [&step, &select](IAST * fn, auto fi)
|
||||
{
|
||||
if (auto * ident = fn->as<ASTIdentifier>())
|
||||
{
|
||||
/// exclude columns from select expression - they are already available
|
||||
if (select.count(ident->getColumnName()) == 0)
|
||||
step.addRequiredOutput(ident->getColumnName());
|
||||
return;
|
||||
}
|
||||
if (fn->as<ASTFunction>() || fn->as<ASTExpressionList>())
|
||||
for (const auto & ch : fn->children)
|
||||
fi(ch.get(), fi);
|
||||
return;
|
||||
};
|
||||
f_impl(function, f_impl);
|
||||
};
|
||||
|
||||
for (const auto & interpolate : interpolate_list->children)
|
||||
find_columns(interpolate->as<ASTInterpolateElement>()->expr.get());
|
||||
}
|
||||
|
||||
if (optimize_read_in_order)
|
||||
{
|
||||
for (auto & child : select_query->orderBy()->children)
|
||||
|
@ -19,26 +19,27 @@ bool equals(const Field & lhs, const Field & rhs)
|
||||
}
|
||||
|
||||
|
||||
FillingRow::FillingRow(const SortDescription & sort_description) : description(sort_description)
|
||||
FillingRow::FillingRow(const SortDescription & sort_description_)
|
||||
: sort_description(sort_description_)
|
||||
{
|
||||
row.resize(description.size());
|
||||
row.resize(sort_description.size());
|
||||
}
|
||||
|
||||
bool FillingRow::operator<(const FillingRow & other) const
|
||||
{
|
||||
for (size_t i = 0; i < size(); ++i)
|
||||
for (size_t i = 0; i < sort_description.size(); ++i)
|
||||
{
|
||||
if (row[i].isNull() || other[i].isNull() || equals(row[i], other[i]))
|
||||
if ((*this)[i].isNull() || other.row[i].isNull() || equals(row[i], other.row[i]))
|
||||
continue;
|
||||
return less(row[i], other[i], getDirection(i));
|
||||
return less(row[i], other.row[i], getDirection(i));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FillingRow::operator==(const FillingRow & other) const
|
||||
{
|
||||
for (size_t i = 0; i < size(); ++i)
|
||||
if (!equals(row[i], other[i]))
|
||||
for (size_t i = 0; i < sort_description.size(); ++i)
|
||||
if (!equals(row[i], other.row[i]))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
@ -48,16 +49,16 @@ bool FillingRow::next(const FillingRow & to_row)
|
||||
size_t pos = 0;
|
||||
|
||||
/// Find position we need to increment for generating next row.
|
||||
for (; pos < row.size(); ++pos)
|
||||
if (!row[pos].isNull() && !to_row[pos].isNull() && !equals(row[pos], to_row[pos]))
|
||||
for (; pos < size(); ++pos)
|
||||
if (!row[pos].isNull() && !to_row.row[pos].isNull() && !equals(row[pos], to_row.row[pos]))
|
||||
break;
|
||||
|
||||
if (pos == row.size() || less(to_row[pos], row[pos], getDirection(pos)))
|
||||
if (pos == size() || less(to_row.row[pos], row[pos], getDirection(pos)))
|
||||
return false;
|
||||
|
||||
/// If we have any 'fill_to' value at position greater than 'pos',
|
||||
/// we need to generate rows up to 'fill_to' value.
|
||||
for (size_t i = row.size() - 1; i > pos; --i)
|
||||
for (size_t i = size() - 1; i > pos; --i)
|
||||
{
|
||||
if (getFillDescription(i).fill_to.isNull() || row[i].isNull())
|
||||
continue;
|
||||
@ -75,21 +76,22 @@ bool FillingRow::next(const FillingRow & to_row)
|
||||
auto next_value = row[pos];
|
||||
getFillDescription(pos).step_func(next_value);
|
||||
|
||||
if (less(to_row[pos], next_value, getDirection(pos)))
|
||||
if (less(to_row.row[pos], next_value, getDirection(pos)))
|
||||
return false;
|
||||
|
||||
row[pos] = next_value;
|
||||
if (equals(row[pos], to_row[pos]))
|
||||
if (equals(row[pos], to_row.row[pos]))
|
||||
{
|
||||
bool is_less = false;
|
||||
for (size_t i = pos + 1; i < size(); ++i)
|
||||
size_t i = pos + 1;
|
||||
for (; i < size(); ++i)
|
||||
{
|
||||
const auto & fill_from = getFillDescription(i).fill_from;
|
||||
if (!fill_from.isNull())
|
||||
row[i] = fill_from;
|
||||
else
|
||||
row[i] = to_row[i];
|
||||
is_less |= less(row[i], to_row[i], getDirection(i));
|
||||
row[i] = to_row.row[i];
|
||||
is_less |= less(row[i], to_row.row[i], getDirection(i));
|
||||
}
|
||||
|
||||
return is_less;
|
||||
@ -101,12 +103,12 @@ bool FillingRow::next(const FillingRow & to_row)
|
||||
|
||||
void FillingRow::initFromDefaults(size_t from_pos)
|
||||
{
|
||||
for (size_t i = from_pos; i < row.size(); ++i)
|
||||
for (size_t i = from_pos; i < sort_description.size(); ++i)
|
||||
row[i] = getFillDescription(i).fill_from;
|
||||
}
|
||||
|
||||
|
||||
void insertFromFillingRow(MutableColumns & filling_columns, MutableColumns & other_columns, const FillingRow & filling_row)
|
||||
void insertFromFillingRow(MutableColumns & filling_columns, MutableColumns & interpolate_columns, MutableColumns & other_columns,
|
||||
const FillingRow & filling_row, const Block & interpolate_block)
|
||||
{
|
||||
for (size_t i = 0; i < filling_columns.size(); ++i)
|
||||
{
|
||||
@ -116,6 +118,16 @@ void insertFromFillingRow(MutableColumns & filling_columns, MutableColumns & oth
|
||||
filling_columns[i]->insert(filling_row[i]);
|
||||
}
|
||||
|
||||
if (size_t size = interpolate_block.columns())
|
||||
{
|
||||
Columns columns = interpolate_block.getColumns();
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
interpolate_columns[i]->insertFrom(*columns[i]->convertToFullColumnIfConst(), 0);
|
||||
}
|
||||
else
|
||||
for (const auto & interpolate_column : interpolate_columns)
|
||||
interpolate_column->insertDefault();
|
||||
|
||||
for (const auto & other_column : other_columns)
|
||||
other_column->insertDefault();
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
#include <Core/SortDescription.h>
|
||||
#include <Core/InterpolateDescription.h>
|
||||
#include <Columns/IColumn.h>
|
||||
|
||||
|
||||
@ -17,7 +18,7 @@ bool equals(const Field & lhs, const Field & rhs);
|
||||
class FillingRow
|
||||
{
|
||||
public:
|
||||
FillingRow(const SortDescription & sort_description);
|
||||
explicit FillingRow(const SortDescription & sort_description);
|
||||
|
||||
/// Generates next row according to fill 'from', 'to' and 'step' values.
|
||||
bool next(const FillingRow & to_row);
|
||||
@ -30,15 +31,16 @@ public:
|
||||
bool operator<(const FillingRow & other) const;
|
||||
bool operator==(const FillingRow & other) const;
|
||||
|
||||
int getDirection(size_t index) const { return description[index].direction; }
|
||||
FillColumnDescription & getFillDescription(size_t index) { return description[index].fill_description; }
|
||||
int getDirection(size_t index) const { return sort_description[index].direction; }
|
||||
FillColumnDescription & getFillDescription(size_t index) { return sort_description[index].fill_description; }
|
||||
|
||||
private:
|
||||
Row row;
|
||||
SortDescription description;
|
||||
SortDescription sort_description;
|
||||
};
|
||||
|
||||
void insertFromFillingRow(MutableColumns & filling_columns, MutableColumns & other_columns, const FillingRow & filling_row);
|
||||
void insertFromFillingRow(MutableColumns & filling_columns, MutableColumns & interpolate_columns, MutableColumns & other_columns,
|
||||
const FillingRow & filling_row, const Block & interpolate_block);
|
||||
void copyRowFromColumns(MutableColumns & dest, const Columns & source, size_t row_num);
|
||||
|
||||
}
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include <Parsers/ASTIdentifier.h>
|
||||
#include <Parsers/ASTLiteral.h>
|
||||
#include <Parsers/ASTOrderByElement.h>
|
||||
#include <Parsers/ASTInterpolateElement.h>
|
||||
#include <Parsers/ASTSelectWithUnionQuery.h>
|
||||
#include <Parsers/ASTSelectIntersectExceptQuery.h>
|
||||
#include <Parsers/ASTTablesInSelectQuery.h>
|
||||
@ -100,6 +101,7 @@ namespace ErrorCodes
|
||||
extern const int INVALID_LIMIT_EXPRESSION;
|
||||
extern const int INVALID_WITH_FILL_EXPRESSION;
|
||||
extern const int ACCESS_DENIED;
|
||||
extern const int UNKNOWN_IDENTIFIER;
|
||||
}
|
||||
|
||||
/// Assumes `storage` is set and the table filter (row-level security) is not empty.
|
||||
@ -780,6 +782,7 @@ static std::pair<Field, std::optional<IntervalKind>> getWithFillStep(const ASTPt
|
||||
static FillColumnDescription getWithFillDescription(const ASTOrderByElement & order_by_elem, ContextPtr context)
|
||||
{
|
||||
FillColumnDescription descr;
|
||||
|
||||
if (order_by_elem.fill_from)
|
||||
descr.fill_from = getWithFillFieldValue(order_by_elem.fill_from, context);
|
||||
if (order_by_elem.fill_to)
|
||||
@ -835,7 +838,6 @@ static SortDescription getSortDescription(const ASTSelectQuery & query, ContextP
|
||||
std::shared_ptr<Collator> collator;
|
||||
if (order_by_elem.collation)
|
||||
collator = std::make_shared<Collator>(order_by_elem.collation->as<ASTLiteral &>().value.get<String>());
|
||||
|
||||
if (order_by_elem.with_fill)
|
||||
{
|
||||
FillColumnDescription fill_desc = getWithFillDescription(order_by_elem, context);
|
||||
@ -848,6 +850,77 @@ static SortDescription getSortDescription(const ASTSelectQuery & query, ContextP
|
||||
return order_descr;
|
||||
}
|
||||
|
||||
static InterpolateDescriptionPtr getInterpolateDescription(
|
||||
const ASTSelectQuery & query, const Block & source_block, const Block & result_block, const Aliases & aliases, ContextPtr context)
|
||||
{
|
||||
InterpolateDescriptionPtr interpolate_descr;
|
||||
if (query.interpolate())
|
||||
{
|
||||
NamesAndTypesList source_columns;
|
||||
ColumnsWithTypeAndName result_columns;
|
||||
ASTPtr exprs = std::make_shared<ASTExpressionList>();
|
||||
|
||||
if (query.interpolate()->children.empty())
|
||||
{
|
||||
std::unordered_map<String, DataTypePtr> column_names;
|
||||
for (const auto & column : result_block.getColumnsWithTypeAndName())
|
||||
column_names[column.name] = column.type;
|
||||
for (const auto & elem : query.orderBy()->children)
|
||||
if (elem->as<ASTOrderByElement>()->with_fill)
|
||||
column_names.erase(elem->as<ASTOrderByElement>()->children.front()->getColumnName());
|
||||
for (const auto & [name, type] : column_names)
|
||||
{
|
||||
source_columns.emplace_back(name, type);
|
||||
result_columns.emplace_back(type, name);
|
||||
exprs->children.emplace_back(std::make_shared<ASTIdentifier>(name));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
NameSet col_set;
|
||||
for (const auto & elem : query.interpolate()->children)
|
||||
{
|
||||
const auto & interpolate = elem->as<ASTInterpolateElement &>();
|
||||
|
||||
if (const ColumnWithTypeAndName *result_block_column = result_block.findByName(interpolate.column))
|
||||
{
|
||||
if (!col_set.insert(result_block_column->name).second)
|
||||
throw Exception(ErrorCodes::INVALID_WITH_FILL_EXPRESSION,
|
||||
"Duplicate INTERPOLATE column '{}'", interpolate.column);
|
||||
|
||||
result_columns.emplace_back(result_block_column->type, result_block_column->name);
|
||||
}
|
||||
else
|
||||
throw Exception(ErrorCodes::UNKNOWN_IDENTIFIER,
|
||||
"Missing column '{}' as an INTERPOLATE expression target", interpolate.column);
|
||||
|
||||
exprs->children.emplace_back(interpolate.expr->clone());
|
||||
}
|
||||
|
||||
col_set.clear();
|
||||
for (const auto & column : source_block)
|
||||
{
|
||||
source_columns.emplace_back(column.name, column.type);
|
||||
col_set.insert(column.name);
|
||||
}
|
||||
for (const auto & column : result_block)
|
||||
if (col_set.count(column.name) == 0)
|
||||
source_columns.emplace_back(column.name, column.type);
|
||||
}
|
||||
|
||||
auto syntax_result = TreeRewriter(context).analyze(exprs, source_columns);
|
||||
ExpressionAnalyzer analyzer(exprs, syntax_result, context);
|
||||
ActionsDAGPtr actions = analyzer.getActionsDAG(true);
|
||||
ActionsDAGPtr conv_dag = ActionsDAG::makeConvertingActions(actions->getResultColumns(),
|
||||
result_columns, ActionsDAG::MatchColumnsMode::Position, true);
|
||||
ActionsDAGPtr merge_dag = ActionsDAG::merge(std::move(*actions->clone()), std::move(*conv_dag));
|
||||
|
||||
interpolate_descr = std::make_shared<InterpolateDescription>(merge_dag, aliases);
|
||||
}
|
||||
|
||||
return interpolate_descr;
|
||||
}
|
||||
|
||||
static SortDescription getSortDescriptionFromGroupBy(const ASTSelectQuery & query)
|
||||
{
|
||||
SortDescription order_descr;
|
||||
@ -2515,7 +2588,9 @@ void InterpreterSelectQuery::executeWithFill(QueryPlan & query_plan)
|
||||
if (fill_descr.empty())
|
||||
return;
|
||||
|
||||
auto filling_step = std::make_unique<FillingStep>(query_plan.getCurrentDataStream(), std::move(fill_descr));
|
||||
InterpolateDescriptionPtr interpolate_descr =
|
||||
getInterpolateDescription(query, source_header, result_header, syntax_analyzer_result->aliases, context);
|
||||
auto filling_step = std::make_unique<FillingStep>(query_plan.getCurrentDataStream(), std::move(fill_descr), interpolate_descr);
|
||||
query_plan.addStep(std::move(filling_step));
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include <Parsers/ASTSelectQuery.h>
|
||||
#include <Parsers/ASTQueryParameter.h>
|
||||
#include <Parsers/ASTTablesInSelectQuery.h>
|
||||
#include <Parsers/ASTInterpolateElement.h>
|
||||
#include <Common/StringUtils/StringUtils.h>
|
||||
#include <Common/quoteString.h>
|
||||
#include <IO/WriteHelpers.h>
|
||||
@ -134,7 +135,8 @@ void QueryNormalizer::visit(ASTTablesInSelectQueryElement & node, const ASTPtr &
|
||||
|
||||
static bool needVisitChild(const ASTPtr & child)
|
||||
{
|
||||
return !(child->as<ASTSelectQuery>() || child->as<ASTTableExpression>());
|
||||
/// exclude interpolate elements - they are not subject for normalization and will be processed in filling transform
|
||||
return !(child->as<ASTSelectQuery>() || child->as<ASTTableExpression>() || child->as<ASTInterpolateElement>());
|
||||
}
|
||||
|
||||
/// special visitChildren() for ASTSelectQuery
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include <Parsers/ASTSelectQuery.h>
|
||||
#include <Parsers/ASTSubquery.h>
|
||||
#include <Parsers/ASTTablesInSelectQuery.h>
|
||||
#include <Parsers/ASTInterpolateElement.h>
|
||||
|
||||
namespace DB
|
||||
{
|
||||
@ -46,7 +47,7 @@ bool RequiredSourceColumnsMatcher::needChildVisit(const ASTPtr & node, const AST
|
||||
return false;
|
||||
|
||||
/// Processed. Do not need children.
|
||||
if (node->as<ASTTableExpression>() || node->as<ASTArrayJoin>() || node->as<ASTSelectQuery>())
|
||||
if (node->as<ASTTableExpression>() || node->as<ASTArrayJoin>() || node->as<ASTSelectQuery>() || node->as<ASTInterpolateElement>())
|
||||
return false;
|
||||
|
||||
if (const auto * f = node->as<ASTFunction>())
|
||||
@ -114,15 +115,42 @@ void RequiredSourceColumnsMatcher::visit(const ASTPtr & ast, Data & data)
|
||||
|
||||
void RequiredSourceColumnsMatcher::visit(const ASTSelectQuery & select, const ASTPtr &, Data & data)
|
||||
{
|
||||
NameSet select_columns;
|
||||
/// special case for top-level SELECT items: they are publics
|
||||
for (auto & node : select.select()->children)
|
||||
{
|
||||
select_columns.insert(node->getAliasOrColumnName());
|
||||
|
||||
if (const auto * identifier = node->as<ASTIdentifier>())
|
||||
data.addColumnIdentifier(*identifier);
|
||||
else
|
||||
data.addColumnAliasIfAny(*node);
|
||||
}
|
||||
|
||||
if (auto interpolate_list = select.interpolate())
|
||||
{
|
||||
auto find_columns = [&data, &select_columns](IAST * function)
|
||||
{
|
||||
auto f_impl = [&data, &select_columns](IAST * fn, auto fi)
|
||||
{
|
||||
if (auto * ident = fn->as<ASTIdentifier>())
|
||||
{
|
||||
if (select_columns.count(ident->getColumnName()) == 0)
|
||||
data.addColumnIdentifier(*ident);
|
||||
return;
|
||||
}
|
||||
if (fn->as<ASTFunction>() || fn->as<ASTExpressionList>())
|
||||
for (const auto & ch : fn->children)
|
||||
fi(ch.get(), fi);
|
||||
return;
|
||||
};
|
||||
f_impl(function, f_impl);
|
||||
};
|
||||
|
||||
for (const auto & interpolate : interpolate_list->children)
|
||||
find_columns(interpolate->as<ASTInterpolateElement>()->expr.get());
|
||||
}
|
||||
|
||||
if (const auto & with = select.with())
|
||||
{
|
||||
for (auto & node : with->children)
|
||||
|
@ -32,6 +32,7 @@
|
||||
#include <Parsers/ASTSelectQuery.h>
|
||||
#include <Parsers/ASTSelectWithUnionQuery.h>
|
||||
#include <Parsers/ASTTablesInSelectQuery.h>
|
||||
#include <Parsers/ASTInterpolateElement.h>
|
||||
#include <Parsers/queryToString.h>
|
||||
|
||||
#include <DataTypes/NestedUtils.h>
|
||||
@ -420,7 +421,8 @@ void renameDuplicatedColumns(const ASTSelectQuery * select_query)
|
||||
/// Sometimes we have to calculate more columns in SELECT clause than will be returned from query.
|
||||
/// This is the case when we have DISTINCT or arrayJoin: we require more columns in SELECT even if we need less columns in result.
|
||||
/// Also we have to remove duplicates in case of GLOBAL subqueries. Their results are placed into tables so duplicates are impossible.
|
||||
void removeUnneededColumnsFromSelectClause(const ASTSelectQuery * select_query, const Names & required_result_columns, bool remove_dups)
|
||||
/// Also remove all INTERPOLATE columns which are not in SELECT anymore.
|
||||
void removeUnneededColumnsFromSelectClause(ASTSelectQuery * select_query, const Names & required_result_columns, bool remove_dups)
|
||||
{
|
||||
ASTs & elements = select_query->select()->children;
|
||||
|
||||
@ -449,6 +451,8 @@ void removeUnneededColumnsFromSelectClause(const ASTSelectQuery * select_query,
|
||||
ASTs new_elements;
|
||||
new_elements.reserve(elements.size());
|
||||
|
||||
NameSet remove_columns;
|
||||
|
||||
for (const auto & elem : elements)
|
||||
{
|
||||
String name = elem->getAliasOrColumnName();
|
||||
@ -465,6 +469,8 @@ void removeUnneededColumnsFromSelectClause(const ASTSelectQuery * select_query,
|
||||
}
|
||||
else
|
||||
{
|
||||
remove_columns.insert(name);
|
||||
|
||||
ASTFunction * func = elem->as<ASTFunction>();
|
||||
|
||||
/// Never remove untuple. It's result column may be in required columns.
|
||||
@ -478,6 +484,24 @@ void removeUnneededColumnsFromSelectClause(const ASTSelectQuery * select_query,
|
||||
}
|
||||
}
|
||||
|
||||
if (select_query->interpolate())
|
||||
{
|
||||
auto & children = select_query->interpolate()->children;
|
||||
if (!children.empty())
|
||||
{
|
||||
for (auto it = children.begin(); it != children.end();)
|
||||
{
|
||||
if (remove_columns.count((*it)->as<ASTInterpolateElement>()->column))
|
||||
it = select_query->interpolate()->children.erase(it);
|
||||
else
|
||||
++it;
|
||||
}
|
||||
|
||||
if (children.empty())
|
||||
select_query->setExpression(ASTSelectQuery::Expression::INTERPOLATE, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
elements = std::move(new_elements);
|
||||
}
|
||||
|
||||
|
16
src/Parsers/ASTInterpolateElement.cpp
Normal file
16
src/Parsers/ASTInterpolateElement.cpp
Normal file
@ -0,0 +1,16 @@
|
||||
#include <Columns/Collator.h>
|
||||
#include <Parsers/ASTInterpolateElement.h>
|
||||
#include <Common/SipHash.h>
|
||||
#include <IO/Operators.h>
|
||||
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
||||
void ASTInterpolateElement::formatImpl(const FormatSettings & settings, FormatState & state, FormatStateStacked frame) const
|
||||
{
|
||||
settings.ostr << column << (settings.hilite ? hilite_keyword : "") << " AS " << (settings.hilite ? hilite_none : "");
|
||||
expr->formatImpl(settings, state, frame);
|
||||
}
|
||||
|
||||
}
|
31
src/Parsers/ASTInterpolateElement.h
Normal file
31
src/Parsers/ASTInterpolateElement.h
Normal file
@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include <Parsers/IAST.h>
|
||||
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
||||
class ASTInterpolateElement : public IAST
|
||||
{
|
||||
public:
|
||||
String column;
|
||||
ASTPtr expr;
|
||||
|
||||
String getID(char delim) const override { return String("InterpolateElement") + delim + "(column " + column + ")"; }
|
||||
|
||||
ASTPtr clone() const override
|
||||
{
|
||||
auto clone = std::make_shared<ASTInterpolateElement>(*this);
|
||||
clone->expr = clone->expr->clone();
|
||||
clone->children.clear();
|
||||
clone->children.push_back(clone->expr);
|
||||
return clone;
|
||||
}
|
||||
|
||||
|
||||
protected:
|
||||
void formatImpl(const FormatSettings & settings, FormatState & state, FormatStateStacked frame) const override;
|
||||
};
|
||||
|
||||
}
|
@ -37,4 +37,5 @@ public:
|
||||
protected:
|
||||
void formatImpl(const FormatSettings & settings, FormatState & state, FormatStateStacked frame) const override;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -129,6 +129,17 @@ void ASTSelectQuery::formatImpl(const FormatSettings & s, FormatState & state, F
|
||||
s.one_line
|
||||
? orderBy()->formatImpl(s, state, frame)
|
||||
: orderBy()->as<ASTExpressionList &>().formatImplMultiline(s, state, frame);
|
||||
|
||||
if (interpolate())
|
||||
{
|
||||
s.ostr << (s.hilite ? hilite_keyword : "") << s.nl_or_ws << indent_str << "INTERPOLATE" << (s.hilite ? hilite_none : "");
|
||||
if (!interpolate()->children.empty())
|
||||
{
|
||||
s.ostr << " (";
|
||||
interpolate()->formatImpl(s, state, frame);
|
||||
s.ostr << " )";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (limitByLength())
|
||||
|
@ -32,7 +32,8 @@ public:
|
||||
LIMIT_BY,
|
||||
LIMIT_OFFSET,
|
||||
LIMIT_LENGTH,
|
||||
SETTINGS
|
||||
SETTINGS,
|
||||
INTERPOLATE
|
||||
};
|
||||
|
||||
static String expressionToString(Expression expr)
|
||||
@ -69,6 +70,8 @@ public:
|
||||
return "LIMIT LENGTH";
|
||||
case Expression::SETTINGS:
|
||||
return "SETTINGS";
|
||||
case Expression::INTERPOLATE:
|
||||
return "INTERPOLATE";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
@ -91,21 +94,22 @@ public:
|
||||
ASTPtr & refWhere() { return getExpression(Expression::WHERE); }
|
||||
ASTPtr & refHaving() { return getExpression(Expression::HAVING); }
|
||||
|
||||
ASTPtr with() const { return getExpression(Expression::WITH); }
|
||||
ASTPtr select() const { return getExpression(Expression::SELECT); }
|
||||
ASTPtr tables() const { return getExpression(Expression::TABLES); }
|
||||
ASTPtr prewhere() const { return getExpression(Expression::PREWHERE); }
|
||||
ASTPtr where() const { return getExpression(Expression::WHERE); }
|
||||
ASTPtr groupBy() const { return getExpression(Expression::GROUP_BY); }
|
||||
ASTPtr having() const { return getExpression(Expression::HAVING); }
|
||||
ASTPtr window() const { return getExpression(Expression::WINDOW); }
|
||||
ASTPtr orderBy() const { return getExpression(Expression::ORDER_BY); }
|
||||
ASTPtr limitByOffset() const { return getExpression(Expression::LIMIT_BY_OFFSET); }
|
||||
ASTPtr limitByLength() const { return getExpression(Expression::LIMIT_BY_LENGTH); }
|
||||
ASTPtr limitBy() const { return getExpression(Expression::LIMIT_BY); }
|
||||
ASTPtr limitOffset() const { return getExpression(Expression::LIMIT_OFFSET); }
|
||||
ASTPtr limitLength() const { return getExpression(Expression::LIMIT_LENGTH); }
|
||||
ASTPtr settings() const { return getExpression(Expression::SETTINGS); }
|
||||
const ASTPtr with() const { return getExpression(Expression::WITH); }
|
||||
const ASTPtr select() const { return getExpression(Expression::SELECT); }
|
||||
const ASTPtr tables() const { return getExpression(Expression::TABLES); }
|
||||
const ASTPtr prewhere() const { return getExpression(Expression::PREWHERE); }
|
||||
const ASTPtr where() const { return getExpression(Expression::WHERE); }
|
||||
const ASTPtr groupBy() const { return getExpression(Expression::GROUP_BY); }
|
||||
const ASTPtr having() const { return getExpression(Expression::HAVING); }
|
||||
const ASTPtr window() const { return getExpression(Expression::WINDOW); }
|
||||
const ASTPtr orderBy() const { return getExpression(Expression::ORDER_BY); }
|
||||
const ASTPtr limitByOffset() const { return getExpression(Expression::LIMIT_BY_OFFSET); }
|
||||
const ASTPtr limitByLength() const { return getExpression(Expression::LIMIT_BY_LENGTH); }
|
||||
const ASTPtr limitBy() const { return getExpression(Expression::LIMIT_BY); }
|
||||
const ASTPtr limitOffset() const { return getExpression(Expression::LIMIT_OFFSET); }
|
||||
const ASTPtr limitLength() const { return getExpression(Expression::LIMIT_LENGTH); }
|
||||
const ASTPtr settings() const { return getExpression(Expression::SETTINGS); }
|
||||
const ASTPtr interpolate() const { return getExpression(Expression::INTERPOLATE); }
|
||||
|
||||
bool hasFiltration() const { return where() || prewhere() || having(); }
|
||||
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include <Parsers/ASTIdentifier.h>
|
||||
#include <Parsers/ASTLiteral.h>
|
||||
#include <Parsers/ASTOrderByElement.h>
|
||||
#include <Parsers/ASTInterpolateElement.h>
|
||||
#include <Parsers/ASTQualifiedAsterisk.h>
|
||||
#include <Parsers/ASTQueryParameter.h>
|
||||
#include <Parsers/ASTSelectWithUnionQuery.h>
|
||||
@ -2316,6 +2317,35 @@ bool ParserOrderByElement::parseImpl(Pos & pos, ASTPtr & node, Expected & expect
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParserInterpolateElement::parseImpl(Pos & pos, ASTPtr & node, Expected & expected)
|
||||
{
|
||||
ParserKeyword as("AS");
|
||||
ParserExpression element_p;
|
||||
ParserIdentifier ident_p;
|
||||
|
||||
ASTPtr ident;
|
||||
if (!ident_p.parse(pos, ident, expected))
|
||||
return false;
|
||||
|
||||
ASTPtr expr;
|
||||
if (as.ignore(pos, expected))
|
||||
{
|
||||
if (!element_p.parse(pos, expr, expected))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
expr = ident;
|
||||
|
||||
auto elem = std::make_shared<ASTInterpolateElement>();
|
||||
elem->column = ident->getColumnName();
|
||||
elem->expr = expr;
|
||||
elem->children.push_back(expr);
|
||||
|
||||
node = elem;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParserFunctionWithKeyValueArguments::parseImpl(Pos & pos, ASTPtr & node, Expected & expected)
|
||||
{
|
||||
ParserIdentifier id_parser;
|
||||
|
@ -420,6 +420,15 @@ protected:
|
||||
bool parseImpl(Pos & pos, ASTPtr & node, Expected & expected) override;
|
||||
};
|
||||
|
||||
/** Element of INTERPOLATE expression
|
||||
*/
|
||||
class ParserInterpolateElement : public IParserBase
|
||||
{
|
||||
protected:
|
||||
const char * getName() const override { return "element of INTERPOLATE expression"; }
|
||||
bool parseImpl(Pos & pos, ASTPtr & node, Expected & expected) override;
|
||||
};
|
||||
|
||||
/** Parser for function with arguments like KEY VALUE (space separated)
|
||||
* no commas allowed, just space-separated pairs.
|
||||
*/
|
||||
|
@ -763,6 +763,13 @@ bool ParserOrderByExpressionList::parseImpl(Pos & pos, ASTPtr & node, Expected &
|
||||
}
|
||||
|
||||
|
||||
bool ParserInterpolateExpressionList::parseImpl(Pos & pos, ASTPtr & node, Expected & expected)
|
||||
{
|
||||
return ParserList(std::make_unique<ParserInterpolateElement>(), std::make_unique<ParserToken>(TokenType::Comma), true)
|
||||
.parse(pos, node, expected);
|
||||
}
|
||||
|
||||
|
||||
bool ParserTTLExpressionList::parseImpl(Pos & pos, ASTPtr & node, Expected & expected)
|
||||
{
|
||||
return ParserList(std::make_unique<ParserTTLElement>(), std::make_unique<ParserToken>(TokenType::Comma), false)
|
||||
|
@ -517,6 +517,12 @@ protected:
|
||||
bool parseImpl(Pos & pos, ASTPtr & node, Expected & expected) override;
|
||||
};
|
||||
|
||||
class ParserInterpolateExpressionList : public IParserBase
|
||||
{
|
||||
protected:
|
||||
const char * getName() const override { return "interpolate expression"; }
|
||||
bool parseImpl(Pos & pos, ASTPtr & node, Expected & expected) override;
|
||||
};
|
||||
|
||||
/// Parser for key-value pair, where value can be list of pairs.
|
||||
class ParserKeyValuePair : public IParserBase
|
||||
|
@ -10,6 +10,10 @@
|
||||
#include <Parsers/ParserSelectQuery.h>
|
||||
#include <Parsers/ParserTablesInSelectQuery.h>
|
||||
#include <Parsers/ParserWithElement.h>
|
||||
#include <Parsers/ASTOrderByElement.h>
|
||||
#include <Parsers/ASTExpressionList.h>
|
||||
#include <Parsers/ASTInterpolateElement.h>
|
||||
#include <Parsers/ASTIdentifier.h>
|
||||
|
||||
|
||||
namespace DB
|
||||
@ -59,12 +63,14 @@ bool ParserSelectQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expected)
|
||||
ParserKeyword s_rows("ROWS");
|
||||
ParserKeyword s_first("FIRST");
|
||||
ParserKeyword s_next("NEXT");
|
||||
ParserKeyword s_interpolate("INTERPOLATE");
|
||||
|
||||
ParserNotEmptyExpressionList exp_list(false);
|
||||
ParserNotEmptyExpressionList exp_list_for_with_clause(false);
|
||||
ParserNotEmptyExpressionList exp_list_for_select_clause(true); /// Allows aliases without AS keyword.
|
||||
ParserExpressionWithOptionalAlias exp_elem(false);
|
||||
ParserOrderByExpressionList order_list;
|
||||
ParserInterpolateExpressionList interpolate_list;
|
||||
|
||||
ParserToken open_bracket(TokenType::OpeningRoundBracket);
|
||||
ParserToken close_bracket(TokenType::ClosingRoundBracket);
|
||||
@ -78,6 +84,7 @@ bool ParserSelectQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expected)
|
||||
ASTPtr having_expression;
|
||||
ASTPtr window_list;
|
||||
ASTPtr order_expression_list;
|
||||
ASTPtr interpolate_expression_list;
|
||||
ASTPtr limit_by_length;
|
||||
ASTPtr limit_by_offset;
|
||||
ASTPtr limit_by_expression_list;
|
||||
@ -239,6 +246,23 @@ bool ParserSelectQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expected)
|
||||
{
|
||||
if (!order_list.parse(pos, order_expression_list, expected))
|
||||
return false;
|
||||
|
||||
/// if any WITH FILL parse possible INTERPOLATE list
|
||||
if (std::any_of(order_expression_list->children.begin(), order_expression_list->children.end(),
|
||||
[](auto & child) { return child->template as<ASTOrderByElement>()->with_fill; }))
|
||||
{
|
||||
if (s_interpolate.ignore(pos, expected))
|
||||
{
|
||||
if (open_bracket.ignore(pos, expected))
|
||||
{
|
||||
if (!interpolate_list.parse(pos, interpolate_expression_list, expected))
|
||||
return false;
|
||||
if (!close_bracket.ignore(pos, expected))
|
||||
return false;
|
||||
} else
|
||||
interpolate_expression_list = std::make_shared<ASTExpressionList>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This is needed for TOP expression, because it can also use WITH TIES.
|
||||
@ -430,6 +454,7 @@ bool ParserSelectQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expected)
|
||||
select_query->setExpression(ASTSelectQuery::Expression::LIMIT_OFFSET, std::move(limit_offset));
|
||||
select_query->setExpression(ASTSelectQuery::Expression::LIMIT_LENGTH, std::move(limit_length));
|
||||
select_query->setExpression(ASTSelectQuery::Expression::SETTINGS, std::move(settings));
|
||||
select_query->setExpression(ASTSelectQuery::Expression::INTERPOLATE, std::move(interpolate_expression_list));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -28,9 +28,9 @@ static ITransformingStep::Traits getTraits()
|
||||
};
|
||||
}
|
||||
|
||||
FillingStep::FillingStep(const DataStream & input_stream_, SortDescription sort_description_)
|
||||
FillingStep::FillingStep(const DataStream & input_stream_, SortDescription sort_description_, InterpolateDescriptionPtr interpolate_description_)
|
||||
: ITransformingStep(input_stream_, FillingTransform::transformHeader(input_stream_.header, sort_description_), getTraits())
|
||||
, sort_description(std::move(sort_description_))
|
||||
, sort_description(std::move(sort_description_)), interpolate_description(interpolate_description_)
|
||||
{
|
||||
if (!input_stream_.has_single_port)
|
||||
throw Exception("FillingStep expects single input", ErrorCodes::LOGICAL_ERROR);
|
||||
@ -41,7 +41,7 @@ void FillingStep::transformPipeline(QueryPipelineBuilder & pipeline, const Build
|
||||
pipeline.addSimpleTransform([&](const Block & header, QueryPipelineBuilder::StreamType stream_type) -> ProcessorPtr
|
||||
{
|
||||
bool on_totals = stream_type == QueryPipelineBuilder::StreamType::Totals;
|
||||
return std::make_shared<FillingTransform>(header, sort_description, on_totals);
|
||||
return std::make_shared<FillingTransform>(header, sort_description, std::move(interpolate_description), on_totals);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
#include <Processors/QueryPlan/ITransformingStep.h>
|
||||
#include <Core/SortDescription.h>
|
||||
#include <Core/InterpolateDescription.h>
|
||||
|
||||
namespace DB
|
||||
{
|
||||
@ -9,7 +10,7 @@ namespace DB
|
||||
class FillingStep : public ITransformingStep
|
||||
{
|
||||
public:
|
||||
FillingStep(const DataStream & input_stream_, SortDescription sort_description_);
|
||||
FillingStep(const DataStream & input_stream_, SortDescription sort_description_, InterpolateDescriptionPtr interpolate_description_);
|
||||
|
||||
String getName() const override { return "Filling"; }
|
||||
|
||||
@ -22,6 +23,7 @@ public:
|
||||
|
||||
private:
|
||||
SortDescription sort_description;
|
||||
InterpolateDescriptionPtr interpolate_description;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ namespace ErrorCodes
|
||||
extern const int INVALID_WITH_FILL_EXPRESSION;
|
||||
}
|
||||
|
||||
Block FillingTransform::transformHeader(Block header, const SortDescription & sort_description)
|
||||
Block FillingTransform::transformHeader(Block header, const SortDescription & sort_description/*, const InterpolateDescription & interpolate_description*/)
|
||||
{
|
||||
NameSet sort_keys;
|
||||
for (const auto & key : sort_description)
|
||||
@ -138,19 +138,28 @@ static bool tryConvertFields(FillColumnDescription & descr, const DataTypePtr &
|
||||
}
|
||||
|
||||
FillingTransform::FillingTransform(
|
||||
const Block & header_, const SortDescription & sort_description_, bool on_totals_)
|
||||
: ISimpleTransform(header_, transformHeader(header_, sort_description_), true)
|
||||
, sort_description(sort_description_)
|
||||
, on_totals(on_totals_)
|
||||
, filling_row(sort_description_)
|
||||
, next_row(sort_description_)
|
||||
const Block & header_, const SortDescription & sort_description_, InterpolateDescriptionPtr interpolate_description_, bool on_totals_)
|
||||
: ISimpleTransform(header_, transformHeader(header_, sort_description_), true)
|
||||
, sort_description(sort_description_)
|
||||
, interpolate_description(interpolate_description_)
|
||||
, on_totals(on_totals_)
|
||||
, filling_row(sort_description_)
|
||||
, next_row(sort_description_)
|
||||
{
|
||||
if (on_totals)
|
||||
return;
|
||||
|
||||
if (interpolate_description)
|
||||
interpolate_actions = std::make_shared<ExpressionActions>(interpolate_description->actions);
|
||||
|
||||
std::vector<bool> is_fill_column(header_.columns());
|
||||
for (size_t i = 0, size = sort_description.size(); i < size; ++i)
|
||||
{
|
||||
if (interpolate_description && interpolate_description->result_columns_set.count(sort_description[i].column_name))
|
||||
throw Exception(ErrorCodes::INVALID_WITH_FILL_EXPRESSION,
|
||||
"Column '{}' is participating in ORDER BY ... WITH FILL expression and can't be INTERPOLATE output",
|
||||
sort_description[i].column_name);
|
||||
|
||||
size_t block_position = header_.getPositionByName(sort_description[i].column_name);
|
||||
is_fill_column[block_position] = true;
|
||||
fill_column_positions.push_back(block_position);
|
||||
@ -176,9 +185,23 @@ FillingTransform::FillingTransform(
|
||||
if (!unique_positions.insert(pos).second)
|
||||
throw Exception("Multiple WITH FILL for identical expressions is not supported in ORDER BY", ErrorCodes::INVALID_WITH_FILL_EXPRESSION);
|
||||
|
||||
for (size_t i = 0; i < header_.columns(); ++i)
|
||||
if (!is_fill_column[i])
|
||||
other_column_positions.push_back(i);
|
||||
size_t idx = 0;
|
||||
for (const ColumnWithTypeAndName & column : header_.getColumnsWithTypeAndName())
|
||||
{
|
||||
if (interpolate_description)
|
||||
if (const auto & p = interpolate_description->required_columns_map.find(column.name);
|
||||
p != interpolate_description->required_columns_map.end())
|
||||
input_positions.emplace_back(idx, p->second);
|
||||
|
||||
if (!is_fill_column[idx] && !(interpolate_description && interpolate_description->result_columns_set.count(column.name)))
|
||||
other_column_positions.push_back(idx);
|
||||
|
||||
++idx;
|
||||
}
|
||||
|
||||
if (interpolate_description)
|
||||
for (const auto & name : interpolate_description->result_columns_order)
|
||||
interpolate_column_positions.push_back(header_.getPositionByName(name));
|
||||
}
|
||||
|
||||
IProcessor::Status FillingTransform::prepare()
|
||||
@ -207,37 +230,90 @@ void FillingTransform::transform(Chunk & chunk)
|
||||
return;
|
||||
|
||||
Columns old_fill_columns;
|
||||
Columns old_interpolate_columns;
|
||||
Columns old_other_columns;
|
||||
MutableColumns res_fill_columns;
|
||||
MutableColumns res_interpolate_columns;
|
||||
MutableColumns res_other_columns;
|
||||
|
||||
auto init_columns_by_positions = [](const Columns & old_columns, Columns & new_columns,
|
||||
MutableColumns & new_mutable_columns, const Positions & positions)
|
||||
std::vector<std::pair<MutableColumns *, size_t>> res_map;
|
||||
res_map.resize(input.getHeader().columns());
|
||||
|
||||
auto init_columns_by_positions = [&res_map](const Columns & old_columns, Columns & new_columns,
|
||||
MutableColumns & new_mutable_columns, const Positions & positions)
|
||||
{
|
||||
for (size_t pos : positions)
|
||||
{
|
||||
auto old_column = old_columns[pos]->convertToFullColumnIfConst();
|
||||
new_columns.push_back(old_column);
|
||||
res_map[pos] = {&new_mutable_columns, new_mutable_columns.size()};
|
||||
new_mutable_columns.push_back(old_column->cloneEmpty()->assumeMutable());
|
||||
}
|
||||
};
|
||||
|
||||
Block interpolate_block;
|
||||
|
||||
auto interpolate = [&]()
|
||||
{
|
||||
if (interpolate_description)
|
||||
{
|
||||
interpolate_block.clear();
|
||||
|
||||
if (!input_positions.empty())
|
||||
{
|
||||
/// populate calculation block with required columns with values from previous row
|
||||
for (const auto & [col_pos, name_type] : input_positions)
|
||||
{
|
||||
MutableColumnPtr column = name_type.type->createColumn();
|
||||
auto [res_columns, pos] = res_map[col_pos];
|
||||
size_t size = (*res_columns)[pos]->size();
|
||||
if (size == 0) /// this is the first row in current chunk
|
||||
{
|
||||
/// take value from last row of previous chunk if exists, else use default
|
||||
if (last_row.size() > col_pos && !last_row[col_pos]->empty())
|
||||
column->insertFrom(*last_row[col_pos], 0);
|
||||
else
|
||||
column->insertDefault();
|
||||
}
|
||||
else /// take value from previous row of current chunk
|
||||
column->insertFrom(*(*res_columns)[pos], size - 1);
|
||||
|
||||
interpolate_block.insert({std::move(column), name_type.type, name_type.name});
|
||||
}
|
||||
interpolate_actions->execute(interpolate_block);
|
||||
}
|
||||
else /// all INTERPOLATE expressions are constants
|
||||
{
|
||||
size_t n = 1;
|
||||
interpolate_actions->execute(interpolate_block, n);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (generate_suffix)
|
||||
{
|
||||
const auto & empty_columns = input.getHeader().getColumns();
|
||||
init_columns_by_positions(empty_columns, old_fill_columns, res_fill_columns, fill_column_positions);
|
||||
init_columns_by_positions(empty_columns, old_interpolate_columns, res_interpolate_columns, interpolate_column_positions);
|
||||
init_columns_by_positions(empty_columns, old_other_columns, res_other_columns, other_column_positions);
|
||||
|
||||
if (first)
|
||||
filling_row.initFromDefaults();
|
||||
|
||||
if (should_insert_first && filling_row < next_row)
|
||||
insertFromFillingRow(res_fill_columns, res_other_columns, filling_row);
|
||||
{
|
||||
interpolate();
|
||||
insertFromFillingRow(res_fill_columns, res_interpolate_columns, res_other_columns, filling_row, interpolate_block);
|
||||
}
|
||||
|
||||
interpolate();
|
||||
while (filling_row.next(next_row))
|
||||
insertFromFillingRow(res_fill_columns, res_other_columns, filling_row);
|
||||
{
|
||||
insertFromFillingRow(res_fill_columns, res_interpolate_columns, res_other_columns, filling_row, interpolate_block);
|
||||
interpolate();
|
||||
}
|
||||
|
||||
setResultColumns(chunk, res_fill_columns, res_other_columns);
|
||||
setResultColumns(chunk, res_fill_columns, res_interpolate_columns, res_other_columns);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -245,6 +321,7 @@ void FillingTransform::transform(Chunk & chunk)
|
||||
auto old_columns = chunk.detachColumns();
|
||||
|
||||
init_columns_by_positions(old_columns, old_fill_columns, res_fill_columns, fill_column_positions);
|
||||
init_columns_by_positions(old_columns, old_interpolate_columns, res_interpolate_columns, interpolate_column_positions);
|
||||
init_columns_by_positions(old_columns, old_other_columns, res_other_columns, other_column_positions);
|
||||
|
||||
if (first)
|
||||
@ -258,7 +335,10 @@ void FillingTransform::transform(Chunk & chunk)
|
||||
{
|
||||
filling_row.initFromDefaults(i);
|
||||
if (less(fill_from, current_value, filling_row.getDirection(i)))
|
||||
insertFromFillingRow(res_fill_columns, res_other_columns, filling_row);
|
||||
{
|
||||
interpolate();
|
||||
insertFromFillingRow(res_fill_columns, res_interpolate_columns, res_other_columns, filling_row, interpolate_block);
|
||||
}
|
||||
break;
|
||||
}
|
||||
filling_row[i] = current_value;
|
||||
@ -284,31 +364,72 @@ void FillingTransform::transform(Chunk & chunk)
|
||||
/// A case, when at previous step row was initialized from defaults 'fill_from' values
|
||||
/// and probably we need to insert it to block.
|
||||
if (should_insert_first && filling_row < next_row)
|
||||
insertFromFillingRow(res_fill_columns, res_other_columns, filling_row);
|
||||
{
|
||||
interpolate();
|
||||
insertFromFillingRow(res_fill_columns, res_interpolate_columns, res_other_columns, filling_row, interpolate_block);
|
||||
}
|
||||
|
||||
/// Insert generated filling row to block, while it is less than current row in block.
|
||||
interpolate();
|
||||
while (filling_row.next(next_row))
|
||||
insertFromFillingRow(res_fill_columns, res_other_columns, filling_row);
|
||||
{
|
||||
insertFromFillingRow(res_fill_columns, res_interpolate_columns, res_other_columns, filling_row, interpolate_block);
|
||||
interpolate();
|
||||
}
|
||||
|
||||
copyRowFromColumns(res_fill_columns, old_fill_columns, row_ind);
|
||||
copyRowFromColumns(res_interpolate_columns, old_interpolate_columns, row_ind);
|
||||
copyRowFromColumns(res_other_columns, old_other_columns, row_ind);
|
||||
}
|
||||
|
||||
setResultColumns(chunk, res_fill_columns, res_other_columns);
|
||||
saveLastRow(res_fill_columns, res_interpolate_columns, res_other_columns);
|
||||
setResultColumns(chunk, res_fill_columns, res_interpolate_columns, res_other_columns);
|
||||
}
|
||||
|
||||
void FillingTransform::setResultColumns(Chunk & chunk, MutableColumns & fill_columns, MutableColumns & other_columns) const
|
||||
void FillingTransform::setResultColumns(Chunk & chunk, MutableColumns & fill_columns, MutableColumns & interpolate_columns, MutableColumns & other_columns) const
|
||||
{
|
||||
MutableColumns result_columns(fill_columns.size() + other_columns.size());
|
||||
MutableColumns result_columns(fill_columns.size() + interpolate_columns.size() + other_columns.size());
|
||||
/// fill_columns always non-empty.
|
||||
size_t num_rows = fill_columns[0]->size();
|
||||
|
||||
for (size_t i = 0, size = fill_columns.size(); i < size; ++i)
|
||||
result_columns[fill_column_positions[i]] = std::move(fill_columns[i]);
|
||||
for (size_t i = 0, size = interpolate_columns.size(); i < size; ++i)
|
||||
result_columns[interpolate_column_positions[i]] = std::move(interpolate_columns[i]);
|
||||
for (size_t i = 0, size = other_columns.size(); i < size; ++i)
|
||||
result_columns[other_column_positions[i]] = std::move(other_columns[i]);
|
||||
|
||||
chunk.setColumns(std::move(result_columns), num_rows);
|
||||
}
|
||||
|
||||
void FillingTransform::saveLastRow(const MutableColumns & fill_columns, const MutableColumns & interpolate_columns, const MutableColumns & other_columns)
|
||||
{
|
||||
last_row.clear();
|
||||
last_row.resize(fill_columns.size() + interpolate_columns.size() + other_columns.size());
|
||||
|
||||
size_t num_rows = fill_columns[0]->size();
|
||||
if (num_rows == 0)
|
||||
return;
|
||||
|
||||
for (size_t i = 0, size = fill_columns.size(); i < size; ++i)
|
||||
{
|
||||
auto column = fill_columns[i]->cloneEmpty();
|
||||
column->insertFrom(*fill_columns[i], num_rows - 1);
|
||||
last_row[fill_column_positions[i]] = std::move(column);
|
||||
}
|
||||
|
||||
for (size_t i = 0, size = interpolate_columns.size(); i < size; ++i)
|
||||
{
|
||||
auto column = interpolate_columns[i]->cloneEmpty();
|
||||
column->insertFrom(*interpolate_columns[i], num_rows - 1);
|
||||
last_row[interpolate_column_positions[i]] = std::move(column);
|
||||
}
|
||||
|
||||
for (size_t i = 0, size = other_columns.size(); i < size; ++i)
|
||||
{
|
||||
auto column = other_columns[i]->cloneEmpty();
|
||||
column->insertFrom(*other_columns[i], num_rows - 1);
|
||||
last_row[other_column_positions[i]] = std::move(column);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
#include <Processors/ISimpleTransform.h>
|
||||
#include <Core/SortDescription.h>
|
||||
#include <Core/InterpolateDescription.h>
|
||||
#include <Interpreters/FillingRow.h>
|
||||
|
||||
namespace DB
|
||||
@ -13,7 +14,7 @@ namespace DB
|
||||
class FillingTransform : public ISimpleTransform
|
||||
{
|
||||
public:
|
||||
FillingTransform(const Block & header_, const SortDescription & sort_description_, bool on_totals_);
|
||||
FillingTransform(const Block & header_, const SortDescription & sort_description_, InterpolateDescriptionPtr interpolate_description_, bool on_totals_);
|
||||
|
||||
String getName() const override { return "FillingTransform"; }
|
||||
|
||||
@ -25,9 +26,11 @@ protected:
|
||||
void transform(Chunk & Chunk) override;
|
||||
|
||||
private:
|
||||
void setResultColumns(Chunk & chunk, MutableColumns & fill_columns, MutableColumns & other_columns) const;
|
||||
void setResultColumns(Chunk & chunk, MutableColumns & fill_columns, MutableColumns & interpolate_columns, MutableColumns & other_columns) const;
|
||||
void saveLastRow(const MutableColumns & fill_columns, const MutableColumns & interpolate_columns, const MutableColumns & other_columns);
|
||||
|
||||
const SortDescription sort_description; /// Contains only rows with WITH FILL.
|
||||
const SortDescription sort_description; /// Contains only columns with WITH FILL.
|
||||
const InterpolateDescriptionPtr interpolate_description; /// Contains INTERPOLATE columns
|
||||
const bool on_totals; /// FillingTransform does nothing on totals.
|
||||
|
||||
FillingRow filling_row; /// Current row, which is used to fill gaps.
|
||||
@ -35,10 +38,15 @@ private:
|
||||
|
||||
using Positions = std::vector<size_t>;
|
||||
Positions fill_column_positions;
|
||||
Positions interpolate_column_positions;
|
||||
Positions other_column_positions;
|
||||
std::vector<std::pair<size_t, NameAndTypePair>> input_positions; /// positions in result columns required for actions
|
||||
ExpressionActionsPtr interpolate_actions;
|
||||
bool first = true;
|
||||
bool generate_suffix = false;
|
||||
|
||||
Columns last_row;
|
||||
|
||||
/// Determines should we insert filling row before start generating next rows.
|
||||
bool should_insert_first = false;
|
||||
};
|
||||
|
240
tests/queries/0_stateless/02233_interpolate_1.reference
Normal file
240
tests/queries/0_stateless/02233_interpolate_1.reference
Normal file
@ -0,0 +1,240 @@
|
||||
0 0
|
||||
0.5 0
|
||||
1 original 1
|
||||
1.5 0
|
||||
2 0
|
||||
2.5 0
|
||||
3 0
|
||||
3.5 0
|
||||
4 original 4
|
||||
4.5 0
|
||||
5 0
|
||||
5.5 0
|
||||
6 0
|
||||
6.5 0
|
||||
7 original 7
|
||||
7.5 0
|
||||
8 0
|
||||
8.5 0
|
||||
9 0
|
||||
9.5 0
|
||||
10 0
|
||||
10.5 0
|
||||
11 0
|
||||
11.5 0
|
||||
0 42
|
||||
0.5 42
|
||||
1 original 1
|
||||
1.5 42
|
||||
2 42
|
||||
2.5 42
|
||||
3 42
|
||||
3.5 42
|
||||
4 original 4
|
||||
4.5 42
|
||||
5 42
|
||||
5.5 42
|
||||
6 42
|
||||
6.5 42
|
||||
7 original 7
|
||||
7.5 42
|
||||
8 42
|
||||
8.5 42
|
||||
9 42
|
||||
9.5 42
|
||||
10 42
|
||||
10.5 42
|
||||
11 42
|
||||
11.5 42
|
||||
0 0
|
||||
0.5 0
|
||||
1 original 1
|
||||
1.5 1
|
||||
2 1
|
||||
2.5 1
|
||||
3 1
|
||||
3.5 1
|
||||
4 original 4
|
||||
4.5 4
|
||||
5 4
|
||||
5.5 4
|
||||
6 4
|
||||
6.5 4
|
||||
7 original 7
|
||||
7.5 7
|
||||
8 7
|
||||
8.5 7
|
||||
9 7
|
||||
9.5 7
|
||||
10 7
|
||||
10.5 7
|
||||
11 7
|
||||
11.5 7
|
||||
0 1
|
||||
0.5 2
|
||||
1 original 1
|
||||
1.5 2
|
||||
2 3
|
||||
2.5 4
|
||||
3 5
|
||||
3.5 6
|
||||
4 original 4
|
||||
4.5 5
|
||||
5 6
|
||||
5.5 7
|
||||
6 8
|
||||
6.5 9
|
||||
7 original 7
|
||||
7.5 8
|
||||
8 9
|
||||
8.5 10
|
||||
9 11
|
||||
9.5 12
|
||||
10 13
|
||||
10.5 14
|
||||
11 15
|
||||
11.5 16
|
||||
0 1
|
||||
0.5 2
|
||||
1 original 2
|
||||
1.5 3
|
||||
2 4
|
||||
2.5 5
|
||||
3 6
|
||||
3.5 7
|
||||
4 original 5
|
||||
4.5 6
|
||||
5 7
|
||||
5.5 8
|
||||
6 9
|
||||
6.5 10
|
||||
7 original 8
|
||||
7.5 9
|
||||
8 10
|
||||
8.5 11
|
||||
9 12
|
||||
9.5 13
|
||||
10 14
|
||||
10.5 15
|
||||
11 16
|
||||
11.5 17
|
||||
0
|
||||
0
|
||||
original 1
|
||||
3
|
||||
3
|
||||
3
|
||||
3
|
||||
3
|
||||
original 4
|
||||
9
|
||||
9
|
||||
9
|
||||
9
|
||||
9
|
||||
original 7
|
||||
15
|
||||
15
|
||||
15
|
||||
15
|
||||
15
|
||||
15
|
||||
15
|
||||
15
|
||||
15
|
||||
0 0
|
||||
0.5 0
|
||||
1 original 1
|
||||
1.5 3
|
||||
2 3
|
||||
2.5 3
|
||||
3 3
|
||||
3.5 3
|
||||
4 original 4
|
||||
4.5 9
|
||||
5 9
|
||||
5.5 9
|
||||
6 9
|
||||
6.5 9
|
||||
7 original 7
|
||||
7.5 15
|
||||
8 15
|
||||
8.5 15
|
||||
9 15
|
||||
9.5 15
|
||||
10 15
|
||||
10.5 15
|
||||
11 15
|
||||
11.5 15
|
||||
0 1
|
||||
0.5 2
|
||||
1 original 1
|
||||
1.5 2
|
||||
2 3
|
||||
2.5 4
|
||||
3 5
|
||||
3.5 6
|
||||
4 original 4
|
||||
4.5 5
|
||||
5 6
|
||||
5.5 7
|
||||
6 8
|
||||
6.5 9
|
||||
7 original 7
|
||||
7.5 8
|
||||
8 9
|
||||
8.5 10
|
||||
9 11
|
||||
9.5 12
|
||||
10 13
|
||||
10.5 14
|
||||
11 15
|
||||
11.5 16
|
||||
0 \N
|
||||
0.5 \N
|
||||
1 original \N
|
||||
1.5 \N
|
||||
2 \N
|
||||
2.5 \N
|
||||
3 \N
|
||||
3.5 \N
|
||||
4 original \N
|
||||
4.5 \N
|
||||
5 \N
|
||||
5.5 \N
|
||||
6 \N
|
||||
6.5 \N
|
||||
7 original \N
|
||||
7.5 \N
|
||||
8 \N
|
||||
8.5 \N
|
||||
9 \N
|
||||
9.5 \N
|
||||
10 \N
|
||||
10.5 \N
|
||||
11 \N
|
||||
11.5 \N
|
||||
0 \N
|
||||
0.5 \N
|
||||
1 original \N
|
||||
1.5 \N
|
||||
2 \N
|
||||
2.5 \N
|
||||
3 \N
|
||||
3.5 \N
|
||||
4 original \N
|
||||
4.5 \N
|
||||
5 \N
|
||||
5.5 \N
|
||||
6 \N
|
||||
6.5 \N
|
||||
7 original \N
|
||||
7.5 \N
|
||||
8 \N
|
||||
8.5 \N
|
||||
9 \N
|
||||
9.5 \N
|
||||
10 \N
|
||||
10.5 \N
|
||||
11 \N
|
||||
11.5 \N
|
72
tests/queries/0_stateless/02233_interpolate_1.sql
Normal file
72
tests/queries/0_stateless/02233_interpolate_1.sql
Normal file
@ -0,0 +1,72 @@
|
||||
# Test WITH FILL without INTERPOLATE
|
||||
SELECT n, source, inter FROM (
|
||||
SELECT toFloat32(number % 10) AS n, 'original' AS source, number as inter FROM numbers(10) WHERE number % 3 = 1
|
||||
) ORDER BY n WITH FILL FROM 0 TO 11.51 STEP 0.5;
|
||||
|
||||
# Test INTERPOLATE with const
|
||||
SELECT n, source, inter FROM (
|
||||
SELECT toFloat32(number % 10) AS n, 'original' AS source, number as inter FROM numbers(10) WHERE number % 3 = 1
|
||||
) ORDER BY n WITH FILL FROM 0 TO 11.51 STEP 0.5 INTERPOLATE (inter AS 42);
|
||||
|
||||
# Test INTERPOLATE with field value
|
||||
SELECT n, source, inter FROM (
|
||||
SELECT toFloat32(number % 10) AS n, 'original' AS source, number as inter FROM numbers(10) WHERE number % 3 = 1
|
||||
) ORDER BY n WITH FILL FROM 0 TO 11.51 STEP 0.5 INTERPOLATE (inter AS inter);
|
||||
|
||||
# Test INTERPOLATE with expression
|
||||
SELECT n, source, inter FROM (
|
||||
SELECT toFloat32(number % 10) AS n, 'original' AS source, number as inter FROM numbers(10) WHERE number % 3 = 1
|
||||
) ORDER BY n WITH FILL FROM 0 TO 11.51 STEP 0.5 INTERPOLATE (inter AS inter + 1);
|
||||
|
||||
# Test INTERPOLATE with incompatible const - should produce error
|
||||
SELECT n, source, inter FROM (
|
||||
SELECT toFloat32(number % 10) AS n, 'original' AS source, number as inter FROM numbers(10) WHERE number % 3 = 1
|
||||
) ORDER BY n WITH FILL FROM 0 TO 11.51 STEP 0.5 INTERPOLATE (inter AS 'inter'); -- { serverError 6 }
|
||||
|
||||
# Test INTERPOLATE with incompatible expression - should produce error
|
||||
SELECT n, source, inter FROM (
|
||||
SELECT toFloat32(number % 10) AS n, 'original' AS source, number as inter FROM numbers(10) WHERE number % 3 = 1
|
||||
) ORDER BY n WITH FILL FROM 0 TO 11.51 STEP 0.5 INTERPOLATE (inter AS inter||'inter'); -- { serverError 44 }
|
||||
|
||||
# Test INTERPOLATE with column from WITH FILL expression - should produce error
|
||||
SELECT n, source, inter FROM (
|
||||
SELECT toFloat32(number % 10) AS n, 'original' AS source, number as inter FROM numbers(10) WHERE number % 3 = 1
|
||||
) ORDER BY n WITH FILL FROM 0 TO 11.51 STEP 0.5 INTERPOLATE (n AS n); -- { serverError 475 }
|
||||
|
||||
# Test INTERPOLATE with inconsistent column - should produce error
|
||||
SELECT n, source, inter FROM (
|
||||
SELECT toFloat32(number % 10) AS n, 'original' AS source, number as inter FROM numbers(10) WHERE number % 3 = 1
|
||||
) ORDER BY n WITH FILL FROM 0 TO 11.51 STEP 0.5 INTERPOLATE (inter AS source); -- { serverError 32 }
|
||||
|
||||
# Test INTERPOLATE with aliased column
|
||||
SELECT n, source, inter + 1 AS inter_p FROM (
|
||||
SELECT toFloat32(number % 10) AS n, 'original' AS source, number AS inter FROM numbers(10) WHERE (number % 3) = 1
|
||||
) ORDER BY n ASC WITH FILL FROM 0 TO 11.51 STEP 0.5 INTERPOLATE ( inter_p AS inter_p + 1 );
|
||||
|
||||
# Test INTERPOLATE with column not present in select
|
||||
SELECT source, inter FROM (
|
||||
SELECT toFloat32(number % 10) AS n, 'original' AS source, number AS inter, number + 1 AS inter2 FROM numbers(10) WHERE (number % 3) = 1
|
||||
) ORDER BY n ASC WITH FILL FROM 0 TO 11.51 STEP 0.5 INTERPOLATE ( inter AS inter2 + inter );
|
||||
|
||||
# Test INTERPOLATE in sub-select
|
||||
SELECT n, source, inter FROM (
|
||||
SELECT n, source, inter, inter2 FROM (
|
||||
SELECT toFloat32(number % 10) AS n, 'original' AS source, number AS inter, number + 1 AS inter2 FROM numbers(10) WHERE (number % 3) = 1
|
||||
) ORDER BY n ASC WITH FILL FROM 0 TO 11.51 STEP 0.5 INTERPOLATE ( inter AS inter + inter2 )
|
||||
);
|
||||
|
||||
# Test INTERPOLATE with aggregates
|
||||
SELECT n, any(source), sum(inter) AS inter_s FROM (
|
||||
SELECT toFloat32(number % 10) AS n, 'original' AS source, number AS inter FROM numbers(10) WHERE (number % 3) = 1
|
||||
) GROUP BY n
|
||||
ORDER BY n ASC WITH FILL FROM 0 TO 11.51 STEP 0.5 INTERPOLATE ( inter_s AS inter_s + 1 );
|
||||
|
||||
# Test INTERPOLATE with Nullable in result
|
||||
SELECT n, source, inter + NULL AS inter_p FROM (
|
||||
SELECT toFloat32(number % 10) AS n, 'original' AS source, number AS inter FROM numbers(10) WHERE (number % 3) = 1
|
||||
) ORDER BY n ASC WITH FILL FROM 0 TO 11.51 STEP 0.5 INTERPOLATE ( inter_p AS inter_p + 1 );
|
||||
|
||||
# Test INTERPOLATE with Nullable in source
|
||||
SELECT n, source, inter AS inter_p FROM (
|
||||
SELECT toFloat32(number % 10) AS n, 'original' AS source, number + NULL AS inter FROM numbers(10) WHERE (number % 3) = 1
|
||||
) ORDER BY n ASC WITH FILL FROM 0 TO 11.51 STEP 0.5 INTERPOLATE ( inter_p AS inter_p + 1 );
|
Loading…
Reference in New Issue
Block a user