Перейти к основному содержимому

Ускорение соединений с сохранением кардинальности

Эта тема описывает, как ускорить соединения с сохранением кардинальности с помощью обрезки таблиц. Эта функция поддерживается начиная с версии 1.5.0.

Обзор

Соединение с сохранением кардинальности гарантирует, что кардинальность и коэффициент дублирования выходных строк остаются такими же, как у одной из входных таблиц в соединении. Рассмотрим следующие примеры:

  • Inner Join:

    SELECT A.* FROM A INNER JOIN B ON A.fk = B.pk;

    В этом случае A.fk (внешний ключ) НЕ NULL и ссылается на B.pk (первичный ключ). Каждая строка в A соответствует точно одной строке в B, поэтому кардинальность и коэффициент дублирования выходных данных соответствуют таковым у A.

  • Left Join:

    SELECT A.* FROM A LEFT JOIN B ON A.fk = B.pk;

    Здесь A.fk ссылается на B.pk, но A.fk может содержать значения NULL. Каждая строка в A соответствует максимум одной строке в B. В результате кардинальность и коэффициент дублирования выходных данных остаются согласованными с A.


В таких типах соединений, если финальные выходные столбцы зависят только от столбцов таблицы A, а столбцы из таблицы B не используются, таблица B может быть обрезана из соединения. Начиная с версии 1.5.0, Selena поддерживает обрезку таблиц в соединениях с сохранением кардинальности, которая может происходить в общих табличных выражениях (CTE), логических представлениях и подзапросах.

Случай использования: выбор признаков в реальном времени для контроля рисков

Функция обрезки таблиц для соединений с сохранением кардинальности особенно полезна в сценариях, таких как выбор признаков в реальном времени для контроля рисков. В этом контексте пользователям необходимо выбирать данные из большого количества таблиц, часто имея дело с комбинаторным взрывом столбцов и таблиц. Следующие характеристики распространены в области контроля рисков:

  • Многочисленные признаки распределены по множеству независимо обновляемых таблиц.
  • Свежие данные должны быть видимыми и запрашиваемыми в реальном времени.
  • Плоское логическое представление используется для упрощения модели данных, делая SQL для извлечения столбцов более лаконичным и продуктивным.

Использование плоского логического представления, а не других ускоренных слоев данных, помогает пользователям эффективно получать доступ к данным в реальном времени. В каждом запросе извлечения столбцов требуется соединить только несколько таблиц (не все таблицы в логическом представлении). Обрезая неиспользуемые таблицы из этих запросов, вы можете уменьшить количество соединений и улучшить производительность.

Поддержка функции

Функция обрезки таблиц поддерживает многотабличные соединения как в схеме звезды, так и в схеме снежинки. Многотабличные соединения могут появляться в CTE, логических представлениях и подзапросах, обеспечивая более эффективное выполнение запросов.

В настоящее время функция обрезки таблиц поддерживается только для OLAP таблиц и облачных таблиц. Внешние таблицы в многотабличных соединениях не могут быть обрезаны.

Использование

Следующие примеры используют набор данных TPC-H.

Предварительные условия

Для использования функции обрезки таблиц должны быть выполнены следующие условия:

  1. Включить обрезку таблиц
  2. Установить ключевые ограничения

Включение обрезки таблиц

По умолчанию обрезка таблиц отключена. Вам необходимо включить эту функцию, настроив следующие переменные сессии:

-- Включить обрезку таблиц на фазе RBO.
SET enable_rbo_table_prune=true;
-- Включить обрезку таблиц на фазе CBO.
SET enable_cbo_table_prune=true;
-- Включить обрезку таблиц на фазе RBO для оператора UPDATE на таблицах с первичным ключом.
SET enable_table_prune_on_update = true;

Установка ключевых ограничений

Таблицы, подлежащие обрезке, должны иметь ограничения Unique Key или Primary Key, по крайней мере, в LEFT или RIGHT соединениях. Для обрезки таблиц в INNER JOIN вы должны определить ограничения Foreign Key в дополнение к ограничениям Unique Key или Primary Key.

