Add Interval arithmetics

This commit is contained in:
Nikolay Degterinsky 2022-10-14 02:16:12 +00:00
parent 68abf43767
commit af1d306b12
6 changed files with 380 additions and 42 deletions

View File

@ -408,6 +408,7 @@ inline bool isDecimal(const DataTypePtr & data_type) { return WhichDataType(data
inline bool isTuple(const DataTypePtr & data_type) { return WhichDataType(data_type).isTuple(); }
inline bool isArray(const DataTypePtr & data_type) { return WhichDataType(data_type).isArray(); }
inline bool isMap(const DataTypePtr & data_type) {return WhichDataType(data_type).isMap(); }
inline bool isInterval(const DataTypePtr & data_type) {return WhichDataType(data_type).isInterval(); }
inline bool isNothing(const DataTypePtr & data_type) { return WhichDataType(data_type).isNothing(); }
inline bool isUUID(const DataTypePtr & data_type) { return WhichDataType(data_type).isUUID(); }

View File

@ -22,6 +22,7 @@
#include <DataTypes/DataTypeFactory.h>
#include <DataTypes/DataTypeFixedString.h>
#include <DataTypes/DataTypeInterval.h>
#include <DataTypes/DataTypeTuple.h>
#include <DataTypes/DataTypeString.h>
#include <DataTypes/DataTypesDecimal.h>
#include <DataTypes/DataTypesNumber.h>
@ -633,7 +634,8 @@ class FunctionBinaryArithmetic : public IFunction
DataTypeInt8, DataTypeInt16, DataTypeInt32, DataTypeInt64, DataTypeInt128, DataTypeInt256,
DataTypeDecimal32, DataTypeDecimal64, DataTypeDecimal128, DataTypeDecimal256,
DataTypeDate, DataTypeDateTime,
DataTypeFixedString, DataTypeString>;
DataTypeFixedString, DataTypeString,
DataTypeInterval>;
using Floats = TypeList<DataTypeFloat32, DataTypeFloat64>;
@ -709,10 +711,10 @@ class FunctionBinaryArithmetic : public IFunction
}
static FunctionOverloadResolverPtr
getFunctionForTupleOfIntervalsArithmetic(const DataTypePtr & type0, const DataTypePtr & type1, ContextPtr context)
getFunctionForDateTupleOfIntervalsArithmetic(const DataTypePtr & type0, const DataTypePtr & type1, ContextPtr context)
{
bool first_is_date_or_datetime = isDate(type0) || isDateTime(type0) || isDateTime64(type0);
bool second_is_date_or_datetime = isDate(type1) || isDateTime(type1) || isDateTime64(type1);
bool first_is_date_or_datetime = isDateOrDate32(type0) || isDateTime(type0) || isDateTime64(type0);
bool second_is_date_or_datetime = isDateOrDate32(type1) || isDateTime(type1) || isDateTime64(type1);
/// Exactly one argument must be Date or DateTime
if (first_is_date_or_datetime == second_is_date_or_datetime)
@ -735,7 +737,7 @@ class FunctionBinaryArithmetic : public IFunction
{
function_name = "addTupleOfIntervals";
}
else if (is_minus)
else
{
function_name = "subtractTupleOfIntervals";
}
@ -743,6 +745,47 @@ class FunctionBinaryArithmetic : public IFunction
return FunctionFactory::instance().get(function_name, context);
}
static FunctionOverloadResolverPtr
getFunctionForMergeIntervalsArithmetic(const DataTypePtr & type0, const DataTypePtr & type1, ContextPtr context)
{
/// Special case when the function is plus or minus, first argument is Interval or Tuple of Intervals
/// and the second argument is the Inteval of a different kind.
/// We construct another function (example: addIntervals) and call it
if constexpr (!is_plus && !is_minus)
return {};
const auto * tuple_data_type_0 = checkAndGetDataType<DataTypeTuple>(type0.get());
const auto * interval_data_type_0 = checkAndGetDataType<DataTypeInterval>(type0.get());
const auto * interval_data_type_1 = checkAndGetDataType<DataTypeInterval>(type1.get());
if ((!tuple_data_type_0 && !interval_data_type_0) || !interval_data_type_1)
return {};
if (interval_data_type_0 && interval_data_type_0->equals(*interval_data_type_1))
return {};
if (tuple_data_type_0)
{
auto & tuple_types = tuple_data_type_0->getElements();
for (auto & type : tuple_types)
if (!isInterval(type))
return {};
}
std::string function_name;
if (is_plus)
{
function_name = "addInterval";
}
else
{
function_name = "subtractInterval";
}
return FunctionFactory::instance().get(function_name, context);
}
static FunctionOverloadResolverPtr
getFunctionForTupleArithmetic(const DataTypePtr & type0, const DataTypePtr & type1, ContextPtr context)
{
@ -955,6 +998,16 @@ class FunctionBinaryArithmetic : public IFunction
return function->execute(new_arguments, result_type, input_rows_count);
}
ColumnPtr executeIntervalTupleOfIntervalsPlusMinus(const ColumnsWithTypeAndName & arguments, const DataTypePtr & result_type,
size_t input_rows_count, const FunctionOverloadResolverPtr & function_builder) const
{
ColumnsWithTypeAndName new_arguments = arguments;
auto function = function_builder->build(new_arguments);
return function->execute(new_arguments, result_type, input_rows_count);
}
ColumnPtr executeTupleNumberOperator(const ColumnsWithTypeAndName & arguments, const DataTypePtr & result_type,
size_t input_rows_count, const FunctionOverloadResolverPtr & function_builder) const
{
@ -1171,7 +1224,7 @@ public:
}
/// Special case when the function is plus or minus, one of arguments is Date/DateTime and another is Tuple.
if (auto function_builder = getFunctionForTupleOfIntervalsArithmetic(arguments[0], arguments[1], context))
if (auto function_builder = getFunctionForDateTupleOfIntervalsArithmetic(arguments[0], arguments[1], context))
{
ColumnsWithTypeAndName new_arguments(2);
@ -1186,6 +1239,18 @@ public:
return function->getResultType();
}
/// Special case when the function is plus or minus, one of arguments is Interval/Tuple of Intervals and another is Interval.
if (auto function_builder = getFunctionForMergeIntervalsArithmetic(arguments[0], arguments[1], context))
{
ColumnsWithTypeAndName new_arguments(2);
for (size_t i = 0; i < 2; ++i)
new_arguments[i].type = arguments[i];
auto function = function_builder->build(new_arguments);
return function->getResultType();
}
/// Special case when the function is multiply or divide, one of arguments is Tuple and another is Number.
if (auto function_builder = getFunctionForTupleAndNumberArithmetic(arguments[0], arguments[1], context))
{
@ -1237,6 +1302,21 @@ public:
type_res = std::make_shared<DataTypeString>();
return true;
}
else if constexpr (std::is_same_v<LeftDataType, DataTypeInterval> || std::is_same_v<RightDataType, DataTypeInterval>)
{
if constexpr (std::is_same_v<LeftDataType, DataTypeInterval> &&
std::is_same_v<RightDataType, DataTypeInterval>)
{
if constexpr (is_plus || is_minus)
{
if (left.getKind() == right.getKind())
{
type_res = std::make_shared<LeftDataType>(left.getKind());
return true;
}
}
}
}
else
{
using ResultDataType = typename BinaryOperationTraits<Op, LeftDataType, RightDataType>::ResultDataType;
@ -1619,11 +1699,17 @@ public:
}
/// Special case when the function is plus or minus, one of arguments is Date/DateTime and another is Tuple.
if (auto function_builder = getFunctionForTupleOfIntervalsArithmetic(arguments[0].type, arguments[1].type, context))
if (auto function_builder = getFunctionForDateTupleOfIntervalsArithmetic(arguments[0].type, arguments[1].type, context))
{
return executeDateTimeTupleOfIntervalsPlusMinus(arguments, result_type, input_rows_count, function_builder);
}
/// Special case when the function is plus or minus, one of arguments is Interval/Tuple of Intervals and another is Interval.
if (auto function_builder = getFunctionForMergeIntervalsArithmetic(arguments[0].type, arguments[1].type, context))
{
return executeIntervalTupleOfIntervalsPlusMinus(arguments, result_type, input_rows_count, function_builder);
}
/// Special case when the function is plus, minus or multiply, both arguments are tuples.
if (auto function_builder = getFunctionForTupleArithmetic(arguments[0].type, arguments[1].type, context))
{

View File

@ -3,6 +3,7 @@
#include <DataTypes/DataTypesNumber.h>
#include <DataTypes/DataTypesDecimal.h>
#include <DataTypes/DataTypeFixedString.h>
#include <DataTypes/DataTypeInterval.h>
#include <DataTypes/Native.h>
#include <Columns/ColumnVector.h>
#include <Columns/ColumnDecimal.h>
@ -145,7 +146,8 @@ class FunctionUnaryArithmetic : public IFunction
DataTypeDecimal<Decimal64>,
DataTypeDecimal<Decimal128>,
DataTypeDecimal<Decimal256>,
DataTypeFixedString
DataTypeFixedString,
DataTypeInterval
>(type, std::forward<F>(f));
}
@ -211,6 +213,12 @@ public:
return false;
result = std::make_shared<DataType>(type.getN());
}
else if constexpr (std::is_same_v<DataTypeInterval, DataType>)
{
if constexpr (!IsUnaryOperation<Op>::negate)
return false;
result = std::make_shared<DataTypeInterval>(type.getKind());
}
else
{
using T0 = typename DataType::FieldType;

View File

@ -1,5 +1,6 @@
#include <Columns/ColumnTuple.h>
#include <DataTypes/DataTypeArray.h>
#include <DataTypes/DataTypeInterval.h>
#include <DataTypes/DataTypeTuple.h>
#include <DataTypes/DataTypesNumber.h>
#include <DataTypes/DataTypeNothing.h>
@ -517,6 +518,172 @@ using FunctionAddTupleOfIntervals = FunctionDateOrDateTimeOperationTupleOfInterv
using FunctionSubtractTupleOfIntervals = FunctionDateOrDateTimeOperationTupleOfIntervals<SubtractTupleOfIntervalsImpl>;
template <bool is_minus>
struct FunctionTupleOperationInterval : public ITupleFunction
{
public:
static constexpr auto name = is_minus ? "subtractInterval" : "addInterval";
explicit FunctionTupleOperationInterval(ContextPtr context_) : ITupleFunction(context_) {}
static FunctionPtr create(ContextPtr context_)
{
return std::make_shared<FunctionTupleOperationInterval>(context_);
}
String getName() const override { return name; }
size_t getNumberOfArguments() const override { return 2; }
DataTypePtr getReturnTypeImpl(const DataTypes & arguments) const override
{
if (!isTuple(arguments[0]) && !isInterval(arguments[0]))
throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT,
"Illegal type {} of first argument of function {}, must be Tuple or Interval",
arguments[0]->getName(), getName());
if (!isInterval(arguments[1]))
throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT,
"Illegal type {} of second argument of function {}, must be Interval",
arguments[0]->getName(), getName());
DataTypes types;
const auto * tuple = checkAndGetDataType<DataTypeTuple>(arguments[0].get());
if (tuple)
{
const auto & cur_types = tuple->getElements();
for (auto & type : cur_types)
if (!isInterval(type))
throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT,
"Illegal type {} of Tuple element of first argument of function {}, must be Interval",
types.back()->getName(), getName());
types = cur_types;
}
else
{
types = {arguments[0]};
}
const auto * interval_last = checkAndGetDataType<DataTypeInterval>(types.back().get());
const auto * interval_new = checkAndGetDataType<DataTypeInterval>(arguments[1].get());
if (!interval_last->equals(*interval_new))
types.push_back(arguments[1]);
return std::make_shared<DataTypeTuple>(types);
}
ColumnPtr executeImpl(const ColumnsWithTypeAndName & arguments, const DataTypePtr &, size_t input_rows_count) const override
{
if (!isInterval(arguments[1].type))
throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT,
"Illegal type {} of second argument of function {}, must be Interval",
arguments[0].type->getName(), getName());
Columns tuple_columns;
const auto * first_tuple = checkAndGetDataType<DataTypeTuple>(arguments[0].type.get());
const auto * first_interval = checkAndGetDataType<DataTypeInterval>(arguments[0].type.get());
const auto * second_interval = checkAndGetDataType<DataTypeInterval>(arguments[1].type.get());
bool can_be_merged;
if (first_interval)
{
can_be_merged = first_interval->equals(*second_interval);
if (can_be_merged)
tuple_columns.resize(1);
else
tuple_columns.resize(2);
tuple_columns[0] = arguments[0].column->convertToFullColumnIfConst();
}
else if (first_tuple)
{
const auto & cur_types = first_tuple->getElements();
for (auto & type : cur_types)
if (!isInterval(type))
throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT,
"Illegal type {} of Tuple element of first argument of function {}, must be Interval",
type->getName(), getName());
auto cur_elements = getTupleElements(*arguments[0].column);
size_t tuple_size = cur_elements.size();
if (tuple_size == 0)
{
can_be_merged = false;
}
else
{
const auto * tuple_last_interval = checkAndGetDataType<DataTypeInterval>(cur_types.back().get());
can_be_merged = tuple_last_interval->equals(*second_interval);
}
if (can_be_merged)
tuple_columns.resize(tuple_size);
else
tuple_columns.resize(tuple_size + 1);
for (size_t i = 0; i < tuple_size; ++i)
tuple_columns[i] = cur_elements[i];
}
else
throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT,
"Illegal type {} of first argument of function {}, must be Tuple or Interval",
arguments[0].type->getName(), getName());
ColumnPtr & last_column = tuple_columns.back();
if (can_be_merged)
{
ColumnWithTypeAndName left{last_column, arguments[1].type, {}};
if constexpr (is_minus)
{
auto minus = FunctionFactory::instance().get("minus", context);
auto elem_minus = minus->build({left, arguments[1]});
last_column = elem_minus->execute({left, arguments[1]}, arguments[1].type, input_rows_count)
->convertToFullColumnIfConst();
}
else
{
auto plus = FunctionFactory::instance().get("plus", context);
auto elem_plus = plus->build({left, arguments[1]});
last_column = elem_plus->execute({left, arguments[1]}, arguments[1].type, input_rows_count)
->convertToFullColumnIfConst();
}
}
else
{
if constexpr (is_minus)
{
auto negate = FunctionFactory::instance().get("negate", context);
auto elem_negate = negate->build({arguments[1]});
last_column = elem_negate->execute({arguments[1]}, arguments[1].type, input_rows_count);
}
else
{
last_column = arguments[1].column;
}
}
return ColumnTuple::create(tuple_columns);
}
};
using FunctionTupleAddInterval = FunctionTupleOperationInterval<false>;
using FunctionTupleSubtractInterval = FunctionTupleOperationInterval<true>;
/// this is for convenient usage in LNormalize
template <class FuncLabel>
class FunctionLNorm : public ITupleFunction {};
@ -1384,8 +1551,64 @@ REGISTER_FUNCTION(VectorFunctions)
factory.registerFunction<FunctionTupleDivide>();
factory.registerFunction<FunctionTupleNegate>();
factory.registerFunction<FunctionAddTupleOfIntervals>();
factory.registerFunction<FunctionSubtractTupleOfIntervals>();
factory.registerFunction<FunctionAddTupleOfIntervals>(
{
R"(
Consecutively adds a tuple of intervals to a Date or a DateTime.
[example:tuple]
)",
Documentation::Examples{
{"tuple", "WITH toDate('2018-01-01') AS date SELECT addTupleOfIntervals(date, (INTERVAL 1 DAY, INTERVAL 1 YEAR))"},
},
Documentation::Categories{"Tuple", "Interval", "Date", "DateTime"}
});
factory.registerFunction<FunctionSubtractTupleOfIntervals>(
{
R"(
Consecutively subtracts a tuple of intervals from a Date or a DateTime.
[example:tuple]
)",
Documentation::Examples{
{"tuple", "WITH toDate('2018-01-01') AS date SELECT subtractTupleOfIntervals(date, (INTERVAL 1 DAY, INTERVAL 1 YEAR))"},
},
Documentation::Categories{"Tuple", "Interval", "Date", "DateTime"}
});
factory.registerFunction<FunctionTupleAddInterval>(
{
R"(
Adds an interval to another interval or tuple of intervals. The returned value is tuple of intervals.
[example:tuple]
[example:interval1]
If the types of the first interval (or the interval in the tuple) and the second interval are the same they will be merged into one interval.
[example:interval2]
)",
Documentation::Examples{
{"tuple", "SELECT addInterval((INTERVAL 1 DAY, INTERVAL 1 YEAR), INTERVAL 1 MONTH)"},
{"interval1", "SELECT addInterval(INTERVAL 1 DAY, INTERVAL 1 MONTH)"},
{"interval2", "SELECT addInterval(INTERVAL 1 DAY, INTERVAL 1 DAY)"},
},
Documentation::Categories{"Tuple", "Interval"}
});
factory.registerFunction<FunctionTupleSubtractInterval>(
{
R"(
Adds an negated interval to another interval or tuple of intervals. The returned value is tuple of intervals.
[example:tuple]
[example:interval1]
If the types of the first interval (or the interval in the tuple) and the second interval are the same they will be merged into one interval.
[example:interval2]
)",
Documentation::Examples{
{"tuple", "SELECT subtractInterval((INTERVAL 1 DAY, INTERVAL 1 YEAR), INTERVAL 1 MONTH)"},
{"interval1", "SELECT subtractInterval(INTERVAL 1 DAY, INTERVAL 1 MONTH)"},
{"interval2", "SELECT subtractInterval(INTERVAL 2 DAY, INTERVAL 1 DAY)"},
},
Documentation::Categories{"Tuple", "Interval"}
});
factory.registerFunction<FunctionTupleMultiplyByNumber>();
factory.registerFunction<FunctionTupleDivideByNumber>();

