CREATE DICTIONARY ... LAYOUT RangeHashed

CREATE DICTIONARY ... LAYOUT RangeHashed

range_hashed 레이아웃은 딕셔너리를 정렬된 범위 배열과 그에 대응하는 값의 해시 테이블 형태로 메모리에 저장해요.

출처: 문서

본문

range_hashed

딕셔너리는 정렬된 범위 배열과 그에 대응하는 값의 해시 테이블 형태로 메모리에 저장돼요.

이 저장 방법은 hashed와 같은 방식으로 동작하며, 키 외에 날짜/시간(임의의 숫자 타입) 범위를 사용할 수 있게 해줘요.

예시: 테이블이 각 광고주에 대한 할인을 다음 형식으로 포함한다고 해요.

┌─advertiser_id─┬─discount_start_date─┬─discount_end_date─┬─amount─┐
│           123 │          2015-01-16 │        2015-01-31 │   0.25 │
│           123 │          2015-01-01 │        2015-01-15 │   0.15 │
│           456 │          2015-01-01 │        2015-01-15 │   0.05 │
└───────────────┴─────────────────────┴───────────────────┴────────┘

날짜 범위에 샘플을 사용하려면 structurerange_minrange_max 요소를 정의하세요. 이 요소들은 nametype 요소를 포함해야 해요(type을 지정하지 않으면 기본 타입이 사용됩니다 - Date). type은 임의의 숫자 타입이 될 수 있어요(Date / DateTime / UInt64 / Int32 / 기타).

range_minrange_max의 값은 Int64 타입에 맞아야 해요.

예시:

  • DDL
  • Configuration file
CREATE DICTIONARY discounts_dict (
    advertiser_id UInt64,
    discount_start_date Date,
    discount_end_date Date,
    amount Float64
)
PRIMARY KEY id
SOURCE(CLICKHOUSE(TABLE 'discounts'))
LIFETIME(MIN 1 MAX 1000)
LAYOUT(RANGE_HASHED(range_lookup_strategy 'max'))
RANGE(MIN discount_start_date MAX discount_end_date)
<layout>
    <range_hashed>
        <!-- Strategy for overlapping ranges (min/max). Default: min (return a matching range with the min(range_min -> range_max) value) -->
        <range_lookup_strategy>min</range_lookup_strategy>
    </range_hashed>
</layout>
<structure>
    <id>
        <name>advertiser_id</name>
    </id>
    <range_min>
        <name>discount_start_date</name>
        <type>Date</type>
    </range_min>
    <range_max>
        <name>discount_end_date</name>
        <type>Date</type>
    </range_max>
    ...
</structure>

이러한 딕셔너리로 작업하려면 dictGet 함수에 추가 인자를 전달해야 하는데, 그 인자에 대해 범위가 선택됩니다:

dictGet('dict_name', 'attr_name', id, date)

쿼리 예시:

SELECT dictGet('discounts_dict', 'amount', 1, '2022-10-20'::Date);

이 함수는 지정된 id와 전달된 날짜를 포함하는 날짜 범위에 대한 값을 반환해요.

알고리즘의 세부 사항:

  • id를 찾지 못하거나 id에 대한 범위를 찾지 못하면 속성 타입의 기본값을 반환해요.
  • 겹치는 범위가 있고 range_lookup_strategy=min이면 최소 range_min을 가진 일치하는 범위를 반환하고, 여러 범위가 발견되면 최소 range_max를 가진 범위를 반환하며, 다시 여러 범위가 발견되면(여러 범위가 같은 range_minrange_max를 가진 경우) 그중 임의의 범위를 반환해요.
  • 겹치는 범위가 있고 range_lookup_strategy=max이면 최대 range_min을 가진 일치하는 범위를 반환하고, 여러 범위가 발견되면 최대 range_max를 가진 범위를 반환하며, 다시 여러 범위가 발견되면(여러 범위가 같은 range_minrange_max를 가진 경우) 그중 임의의 범위를 반환해요.
  • range_maxNULL이면 범위는 열려 있어요. NULL은 가능한 최대 값으로 처리됩니다. range_min의 경우 1970-01-01 또는 0(-MAX_INT)이 열린 값으로 사용될 수 있어요.

구성 예시:

  • DDL
  • Configuration file
CREATE DICTIONARY somedict(
    Abcdef UInt64,
    StartTimeStamp UInt64,
    EndTimeStamp UInt64,
    XXXType String DEFAULT ''
)
PRIMARY KEY Abcdef
RANGE(MIN StartTimeStamp MAX EndTimeStamp)
<clickhouse>
    <dictionary>
        ...

        <layout>
            <range_hashed />
        </layout>

        <structure>
            <id>
                <name>Abcdef</name>
            </id>
            <range_min>
                <name>StartTimeStamp</name>
                <type>UInt64</type>
            </range_min>
            <range_max>
                <name>EndTimeStamp</name>
                <type>UInt64</type>
            </range_max>
            <attribute>
                <name>XXXType</name>
                <type>String</type>
                <null_value />
            </attribute>
        </structure>

    </dictionary>
</clickhouse>

겹치는 범위와 열린 범위가 있는 구성 예시:

CREATE TABLE discounts
(
    advertiser_id UInt64,
    discount_start_date Date,
    discount_end_date Nullable(Date),
    amount Float64
)
ENGINE = Memory;