Таблицы Primary Key и Unique Key имеют свои неявные ограничения Primary Key или Unique Key, естественно встроенные в них. Однако для таблиц Duplicate Key вы должны вручную определить ограничения Unique Key и убедиться, что дублирующиеся строки не существуют. Обратите внимание, что Selena не принудительно применяет ограничения Unique Key к таблицам Duplicate Key. Вместо этого она рассматривает их как подсказки для оптимизации для более агрессивного планирования запросов.

Пример:

-- Определить ограничение Unique Key при создании таблицы.
CREATE TABLE `lineitem` (
`l_orderkey` int(11) NOT NULL COMMENT "",
`l_partkey` int(11) NOT NULL COMMENT "",
`l_suppkey` int(11) NOT NULL COMMENT "",
`l_linenumber` int(11) NOT NULL COMMENT "",
`l_quantity` decimal64(15, 2) NOT NULL COMMENT "",
`l_extendedprice` decimal64(15, 2) NOT NULL COMMENT "",
`l_discount` decimal64(15, 2) NOT NULL COMMENT "",
`l_tax` decimal64(15, 2) NOT NULL COMMENT "",
`l_returnflag` varchar(1) NOT NULL COMMENT "",
`l_linestatus` varchar(1) NOT NULL COMMENT "",
`l_shipdate` date NOT NULL COMMENT "",
`l_commitdate` date NOT NULL COMMENT "",
`l_receiptdate` date NOT NULL COMMENT "",
`l_shipinstruct` varchar(25) NOT NULL COMMENT "",
`l_shipmode` varchar(10) NOT NULL COMMENT "",
`l_comment` varchar(44) NOT NULL COMMENT ""
) ENGINE=OLAP
DUPLICATE KEY(`l_orderkey`,`l_partkey`, `l_suppkey`)
DISTRIBUTED BY HASH(`l_orderkey`) BUCKETS 96
PROPERTIES (
"unique_constraints" = "l_orderkey,l_linenumber"
);

-- Или вы можете определить ограничение Unique Key после создания таблицы.
ALTER TABLE lineitem SET ("unique_constraints" = "l_orderkey,l_linenumber");

Ограничения Foreign Key, с другой стороны, должны быть определены явно. Подобно ограничениям Unique Key для таблиц Duplicate Key, ограничения Foreign Key действуют как подсказки для оптимизатора. Selena не принудительно применяет согласованность ограничений Foreign Key. Вы должны обеспечить целостность данных при производстве и загрузке данных в Selena.

Пример:

-- Создать таблицу, на которую будет ссылаться ограничение Foreign Key.
-- Обратите внимание, что столбец, на который ссылаются, должен иметь ограничения Unique Key или Primary Key.
-- В этом примере `p_partkey` является первичным ключом таблицы `part`.
CREATE TABLE part (
p_partkey int(11) NOT NULL,
p_name VARCHAR(55) NOT NULL,
p_mfgr CHAR(25) NOT NULL,
p_brand CHAR(10) NOT NULL,
p_type VARCHAR(25) NOT NULL,
p_size INT NOT NULL,
p_container CHAR(10) NOT NULL,
p_retailprice DOUBLE NOT NULL,
p_comment VARCHAR(23) NOT NULL
) ENGINE=OLAP
PRIMARY KEY(`p_partkey`)
DISTRIBUTED BY HASH(`p_partkey`) BUCKETS 12;