View File

@ -1,17 +1,16 @@
SELECT (toIntervalSecond(-1), toIntervalMinute(2), toIntervalMonth(-3), toIntervalYear(1))
-
2022-11-12
2022-11-12
2022-11-12
-
2023-07-11 00:01:59
2023-07-11 00:01:59
2023-07-11 00:01:59
-
2021-07-31 23:00:00
2021-07-31 23:00:00
2021-07-31 23:00:00
-
2021-06-10 23:59:59.000
2021-06-10 23:59:59.000
2021-06-10 23:59:59.000
---
3 IntervalSecond
(1,2) Tuple(IntervalHour, IntervalSecond)
(1,1,1) Tuple(IntervalSecond, IntervalHour, IntervalSecond)
(2,1) Tuple(IntervalSecond, IntervalHour)
---
-3 IntervalSecond
(-1,-2) Tuple(IntervalHour, IntervalSecond)
(-1,-1,-1) Tuple(IntervalSecond, IntervalHour, IntervalSecond)
(-2,-1) Tuple(IntervalSecond, IntervalHour)
---
1
1
1
1

View File

@ -1,21 +1,42 @@
EXPLAIN SYNTAX SELECT INTERVAL '-1 SECOND 2 MINUTE -3 MONTH 1 YEAR';
SELECT '-';
SELECT '2022-10-11'::Date + INTERVAL 1 DAY + INTERVAL 1 MONTH;
SELECT '2022-10-11'::Date + (INTERVAL 1 DAY, INTERVAL 1 MONTH);
SELECT '2022-10-11'::Date + INTERVAL '1 DAY 1 MONTH';
SELECT '---';
SELECT '-';
SELECT '2022-10-11'::Date + INTERVAL -1 SECOND + INTERVAL 2 MINUTE + INTERVAL -3 MONTH + INTERVAL 1 YEAR;
SELECT '2022-10-11'::Date + (INTERVAL -1 SECOND, INTERVAL 2 MINUTE, INTERVAL -3 MONTH, INTERVAL 1 YEAR);
SELECT '2022-10-11'::Date + INTERVAL '-1 SECOND 2 MINUTE -3 MONTH 1 YEAR';
WITH INTERVAL 1 SECOND + INTERVAL 1 SECOND + INTERVAL 1 SECOND as expr SELECT expr, toTypeName(expr);
WITH INTERVAL 1 HOUR + INTERVAL 1 SECOND + INTERVAL 1 SECOND as expr SELECT expr, toTypeName(expr);
WITH INTERVAL 1 SECOND + INTERVAL 1 HOUR + INTERVAL 1 SECOND as expr SELECT expr, toTypeName(expr);
WITH INTERVAL 1 SECOND + INTERVAL 1 SECOND + INTERVAL 1 HOUR as expr SELECT expr, toTypeName(expr);
SELECT '-';
SELECT '2022-10-11'::DateTime - INTERVAL 1 QUARTER - INTERVAL -3 WEEK - INTERVAL 1 YEAR - INTERVAL 1 HOUR;
SELECT '2022-10-11'::DateTime - (INTERVAL 1 QUARTER, INTERVAL -3 WEEK, INTERVAL 1 YEAR, INTERVAL 1 HOUR);
SELECT '2022-10-11'::DateTime - INTERVAL '1 QUARTER -3 WEEK 1 YEAR 1 HOUR';
SELECT '---';
SELECT '-';
SELECT '2022-10-11'::DateTime64 - INTERVAL 1 YEAR - INTERVAL 4 MONTH - INTERVAL 1 SECOND;
SELECT '2022-10-11'::DateTime64 - (INTERVAL 1 YEAR, INTERVAL 4 MONTH, INTERVAL 1 SECOND);
SELECT '2022-10-11'::DateTime64 - INTERVAL '1 YEAR 4 MONTH 1 SECOND';
WITH - INTERVAL 1 SECOND - INTERVAL 1 SECOND - INTERVAL 1 SECOND as expr SELECT expr, toTypeName(expr);
WITH - INTERVAL 1 HOUR - INTERVAL 1 SECOND - INTERVAL 1 SECOND as expr SELECT expr, toTypeName(expr);
WITH - INTERVAL 1 SECOND - INTERVAL 1 HOUR - INTERVAL 1 SECOND as expr SELECT expr, toTypeName(expr);
WITH - INTERVAL 1 SECOND - INTERVAL 1 SECOND - INTERVAL 1 HOUR as expr SELECT expr, toTypeName(expr);
SELECT '---';
WITH '2022-10-11'::Date + INTERVAL 1 DAY + INTERVAL 1 MONTH AS e1,
'2022-10-11'::Date + (INTERVAL 1 DAY + INTERVAL 1 MONTH) AS e2,
'2022-10-11'::Date + (INTERVAL 1 DAY, INTERVAL 1 MONTH) AS e3,
'2022-10-11'::Date + INTERVAL '1 DAY 1 MONTH' AS e4
SELECT e1 == e2 AND e2 == e3 AND e3 == e4;
WITH '2022-10-11'::Date + INTERVAL -1 SECOND + INTERVAL 2 MINUTE + INTERVAL -3 MONTH + INTERVAL 1 YEAR AS e1,
'2022-10-11'::Date + (INTERVAL -1 SECOND + INTERVAL 2 MINUTE + INTERVAL -3 MONTH + INTERVAL 1 YEAR) AS e2,
'2022-10-11'::Date + (INTERVAL -1 SECOND, INTERVAL 2 MINUTE, INTERVAL -3 MONTH, INTERVAL 1 YEAR) AS e3,
'2022-10-11'::Date + INTERVAL '-1 SECOND 2 MINUTE -3 MONTH 1 YEAR' AS e4
SELECT e1 == e2 AND e2 == e3 AND e3 == e4;
WITH '2022-10-11'::DateTime - INTERVAL 1 QUARTER - INTERVAL -3 WEEK - INTERVAL 1 YEAR - INTERVAL 1 HOUR AS e1,
'2022-10-11'::DateTime + (- INTERVAL 1 QUARTER - INTERVAL -3 WEEK - INTERVAL 1 YEAR - INTERVAL 1 HOUR) AS e2,
'2022-10-11'::DateTime - (INTERVAL 1 QUARTER, INTERVAL -3 WEEK, INTERVAL 1 YEAR, INTERVAL 1 HOUR) AS e3,
'2022-10-11'::DateTime - INTERVAL '1 QUARTER -3 WEEK 1 YEAR 1 HOUR' AS e4
SELECT e1 == e2 AND e2 == e3 AND e3 == e4;
WITH '2022-10-11'::DateTime64 - INTERVAL 1 YEAR - INTERVAL 4 MONTH - INTERVAL 1 SECOND AS e1,
'2022-10-11'::DateTime64 + (- INTERVAL 1 YEAR - INTERVAL 4 MONTH - INTERVAL 1 SECOND) AS e2,
'2022-10-11'::DateTime64 - (INTERVAL 1 YEAR, INTERVAL 4 MONTH, INTERVAL 1 SECOND) AS e3,
'2022-10-11'::DateTime64 - INTERVAL '1 YEAR 4 MONTH 1 SECOND' AS e4
SELECT e1 == e2 AND e2 == e3 AND e3 == e4;