INSERT INTO discounts VALUES (1, '2015-01-01', Null, 0.1);
INSERT INTO discounts VALUES (1, '2015-01-15', Null, 0.2);
INSERT INTO discounts VALUES (2, '2015-01-01', '2015-01-15', 0.3);
INSERT INTO discounts VALUES (2, '2015-01-04', '2015-01-10', 0.4);
INSERT INTO discounts VALUES (3, '1970-01-01', '2015-01-15', 0.5);
INSERT INTO discounts VALUES (3, '1970-01-01', '2015-01-10', 0.6);

SELECT * FROM discounts ORDER BY advertiser_id, discount_start_date;
┌─advertiser_id─┬─discount_start_date─┬─discount_end_date─┬─amount─┐
│             1 │          2015-01-01 │              ᴺᵁᴸᴸ │    0.1 │
│             1 │          2015-01-15 │              ᴺᵁᴸᴸ │    0.2 │
│             2 │          2015-01-01 │        2015-01-15 │    0.3 │
│             2 │          2015-01-04 │        2015-01-10 │    0.4 │
│             3 │          1970-01-01 │        2015-01-15 │    0.5 │
│             3 │          1970-01-01 │        2015-01-10 │    0.6 │
└───────────────┴─────────────────────┴───────────────────┴────────┘

-- RANGE_LOOKUP_STRATEGY 'max'

CREATE DICTIONARY discounts_dict
(
    advertiser_id UInt64,
    discount_start_date Date,
    discount_end_date Nullable(Date),
    amount Float64
)
PRIMARY KEY advertiser_id
SOURCE(CLICKHOUSE(TABLE discounts))
LIFETIME(MIN 600 MAX 900)
LAYOUT(RANGE_HASHED(RANGE_LOOKUP_STRATEGY 'max'))
RANGE(MIN discount_start_date MAX discount_end_date);

select dictGet('discounts_dict', 'amount', 1, toDate('2015-01-14')) res;
┌─res─┐
│ 0.1 │ -- the only one range is matching: 2015-01-01 - Null
└─────┘

select dictGet('discounts_dict', 'amount', 1, toDate('2015-01-16')) res;
┌─res─┐
│ 0.2 │ -- two ranges are matching, range_min 2015-01-15 (0.2) is bigger than 2015-01-01 (0.1)
└─────┘

select dictGet('discounts_dict', 'amount', 2, toDate('2015-01-06')) res;
┌─res─┐
│ 0.4 │ -- two ranges are matching, range_min 2015-01-04 (0.4) is bigger than 2015-01-01 (0.3)
└─────┘

select dictGet('discounts_dict', 'amount', 3, toDate('2015-01-01')) res;
┌─res─┐
│ 0.5 │ -- two ranges are matching, range_min are equal, 2015-01-15 (0.5) is bigger than 2015-01-10 (0.6)
└─────┘

DROP DICTIONARY discounts_dict;

-- RANGE_LOOKUP_STRATEGY 'min'

CREATE DICTIONARY discounts_dict
(
    advertiser_id UInt64,
    discount_start_date Date,
    discount_end_date Nullable(Date),
    amount Float64
)
PRIMARY KEY advertiser_id
SOURCE(CLICKHOUSE(TABLE discounts))
LIFETIME(MIN 600 MAX 900)
LAYOUT(RANGE_HASHED(RANGE_LOOKUP_STRATEGY 'min'))
RANGE(MIN discount_start_date MAX discount_end_date);

select dictGet('discounts_dict', 'amount', 1, toDate('2015-01-14')) res;
┌─res─┐
│ 0.1 │ -- the only one range is matching: 2015-01-01 - Null
└─────┘

select dictGet('discounts_dict', 'amount', 1, toDate('2015-01-16')) res;
┌─res─┐
│ 0.1 │ -- two ranges are matching, range_min 2015-01-01 (0.1) is less than 2015-01-15 (0.2)
└─────┘

select dictGet('discounts_dict', 'amount', 2, toDate('2015-01-06')) res;
┌─res─┐
│ 0.3 │ -- two ranges are matching, range_min 2015-01-01 (0.3) is less than 2015-01-04 (0.4)
└─────┘

select dictGet('discounts_dict', 'amount', 3, toDate('2015-01-01')) res;
┌─res─┐
│ 0.6 │ -- two ranges are matching, range_min are equal, 2015-01-10 (0.6) is less than 2015-01-15 (0.5)
└─────┘

complex_key_range_hashed

딕셔너리는 정렬된 범위 배열과 그에 대응하는 값의 해시 테이블 형태로 메모리에 저장돼요(range_hashed 참조). 이 저장 타입은 복합 keys와 함께 사용하기 위한 것이에요.

구성 예시:

CREATE DICTIONARY range_dictionary
(
  CountryID UInt64,
  CountryKey String,
  StartDate Date,
  EndDate Date,
  Tax Float64 DEFAULT 0.2
)
PRIMARY KEY CountryID, CountryKey
SOURCE(CLICKHOUSE(TABLE 'date_table'))
LIFETIME(MIN 1 MAX 1000)
LAYOUT(COMPLEX_KEY_RANGE_HASHED())
RANGE(MIN StartDate MAX EndDate);

더 알아보기 (Learn more)