-- Определить ограничение Foreign Key при создании таблицы.
CREATE TABLE `lineitem` (
`l_orderkey` int(11) NOT NULL COMMENT "",
`l_partkey` int(11) NOT NULL COMMENT "",
`l_suppkey` int(11) NOT NULL COMMENT "",
`l_linenumber` int(11) NOT NULL COMMENT "",
`l_quantity` decimal64(15, 2) NOT NULL COMMENT "",
`l_extendedprice` decimal64(15, 2) NOT NULL COMMENT "",
`l_discount` decimal64(15, 2) NOT NULL COMMENT "",
`l_tax` decimal64(15, 2) NOT NULL COMMENT "",
`l_returnflag` varchar(1) NOT NULL COMMENT "",
`l_linestatus` varchar(1) NOT NULL COMMENT "",
`l_shipdate` date NOT NULL COMMENT "",
`l_commitdate` date NOT NULL COMMENT "",
`l_receiptdate` date NOT NULL COMMENT "",
`l_shipinstruct` varchar(25) NOT NULL COMMENT "",
`l_shipmode` varchar(10) NOT NULL COMMENT "",
`l_comment` varchar(44) NOT NULL COMMENT ""
) ENGINE=OLAP
DUPLICATEK KEY(`l_orderkey`,`l_partkey`, `l_suppkey`)
DISTRIBUTED BY HASH(`l_orderkey`) BUCKETS 96
PROPERTIES (
"foreign_key_constraints" = "(l_partkey) REFERENCES part(p_partkey)"
);

-- Или вы можете определить ограничение Foreign Key после создания таблицы.
ALTER TABLE lineitem SET ("foreign_key_constraints" = "(l_partkey) REFERENCES part(p_partkey)");

Обрезка таблиц в LEFT/RIGHT соединениях на основе уникальных или первичных ключей

Обрезка таблиц в LEFT или RIGHT соединениях не требует, чтобы сохраняемая сторона соединения имела внешний ключ, ссылающийся на обрезаемую сторону. Это делает обрезку более гибкой и надежной, даже если целостность ссылок не может быть гарантирована.

Обрезка в LEFT/RIGHT соединениях на основе уникальных или первичных ключей имеет менее строгие требования по сравнению с обрезкой INNER JOIN на основе внешних ключей.

Условия для обрезки:

  • Обрезаемая сторона

    Обрезаемая таблица должна быть правой стороной в LEFT JOIN или левой стороной в RIGHT JOIN.

  • Условия соединения

    Соединение должно использовать только условия равенства (=), а соединяемые столбцы обрезаемой стороны должны быть надмножеством уникальных или первичных ключей.

  • Выходные столбцы

    Должны выводиться только столбцы сохраняемой стороны, и результат должен поддерживать ту же кардинальность и коэффициент дублирования, что и сохраняемая сторона.

  • NULL/значения по умолчанию

    Соединяемые столбцы в сохраняемой стороне могут содержать NULL или другие значения по умолчанию, которые не соответствуют обрезаемой стороне.

