2014-07-24 20:20:36 +00:00
|
|
|
|
#pragma once
|
|
|
|
|
|
2014-07-25 01:32:59 +00:00
|
|
|
|
#include <DB/AggregateFunctions/IUnaryAggregateFunction.h>
|
|
|
|
|
#include <DB/DataTypes/DataTypesNumberFixed.h>
|
2014-07-24 20:20:36 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
namespace DB
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** Считает количество уникальных значений до не более чем указанного в параметре.
|
|
|
|
|
*
|
|
|
|
|
* Пример: uniqUpTo(3)(UserID)
|
|
|
|
|
* - посчитает количество уникальных посетителей, вернёт 1, 2, 3 или 4, если их >= 4.
|
|
|
|
|
*
|
|
|
|
|
* Для строк используется некриптографическая хэш-функция, за счёт чего рассчёт может быть немного неточным.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
template <typename T>
|
2014-08-01 05:19:17 +00:00
|
|
|
|
struct __attribute__((__packed__)) AggregateFunctionUniqUpToData
|
2014-07-24 20:20:36 +00:00
|
|
|
|
{
|
2014-08-01 19:14:21 +00:00
|
|
|
|
/** Если count == threshold + 1 - это значит, что "переполнилось" (значений больше threshold).
|
|
|
|
|
* В этом случае (например, после вызова функции merge), массив data не обязательно содержит инициализированные значения
|
|
|
|
|
* - пример: объединяем состояние, в котором мало значений, с другим состоянием, которое переполнилось;
|
|
|
|
|
* тогда выставляем count в threshold + 1, а значения из другого состояния не копируем.
|
|
|
|
|
*/
|
2014-08-01 05:19:17 +00:00
|
|
|
|
UInt8 count = 0;
|
2014-08-01 19:14:21 +00:00
|
|
|
|
|
|
|
|
|
/// Данные идут после конца структуры. При вставке, делается линейный поиск.
|
|
|
|
|
T data[0];
|
2014-07-24 20:20:36 +00:00
|
|
|
|
|
2014-07-25 01:32:59 +00:00
|
|
|
|
|
2014-07-24 20:20:36 +00:00
|
|
|
|
size_t size() const
|
|
|
|
|
{
|
|
|
|
|
return count;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// threshold - для скольки элементов есть место в data.
|
2014-08-01 05:19:17 +00:00
|
|
|
|
void insert(T x, UInt8 threshold)
|
2014-07-24 20:20:36 +00:00
|
|
|
|
{
|
2014-08-01 19:14:21 +00:00
|
|
|
|
/// Состояние уже переполнено - ничего делать не нужно.
|
2014-07-24 20:20:36 +00:00
|
|
|
|
if (count > threshold)
|
|
|
|
|
return;
|
|
|
|
|
|
2014-08-01 19:14:21 +00:00
|
|
|
|
/// Линейный поиск совпадающего элемента.
|
|
|
|
|
for (size_t i = 0; i < count; ++i)
|
2014-07-24 20:20:36 +00:00
|
|
|
|
if (data[i] == x)
|
|
|
|
|
return;
|
|
|
|
|
|
2014-08-01 19:14:21 +00:00
|
|
|
|
/// Не нашли совпадающий элемент. Если есть место ещё для одного элемента - вставляем его.
|
2014-07-24 20:20:36 +00:00
|
|
|
|
if (count < threshold)
|
|
|
|
|
data[count] = x;
|
|
|
|
|
|
2014-08-01 19:14:21 +00:00
|
|
|
|
/// После увеличения count, состояние может оказаться переполненным.
|
2014-07-24 20:20:36 +00:00
|
|
|
|
++count;
|
|
|
|
|
}
|
|
|
|
|
|
2014-08-01 05:19:17 +00:00
|
|
|
|
void merge(const AggregateFunctionUniqUpToData<T> & rhs, UInt8 threshold)
|
2014-07-24 20:20:36 +00:00
|
|
|
|
{
|
|
|
|
|
if (count > threshold)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (rhs.count > threshold)
|
|
|
|
|
{
|
2014-08-01 19:14:21 +00:00
|
|
|
|
/// Если rhs переполнено, то выставляем у текущего состояния count тоже переполненным.
|
2014-07-24 20:20:36 +00:00
|
|
|
|
count = rhs.count;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2014-08-01 19:14:21 +00:00
|
|
|
|
for (size_t i = 0; i < rhs.count; ++i)
|
2014-07-24 20:20:36 +00:00
|
|
|
|
insert(rhs.data[i], threshold);
|
|
|
|
|
}
|
|
|
|
|
|
2014-08-01 05:19:17 +00:00
|
|
|
|
void write(WriteBuffer & wb, UInt8 threshold) const
|
2014-07-24 20:20:36 +00:00
|
|
|
|
{
|
2014-08-01 19:14:21 +00:00
|
|
|
|
writeBinary(count, wb);
|
|
|
|
|
|
|
|
|
|
/// Пишем значения, только если состояние не переполнено. Иначе они не нужны, а важен только факт того, что состояние переполнено.
|
|
|
|
|
if (count <= threshold)
|
2014-10-02 19:20:09 +00:00
|
|
|
|
wb.write(reinterpret_cast<const char *>(&data[0]), count * sizeof(data[0]));
|
2014-07-24 20:20:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
2014-08-01 05:19:17 +00:00
|
|
|
|
void readAndMerge(ReadBuffer & rb, UInt8 threshold)
|
2014-07-24 20:20:36 +00:00
|
|
|
|
{
|
2014-08-01 05:19:17 +00:00
|
|
|
|
UInt8 rhs_count;
|
|
|
|
|
readBinary(rhs_count, rb);
|
2014-07-24 20:20:36 +00:00
|
|
|
|
|
2014-08-01 19:14:21 +00:00
|
|
|
|
if (rhs_count > threshold)
|
|
|
|
|
{
|
|
|
|
|
/// Если rhs переполнено, то выставляем у текущего состояния count тоже переполненным.
|
|
|
|
|
count = rhs_count;
|
|
|
|
|
return;
|
|
|
|
|
}
|
2014-07-24 20:20:36 +00:00
|
|
|
|
|
2014-08-01 19:14:21 +00:00
|
|
|
|
for (size_t i = 0; i < rhs_count; ++i)
|
2014-07-24 20:20:36 +00:00
|
|
|
|
{
|
|
|
|
|
T x;
|
|
|
|
|
readBinary(x, rb);
|
|
|
|
|
insert(x, threshold);
|
|
|
|
|
}
|
|
|
|
|
}
|
2014-07-25 01:32:59 +00:00
|
|
|
|
|
|
|
|
|
|
2014-08-01 05:19:17 +00:00
|
|
|
|
void addOne(const IColumn & column, size_t row_num, UInt8 threshold)
|
2014-07-25 01:32:59 +00:00
|
|
|
|
{
|
|
|
|
|
insert(static_cast<const ColumnVector<T> &>(column).getData()[row_num], threshold);
|
|
|
|
|
}
|
2014-07-24 20:20:36 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Для строк, запоминаются их хэши.
|
|
|
|
|
template <>
|
2014-07-25 01:32:59 +00:00
|
|
|
|
struct AggregateFunctionUniqUpToData<String> : AggregateFunctionUniqUpToData<UInt64>
|
2014-07-24 20:20:36 +00:00
|
|
|
|
{
|
2014-08-01 05:19:17 +00:00
|
|
|
|
void addOne(const IColumn & column, size_t row_num, UInt8 threshold)
|
2014-07-24 20:20:36 +00:00
|
|
|
|
{
|
2014-07-25 01:32:59 +00:00
|
|
|
|
/// Имейте ввиду, что вычисление приближённое.
|
|
|
|
|
StringRef value = column.getDataAt(row_num);
|
|
|
|
|
insert(CityHash64(value.data, value.size), threshold);
|
|
|
|
|
}
|
|
|
|
|
};
|
2014-07-24 20:20:36 +00:00
|
|
|
|
|
|
|
|
|
|
2014-08-01 05:19:17 +00:00
|
|
|
|
constexpr UInt8 uniq_upto_max_threshold = 100;
|
2014-07-24 20:20:36 +00:00
|
|
|
|
|
|
|
|
|
template <typename T>
|
|
|
|
|
class AggregateFunctionUniqUpTo final : public IUnaryAggregateFunction<AggregateFunctionUniqUpToData<T>, AggregateFunctionUniqUpTo<T> >
|
|
|
|
|
{
|
|
|
|
|
private:
|
2014-08-01 05:19:17 +00:00
|
|
|
|
UInt8 threshold = 5; /// Значение по-умолчанию, если параметр не указан.
|
2014-07-24 20:20:36 +00:00
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
size_t sizeOfData() const
|
|
|
|
|
{
|
|
|
|
|
return sizeof(AggregateFunctionUniqUpToData<T>) + sizeof(T) * threshold;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String getName() const { return "uniqUpTo"; }
|
|
|
|
|
|
|
|
|
|
DataTypePtr getReturnType() const
|
|
|
|
|
{
|
|
|
|
|
return new DataTypeUInt64;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void setArgument(const DataTypePtr & argument)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void setParameters(const Array & params)
|
|
|
|
|
{
|
|
|
|
|
if (params.size() != 1)
|
|
|
|
|
throw Exception("Aggregate function " + getName() + " requires exactly one parameter.", ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH);
|
|
|
|
|
|
2014-08-01 19:31:38 +00:00
|
|
|
|
UInt64 threshold_param = apply_visitor(FieldVisitorConvertToNumber<UInt64>(), params[0]);
|
2014-07-24 20:20:36 +00:00
|
|
|
|
|
2014-08-01 19:31:38 +00:00
|
|
|
|
if (threshold_param > uniq_upto_max_threshold)
|
2014-07-24 20:20:36 +00:00
|
|
|
|
throw Exception("Too large parameter for aggregate function " + getName() + ". Maximum: " + toString(uniq_upto_max_threshold),
|
|
|
|
|
ErrorCodes::ARGUMENT_OUT_OF_BOUND);
|
2014-08-01 19:31:38 +00:00
|
|
|
|
|
|
|
|
|
threshold = threshold_param;
|
2014-07-24 20:20:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void addOne(AggregateDataPtr place, const IColumn & column, size_t row_num) const
|
|
|
|
|
{
|
2014-07-25 01:32:59 +00:00
|
|
|
|
this->data(place).addOne(column, row_num, threshold);
|
2014-07-24 20:20:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void merge(AggregateDataPtr place, ConstAggregateDataPtr rhs) const
|
|
|
|
|
{
|
|
|
|
|
this->data(place).merge(this->data(rhs), threshold);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void serialize(ConstAggregateDataPtr place, WriteBuffer & buf) const
|
|
|
|
|
{
|
2014-07-31 20:26:22 +00:00
|
|
|
|
this->data(place).write(buf, threshold);
|
2014-07-24 20:20:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void deserializeMerge(AggregateDataPtr place, ReadBuffer & buf) const
|
|
|
|
|
{
|
|
|
|
|
this->data(place).readAndMerge(buf, threshold);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void insertResultInto(ConstAggregateDataPtr place, IColumn & to) const
|
|
|
|
|
{
|
|
|
|
|
static_cast<ColumnUInt64 &>(to).getData().push_back(this->data(place).size());
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|