Перейти к основному содержимому
Версия: 2.0.x

Ускорение соединений, сохраняющих кардинальность

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

Обзор

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

  • Inner Join:

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

    В этом случае A.fk (Foreign Key) является NOT NULL и ссылается на B.pk (Primary Key). Каждая строка в 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 может быть отсечена из соединения. Начиная с версии v1.5.2, Selena поддерживает отсечение таблиц в соединениях, сохраняющих кардинальность, которые могут встречаться в общих табличных выражениях (CTE), логических представлениях и подзапросах.

Вариант использования: Выбор признаков в реальном времени в контроле рисков

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

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

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

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

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

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

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

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

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

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

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

Включение отсечения таблиц

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

-- Включить отсечение таблиц на этапе RBO.
SET enable_rbo_table_prune=true;
-- Включить отсечение таблиц на этапе CBO.
SET enable_cbo_table_prune=true;
-- Включить отсечение таблиц на этапе RBO для оператора UPDATE на таблицах Primary Key.
SET enable_table_prune_on_update = true;

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

Таблицы, которые должны быть отсечены, должны иметь ограничения Unique Key или Primary Key, по крайней мере, в LEFT или RIGHT Joins. Для отсечения таблиц в 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` является Primary Key таблицы `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 JOIN на основе Unique или Primary Keys

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

Отсечение в LEFT/RIGHT JOIN на основе Unique или Primary keys имеет менее строгие требования по сравнению с отсечением INNER JOIN на основе Foreign Keys.

Условия для отсечения:

  • Отсекаемая сторона

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

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

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

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

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

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

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

Пример:

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

    -- Таблица `depts` имеет ограничение Primary Key на столбце `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 JOIN на основе Foreign Keys

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

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

Условия для отсечения следующие:

  • Отсекаемая сторона

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

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

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

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

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

Пример:

  1. Создайте таблицы, определите Foreign Key и вставьте данные.

    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`.
    -- Однако, как показано в условии Join, `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, чей предикат влияет на кардинальность конечных результатов, не выполняет отсечение таблиц.