Пример:

  1. Создать таблицы и вставить данные.

    -- Таблица `depts` имеет ограничение первичного ключа на столбце `deptno`.
    CREATE TABLE `depts` (
    `deptno` int(11) NOT NULL COMMENT "",
    `name` varchar(25) NOT NULL COMMENT ""
    ) ENGINE=OLAP
    PRIMARY KEY(`deptno`)
    DISTRIBUTED BY HASH(`deptno`) BUCKETS 10;

    CREATE TABLE `emps` (
    `empid` int(11) NOT NULL COMMENT "",
    `deptno` int(11) NOT NULL COMMENT "",
    `name` varchar(25) NOT NULL COMMENT "",
    `salary` double NULL COMMENT ""
    ) ENGINE=OLAP
    DUPLICATE KEY(`empid`)
    DISTRIBUTED BY HASH(`empid`) BUCKETS 10;

    INSERT INTO depts VALUES
    (1, "R&D"),
    (2, "Marketing"),
    (3, "Community"),
    (4, "DBA"),(5, "POC");

    INSERT INTO emps VALUES
    (1, 1, "Alice", "6000"),
    (2, 1, "Bob", "6100"),
    (3, 2, "Candy", "10000"),
    (4, 2, "Dave", "20000"),
    (5, 3, "Evan","18000"),
    (6, 3, "Freman","1000"),
    (7, 4, "George","1800"),
    (8, 4, "Harry","2000"),
    (9, 5, "Ivan", "15000"),
    (10, 5, "Jim","20000"),
    (11, -1, "Kevin","1500"),
    (12, -1, "Lily","2500");
  2. Просмотреть логические планы выполнения запросов.

    -- Q1: Запросить все столбцы `emps`. Не все столбцы `depts` запрашиваются.
    EXPLAIN LOGICAL SELECT emps.* FROM emps LEFT JOIN depts ON emps.deptno = depts.deptno;
    +-----------------------------------------------------------------------------+
    | Explain String |
    +-----------------------------------------------------------------------------+
    | - Output => [1:empid, 2:deptno, 3:name, 4:salary] |
    | - SCAN [emps] => [1:empid, 2:deptno, 3:name, 4:salary] |
    | Estimates: {row: 10, cpu: ?, memory: ?, network: ?, cost: 20.0} |
    | partitionRatio: 1/1, tabletRatio: 10/10 |
    +-----------------------------------------------------------------------------+

    -- Q2: Запросить только `deptno` и `salary` в `emps`. Ни один столбец в `depts` не запрашивается.
    EXPLAIN LOGICAL SELECT emps.deptno, avg(salary) AS mean_salary
    FROM emps LEFT JOIN depts ON emps.deptno = depts.deptno
    GROUP BY emps.deptno
    ORDER BY mean_salary DESC
    LIMIT 5;
    +-------------------------------------------------------------------------------------------------+
    | Explain String |
    +-------------------------------------------------------------------------------------------------+
    | - Output => [2:deptno, 7:avg] |
    | - TOP-5(FINAL)[7: avg DESC NULLS LAST] |
    | Estimates: {row: 3, cpu: ?, memory: ?, network: ?, cost: 138.0} |
    | - TOP-5(PARTIAL)[7: avg DESC NULLS LAST] |
    | Estimates: {row: 3, cpu: ?, memory: ?, network: ?, cost: 114.0} |
    | - AGGREGATE(GLOBAL) [2:deptno] |
    | Estimates: {row: 3, cpu: ?, memory: ?, network: ?, cost: 90.0} |
    | 7:avg := avg(7:avg) |
    | - EXCHANGE(SHUFFLE) [2] |
    | Estimates: {row: 6, cpu: ?, memory: ?, network: ?, cost: 72.0} |
    | - AGGREGATE(LOCAL) [2:deptno] |
    | Estimates: {row: 6, cpu: ?, memory: ?, network: ?, cost: 48.0} |
    | 7:avg := avg(4:salary) |
    | - SCAN [emps] => [2:deptno, 4:salary] |
    | Estimates: {row: 12, cpu: ?, memory: ?, network: ?, cost: 12.0} |
    | partitionRatio: 1/1, tabletRatio: 10/10 |
    +-------------------------------------------------------------------------------------------------+

    -- Q3: Запрашиваются только столбцы в `emps`. Хотя предикат `name = "R&D"` только
    -- выбирает определенные строки `depts`, финальные результаты зависят только от `emps`.
    EXPLAIN LOGICAL SELECT emps.deptno, avg(salary) AS mean_salary
    FROM emps LEFT JOIN
    (SELECT deptno FROM depts WHERE name="R&D") t ON emps.deptno = t.deptno
    GROUP BY emps.deptno
    ORDER BY mean_salary DESC
    LIMIT 5;
    +-------------------------------------------------------------------------------------------------+
    | Explain String |
    +-------------------------------------------------------------------------------------------------+
    | - Output => [2:deptno, 7:avg] |
    | - TOP-5(FINAL)[7: avg DESC NULLS LAST] |
    | Estimates: {row: 3, cpu: ?, memory: ?, network: ?, cost: 138.0} |
    | - TOP-5(PARTIAL)[7: avg DESC NULLS LAST] |
    | Estimates: {row: 3, cpu: ?, memory: ?, network: ?, cost: 114.0} |
    | - AGGREGATE(GLOBAL) [2:deptno] |
    | Estimates: {row: 3, cpu: ?, memory: ?, network: ?, cost: 90.0} |
    | 7:avg := avg(7:avg) |
    | - EXCHANGE(SHUFFLE) [2] |
    | Estimates: {row: 6, cpu: ?, memory: ?, network: ?, cost: 72.0} |
    | - AGGREGATE(LOCAL) [2:deptno] |
    | Estimates: {row: 6, cpu: ?, memory: ?, network: ?, cost: 48.0} |
    | 7:avg := avg(4:salary) |
    | - SCAN [emps] => [2:deptno, 4:salary] |
    | Estimates: {row: 12, cpu: ?, memory: ?, network: ?, cost: 12.0} |
    | partitionRatio: 1/1, tabletRatio: 10/10 |
    +-------------------------------------------------------------------------------------------------+

    -- Q4: Предикат `depts.name="R&D"` в предложении WHERE нарушает
    -- условия сохранения кардинальности, поэтому `depts` не может быть обрезана.
    EXPLAIN LOGICAL SELECT emps.deptno, avg(salary) AS mean_salary
    FROM emps LEFT JOIN depts ON emps.deptno = depts.deptno
    WHERE depts.name="R&D";

    +-----------------------------------------------------------------------------------------------+
    | Explain String |
    +-----------------------------------------------------------------------------------------------+
    | - Output => [8:any_value, 7:avg] |
    | - AGGREGATE(GLOBAL) [] |
    | Estimates: {row: 1, cpu: ?, memory: ?, network: ?, cost: 110.5} |
    | 7:avg := avg(7:avg) |
    | 8:any_value := any_value(8:any_value) |
    | - EXCHANGE(GATHER) |
    | Estimates: {row: 1, cpu: ?, memory: ?, network: ?, cost: 105.5} |
    | - AGGREGATE(LOCAL) [] |
    | Estimates: {row: 1, cpu: ?, memory: ?, network: ?, cost: 101.5} |
    | 7:avg := avg(4:salary) |
    | 8:any_value := any_value(2:deptno) |
    | - HASH/INNER JOIN [2:deptno = 5:deptno] => [2:deptno, 4:salary] |
    | Estimates: {row: 12, cpu: ?, memory: ?, network: ?, cost: 79.5} |
    | - SCAN [emps] => [2:deptno, 4:salary] |
    | Estimates: {row: 12, cpu: ?, memory: ?, network: ?, cost: 12.0} |
    | partitionRatio: 1/1, tabletRatio: 10/10 |
    | - EXCHANGE(BROADCAST) |
    | Estimates: {row: 5, cpu: ?, memory: ?, network: ?, cost: 25.0} |
    | - SCAN [depts] => [5:deptno] |
    | Estimates: {row: 5, cpu: ?, memory: ?, network: ?, cost: 5.0} |
    | partitionRatio: 1/1, tabletRatio: 10/10 |
    | predicate: 6:name = 'R&D' |
    +-----------------------------------------------------------------------------------------------+

