Select query with aggregation and join on external dictionary works slow when no cast in join expression provided. Table and the dictionary both have UInt64 id's. When using explicit cast like `table.dict_id = toUInt64(dict.id)` the query works fast.

**Does it reproduce on recent release?**
Yes

**How to reproduce**
* ClickHouse server version to use: 21.4.6.55

```
create table test (key UInt64, value String) engine=MergeTree order by key;
insert into test select number, '' from numbers(1000000);

create dictionary test_dict (key UInt64, value String)
primary key key source(clickhouse(table test db 'default' user 'default'))
lifetime(min 0 max 0) layout(hashed());

-- Select without GROUP BY
-- 1.1
select test.key, test_dict.value from test join test_dict on test.key = test_dict.key limit 1;
-- Elapsed: 0.038 sec.

-- 1.2
select test.key, test_dict.value from test join test_dict on test.key = toUInt64(test_dict.key) limit 1;
-- Elapsed: 0.316 sec. Processed 1.00 million rows, 17.00 MB (3.17 million rows/s., 53.86 MB/s.)

-- 1.3
select test.key, dictGetString('test_dict', 'value', test.key) from test limit 1;
-- Elapsed: 0.007 sec.

-- Select with GROUP BY

-- 2.1
select test.key from test join test_dict on test.key = test_dict.key group by test.key limit 1;
-- Elapsed: 1286.466 sec. Processed 1.00 million rows, 8.00 MB (777.32 rows/s., 6.22 KB/s.)

-- 2.2
select test.key from test join test_dict on test.key = toUInt64(test_dict.key) group by test.key limit 1;
-- Elapsed: 0.169 sec. Processed 2.00 million rows, 16.00 MB (11.81 million rows/s., 94.49 MB/s.)

-- 2.3
select test.key, dictGetString('test_dict', 'value', test.key) from test group by test.key limit 1;
--  Elapsed: 0.056 sec. Processed 1.00 million rows, 8.00 MB (17.91 million rows/s., 143.32 MB/s.)

```

**Expected behavior**
Both queries with explicit cast in join and without it expected to work fast.

ConstNullMapPtr null_map{};
    ColumnPtr null_map_holder = extractNestedColumnsAndNullMap(left_key_columns, null_map);
    size_t existing_columns = block.columns();
    /** If you use FULL or RIGHT JOIN, then the columns from the "left" table must be materialized.
      * Because if they are constants, then in the "not joined" rows, they may have different values
      *  - default values, which can differ from the values of these constants.
      */
    if constexpr (right || full)
    {
        materializeBlockInplace(block);
        if (nullable_left_side)
            JoinCommon::convertColumnsToNullable(block);
    }
    /** For LEFT/INNER JOIN, the saved blocks do not contain keys.
      * For FULL/RIGHT JOIN, the saved blocks contain keys;
      *  but they will not be used at this stage of joining (and will be in `AdderNonJoined`), and they need to be skipped.
      * For ASOF, the last column is used as the ASOF column
      */
    AddedColumns added_columns(block_with_columns_to_add, block, savedBlockSample(), *this, left_key_columns, key_sizes, is_asof_join, is_join_get);
    bool has_required_right_keys = (required_right_keys.columns() != 0);
    added_columns.need_filter = need_filter || has_required_right_keys;
    IColumn::Filter row_filter = overDictionary() ?
        dictionaryJoinRightColumns<KIND, STRICTNESS>(*table_join, added_columns, null_map) :
        switchJoinRightColumns<KIND, STRICTNESS>(maps_, added_columns, data->type, null_map, used_flags);
    for (size_t i = 0; i < added_columns.size(); ++i)
        block.insert(added_columns.moveColumn(i));
    std::vector<size_t> right_keys_to_replicate [[maybe_unused]];
    if constexpr (need_filter)
    {