В приведенном выше примере обрезка таблиц разрешена в Q1, Q2 и Q3, как показано в плане выполнения, в то время как Q4, который нарушает условия сохранения кардинальности, не может выполнить обрезку таблиц.

Обрезка таблиц в INNER/LEFT/RIGHT соединениях на основе внешних ключей

Обрезка таблиц в INNER соединениях более ограничительна, поскольку сохраняемая сторона должна иметь внешний ключ, который ссылается на обрезаемую сторону, и должна быть обеспечена целостность ссылок. В настоящее время Selena не выполняет проверки согласованности ограничений внешних ключей.

Хотя обрезка таблиц на основе внешних ключей более строгая, она также более мощная. Она позволяет оптимизатору использовать вывод эквивалентности столбцов в INNER соединениях, делая обрезку возможной в более сложных сценариях.

Условия для обрезки следующие:

  • Обрезаемая сторона

    • В LEFT JOIN обрезаемая таблица должна быть на правой стороне. В RIGHT JOIN она должна быть на левой стороне.
    • В INNER JOIN обрезаемая таблица должна иметь ограничение Unique Key на соединяемых столбцах, и каждая строка в сохраняемой стороне должна соответствовать точно одной строке в обрезаемой стороне. Если в сохраняемой стороне существуют несоответствующие строки, обрезка не может произойти.
  • Условия соединения

    Соединение должно использовать только условия равенства (=). Соединяемые столбцы в сохраняемой стороне должны быть внешними ключами, в то время как соединяемые столбцы в обрезаемой стороне должны быть первичными ключами или уникальными ключами. Эти столбцы должны быть выровнены в соответствии с ограничениями внешних ключей.

  • Выходные столбцы

    • Должны выводиться только столбцы сохраняемой стороны, и результат должен поддерживать ту же кардинальность и коэффициент дублирования, что и сохраняемая сторона.
    • Для INNER соединений соединяемые столбцы из обрезаемой стороны могут быть заменены их эквивалентными столбцами в сохраняемой стороне. Если все выходные столбцы из обрезаемой стороны являются соединяемыми столбцами, обрезка также может произойти.
  • NULL/значения по умолчанию:

    Соединяемые столбцы в сохраняемой стороне не могут содержать NULL или другие значения по умолчанию, которые не соответствуют обрезаемой стороне.

Пример:

  1. Создать таблицы, определить внешний ключ и вставить данные.

    CREATE TABLE `depts` (
    `deptno` int(11) NOT NULL COMMENT "",
    `name` varchar(25) NOT NULL COMMENT ""
    ) ENGINE=OLAP
    PRIMARY KEY(`deptno`)
    DISTRIBUTED BY HASH(`deptno`) BUCKETS 10;

    CREATE TABLE `emps` (
    `empid` int(11) NOT NULL COMMENT "",
    `deptno` int(11) NOT NULL COMMENT "",
    `name` varchar(25) NOT NULL COMMENT "",
    `salary` double NULL COMMENT ""
    ) ENGINE=OLAP
    DUPLICATE KEY(`empid`)
    DISTRIBUTED BY HASH(`empid`) BUCKETS 10;

    ALTER TABLE emps SET ("foreign_key_constraints" = "(deptno) REFERENCES depts(deptno)");

    INSERT INTO depts VALUES
    (1, "R&D"),
    (2, "Marketing"),
    (3, "Community"),
    (4, "DBA"),(5, "POC");

    INSERT INTO emps VALUES
    (1, 1, "Alice", "6000"),
    (2, 1, "Bob", "6100"),
    (3, 2, "Candy", "10000"),
    (4, 2, "Dave", "20000"),
    (5, 3, "Evan","18000"),
    (6, 3, "Freman","1000"),
    (7, 4, "George","1800"),
    (8, 4, "Harry","2000"),
    (9, 5, "Ivan", "15000"),
    (10, 5, "Jim","20000");
  2. Просмотреть логические планы выполнения запросов.

    -- Q1: Запросить `empid` и `name` в `emps`, и `deptno` в `depts`.
    -- Однако, как показано в условии соединения, `emps.deptno` эквивалентен `depts.deptno`,
    -- поэтому `emps.deptno` может заменить `depts.deptno`.
    EXPLAIN LOGICAL WITH t0 AS (
    SELECT empid, depts.deptno, emps.name, emps.salary, depts.name AS dept_name
    FROM emps INNER JOIN depts ON emps.deptno = depts.deptno
    )
    SELECT empid, deptno, name FROM t0;
    +-----------------------------------------------------------------------------+
    | Explain String |
    +-----------------------------------------------------------------------------+
    | - Output => [7:empid, 8:deptno, 9:name] |
    | - SCAN [emps] => [7:empid, 8:deptno, 9:name] |
    | Estimates: {row: 12, cpu: ?, memory: ?, network: ?, cost: 18.0} |
    | partitionRatio: 1/1, tabletRatio: 10/10 |
    +-----------------------------------------------------------------------------+

    -- Q2: Запросить только `salary` в `emps`.
    EXPLAIN LOGICAL SELECT avg(salary)
    FROM emps INNER JOIN depts ON emps.deptno = depts.deptno;
    +----------------------------------------------------------------------------------------+
    | Explain String |
    +----------------------------------------------------------------------------------------+
    | - Output => [7:avg] |
    | - AGGREGATE(GLOBAL) [] |
    | Estimates: {row: 1, cpu: ?, memory: ?, network: ?, cost: 16.5} |
    | 7:avg := avg(7:avg) |
    | - EXCHANGE(GATHER) |
    | Estimates: {row: 1, cpu: ?, memory: ?, network: ?, cost: 14.0} |
    | - AGGREGATE(LOCAL) [] |
    | Estimates: {row: 1, cpu: ?, memory: ?, network: ?, cost: 12.0} |
    | 7:avg := avg(4:salary) |
    | - SCAN [emps] => [4:salary] |
    | Estimates: {row: 10, cpu: ?, memory: ?, network: ?, cost: 5.0} |
    | partitionRatio: 1/1, tabletRatio: 10/10 |
    +----------------------------------------------------------------------------------------+

    -- Q3: Предикат `name="R&D"` влияет на кардинальность финальных результатов.
    EXPLAIN LOGICAL SELECT emps.deptno, avg(salary) AS mean_salary
    FROM emps INNER JOIN
    (SELECT deptno FROM depts WHERE name="R&D") t ON emps.deptno = t.deptno
    GROUP BY emps.deptno
    ORDER BY mean_salary DESC
    LIMIT 5;
    +-------------------------------------------------------------------------------------------------------+
    | Explain String |
    +-------------------------------------------------------------------------------------------------------+
    | - Output => [2:deptno, 7:avg] |
    | - TOP-5(FINAL)[7: avg DESC NULLS LAST] |
    | Estimates: {row: 2, cpu: ?, memory: ?, network: ?, cost: 165.0480769230769} |
    | - TOP-5(PARTIAL)[7: avg DESC NULLS LAST] |
    | Estimates: {row: 2, cpu: ?, memory: ?, network: ?, cost: 145.0480769230769} |
    | - AGGREGATE(GLOBAL) [2:deptno] |
    | Estimates: {row: 2, cpu: ?, memory: ?, network: ?, cost: 125.04807692307692} |
    | 7:avg := avg(4:salary) |
    | - HASH/INNER JOIN [2:deptno = 5:deptno] => [2:deptno, 4:salary] |
    | Estimates: {row: 10, cpu: ?, memory: ?, network: ?, cost: 100.04807692307692} |
    | - EXCHANGE(SHUFFLE) [2] |
    | Estimates: {row: 10, cpu: ?, memory: ?, network: ?, cost: 50.0} |
    | - SCAN [emps] => [2:deptno, 4:salary] |
    | Estimates: {row: 10, cpu: ?, memory: ?, network: ?, cost: 10.0} |
    | partitionRatio: 1/1, tabletRatio: 10/10 |
    | - EXCHANGE(SHUFFLE) [5] |
    | Estimates: {row: 5, cpu: ?, memory: ?, network: ?, cost: 15.0} |
    | - SCAN [depts] => [5:deptno] |
    | Estimates: {row: 5, cpu: ?, memory: ?, network: ?, cost: 5.0} |
    | partitionRatio: 1/1, tabletRatio: 10/10 |
    | predicate: 6:name = 'R&D' |
    +-------------------------------------------------------------------------------------------------------+

В приведенном выше примере обрезка таблиц разрешена в Q1 и Q2, как показано в плане выполнения, в то время как Q3, чей предикат влияет на кардинальность финальных результатов, не может выполнить обрезку таблиц.