> ## Documentation Index
> Fetch the complete documentation index at: https://private-7c7dfe99-fix-nav-issues.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Партиции таблицы

> Что такое партиции таблицы в ClickHouse

export const RunnableCode = ({children, run = false, showStats = true}) => {
  const [results, setResults] = useState(null);
  const [error, setError] = useState(null);
  const [loading, setLoading] = useState(false);
  const [showResults, setShowResults] = useState(false);
  const [stats, setStats] = useState(null);
  const [isDark, setIsDark] = useState(false);
  const [hoveredRow, setHoveredRow] = useState(-1);
  const codeRef = useRef(null);
  useEffect(() => {
    if (typeof window !== 'undefined') {
      const check = () => setIsDark(document.documentElement.classList.contains('dark'));
      check();
      const observer = new MutationObserver(check);
      observer.observe(document.documentElement, {
        attributes: true,
        attributeFilter: ['class']
      });
      return () => observer.disconnect();
    }
  }, []);
  useEffect(() => {
    if (codeRef.current) {
      const block = codeRef.current.querySelector('.code-block');
      if (block) {
        block.style.marginBottom = '0';
        block.style.marginTop = '0';
        block.style.borderBottomLeftRadius = '0';
        block.style.borderBottomRightRadius = '0';
      }
    }
  });
  const getSqlText = () => {
    if (!codeRef.current) return '';
    const code = codeRef.current.querySelector('code');
    return (code || codeRef.current).textContent.trim();
  };
  const executeQuery = async () => {
    const sql = getSqlText();
    if (!sql) return;
    setLoading(true);
    setError(null);
    setResults(null);
    setShowResults(true);
    try {
      const cleanQuery = sql.replace(/;$/, '').trim();
      const params = new URLSearchParams({
        query: cleanQuery,
        default_format: 'JSONCompact',
        result_overflow_mode: 'break',
        read_overflow_mode: 'break',
        allow_experimental_analyzer: '1'
      });
      const res = await fetch(`https://sql-clickhouse.clickhouse.com/?${params.toString()}`, {
        method: 'POST',
        headers: {
          'Authorization': `Basic ${btoa(`demo:`)}`
        }
      });
      const text = await res.text();
      if (!res.ok) {
        setError(text || `HTTP ${res.status}`);
        setLoading(false);
        return;
      }
      const json = JSON.parse(text);
      setResults(json);
      setStats(json.statistics || null);
    } catch (err) {
      setError(err.message || 'Query execution failed');
    }
    setLoading(false);
  };
  useEffect(() => {
    if (run) executeQuery();
  }, []);
  const formatRows = n => {
    if (n >= 1e9) return `${(n / 1e9).toFixed(1)}B`;
    if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
    if (n >= 1e3) return `${(n / 1e3).toFixed(1)}K`;
    return String(n);
  };
  const formatBytes = b => {
    if (b >= 1e9) return `${(b / 1e9).toFixed(2)} GB`;
    if (b >= 1e6) return `${(b / 1e6).toFixed(2)} MB`;
    if (b >= 1e3) return `${(b / 1e3).toFixed(2)} KB`;
    return `${b} B`;
  };
  const isNumericType = type => {
    return (/^(UInt|Int|Float|Decimal)/).test(type);
  };
  const isHyperlink = value => {
    return typeof value === 'string' && (/^https?:\/\//).test(value);
  };
  const computeColumnExtremes = (meta, data) => {
    const extremes = {};
    for (let i = 0; i < meta.length; i++) {
      if (isNumericType(meta[i].type)) {
        let min = Infinity, max = -Infinity;
        for (const row of data) {
          const v = Number(row[i]);
          if (!isNaN(v)) {
            if (v < min) min = v;
            if (v > max) max = v;
          }
        }
        if (max > -Infinity) {
          extremes[i] = {
            min,
            max
          };
        }
      }
    }
    return extremes;
  };
  const computeColumnWidths = (meta, data) => {
    const lengths = meta.map((col, i) => {
      const headerLen = col.name.length + col.type.length + 1;
      let maxData = 0;
      for (const row of data) {
        const v = row[i];
        const len = v === null ? 4 : String(v).length;
        if (len > maxData) maxData = len;
      }
      return Math.max(headerLen, maxData);
    });
    const total = lengths.reduce((s, l) => s + l, 0);
    return lengths.map(l => `${(l / total * 100).toFixed(1)}%`);
  };
  const copyResultsAsTSV = () => {
    if (!results || !results.meta || !results.data) return;
    const header = results.meta.map(col => col.name).join('\t');
    const rows = results.data.map(row => row.map(cell => cell === null ? 'NULL' : String(cell)).join('\t'));
    const tsv = [header, ...rows].join('\n');
    navigator.clipboard.writeText(tsv);
  };
  const borderColor = isDark ? 'rgba(255,255,255,0.15)' : '#e5e7eb';
  const bgColor = isDark ? 'rgba(255,255,255,0.05)' : '#f9fafb';
  const headerBg = isDark ? '#2a2a2a' : '#f3f4f6';
  const textColor = isDark ? '#e5e7eb' : '#1f2937';
  const mutedColor = isDark ? '#d1d5db' : '#6b7280';
  const accentColor = isDark ? '#FAFF69' : '#323232';
  const accentTextColor = isDark ? '#000' : '#fff';
  const barColor = isDark ? '#35372f' : '#d2d2d2';
  const cellBg = isDark ? '#1f201b' : '#ffffff';
  const cellBgHover = isDark ? 'lch(15.8 0 0)' : '#f0f0f0';
  const extremes = results && results.meta && results.data ? computeColumnExtremes(results.meta, results.data) : {};
  const colWidths = results && results.meta && results.data ? computeColumnWidths(results.meta, results.data) : [];
  const getCellBarStyle = (cell, ci, ri) => {
    if (cell === null) return null;
    const colMeta = results.meta[ci];
    if (!isNumericType(colMeta.type) || !extremes[ci] || results.data.length <= 1 || extremes[ci].max <= 0) return null;
    const ratio = 100 * Number(cell) / extremes[ci].max;
    const bg = ri === hoveredRow ? cellBgHover : cellBg;
    return {
      background: `linear-gradient(to right, ${barColor} 0%, ${barColor} ${ratio}%, ${bg} ${ratio}%, ${bg} 100%)`
    };
  };
  const renderCell = (cell, ci) => {
    if (cell === null) {
      return <span style={{
        color: mutedColor,
        fontStyle: 'italic'
      }}>NULL</span>;
    }
    const value = String(cell);
    if (isHyperlink(value)) {
      return <a href={value} target="_blank" rel="noopener noreferrer" style={{
        color: accentColor,
        textDecoration: 'underline',
        cursor: 'pointer'
      }}>
          {value}
        </a>;
    }
    return value;
  };
  return <div className="not-prose" style={{
    margin: '1rem 0',
    width: '100%',
    boxSizing: 'border-box',
    contain: 'inline-size'
  }}>

      {}
      <div>
        <div ref={codeRef}>
          {children}
        </div>

        {}
        <div style={{
    display: 'flex',
    justifyContent: 'space-between',
    alignItems: 'center',
    padding: '6px 12px',
    backgroundColor: headerBg,
    borderWidth: '0 1px 1px 1px',
    borderStyle: 'solid',
    borderColor: isDark ? 'rgba(255,255,255,0.1)' : 'rgba(11,11,11,0.1)',
    borderRadius: '0 0 4px 4px'
  }}>
          <div style={{
    display: 'flex',
    alignItems: 'center',
    gap: '12px'
  }}>
            {results && <button onClick={() => setShowResults(!showResults)} style={{
    background: 'none',
    border: 'none',
    cursor: 'pointer',
    color: mutedColor,
    fontSize: '12px',
    padding: '2px 4px'
  }}>
                {showResults ? '▼ Hide results' : '▶ Show results'}
              </button>}
            {showStats && stats && <span style={{
    fontSize: '11px',
    color: mutedColor,
    fontStyle: 'italic'
  }}>
                Read {formatRows(stats.rows_read)} rows, {formatBytes(stats.bytes_read)} in {stats.elapsed.toFixed(3)}s
              </span>}
          </div>
          <button onClick={() => executeQuery()} disabled={loading} style={{
    display: 'flex',
    alignItems: 'center',
    gap: '6px',
    padding: '4px 14px',
    borderRadius: '4px',
    border: 'none',
    cursor: loading ? 'wait' : 'pointer',
    backgroundColor: accentColor,
    color: accentTextColor,
    fontSize: '12px',
    fontWeight: 600
  }}>
            {loading ? <span>Running...</span> : <>
                <span style={{
    fontSize: '10px'
  }}>▶</span>
                <span>Run</span>
              </>}
          </button>
        </div>
      </div>

      {}
      {showResults && <div className="not-prose" style={{
    marginTop: '8px',
    maxHeight: '350px',
    overflow: 'auto',
    border: `1px solid ${borderColor}`,
    borderRadius: '4px'
  }}>
          <div>
          {loading && <div style={{
    padding: '24px',
    textAlign: 'center',
    color: mutedColor
  }}>
              Executing query...
            </div>}

          {error && <div style={{
    padding: '12px 16px',
    color: '#ef4444',
    backgroundColor: isDark ? 'rgba(239,68,68,0.1)' : '#fef2f2',
    fontSize: '13px',
    fontFamily: 'monospace',
    whiteSpace: 'pre-wrap'
  }}>
              {error}
            </div>}

          {results && results.meta && results.data && <div style={{
    display: 'grid',
    gridTemplateColumns: colWidths.join(' '),
    width: '100%',
    fontSize: '13px',
    fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace'
  }}>
              {results.meta.map((col, i) => <div key={`h-${i}`} style={{
    position: 'sticky',
    top: 0,
    zIndex: 1,
    padding: '6px 12px',
    textAlign: isNumericType(col.type) && results.meta.length > 1 ? 'right' : 'left',
    backgroundColor: headerBg,
    borderBottom: `1px solid ${borderColor}`,
    color: textColor,
    fontWeight: 600,
    fontSize: '12px',
    whiteSpace: 'nowrap',
    overflow: 'hidden',
    textOverflow: 'ellipsis'
  }}>
                  {col.name}
                  <span style={{
    color: mutedColor,
    fontWeight: 400,
    marginLeft: '4px',
    fontSize: '10px'
  }}>
                    {col.type}
                  </span>
                </div>)}
              {results.data.map((row, ri) => row.map((cell, ci) => <div key={`${ri}-${ci}`} onMouseEnter={() => setHoveredRow(ri)} onMouseLeave={() => setHoveredRow(-1)} style={{
    padding: '4px 12px',
    color: textColor,
    whiteSpace: 'nowrap',
    overflow: 'hidden',
    textOverflow: 'ellipsis',
    textAlign: isNumericType(results.meta[ci].type) && results.meta.length > 1 ? 'right' : 'left',
    borderBottom: `1px solid ${borderColor}`,
    backgroundColor: ri === hoveredRow ? cellBgHover : ri % 2 === 0 ? 'transparent' : bgColor,
    transition: 'background-color 0.1s',
    ...getCellBarStyle(cell, ci, ri)
  }}>
                    {renderCell(cell, ci)}
                  </div>))}
            </div>}

          {results && results.data && <div style={{
    display: 'flex',
    justifyContent: 'space-between',
    alignItems: 'center',
    padding: '4px 12px',
    fontSize: '11px',
    color: mutedColor,
    borderTop: `1px solid ${borderColor}`,
    backgroundColor: headerBg
  }}>
              <span>
                {results.rows} row{results.rows !== 1 ? 's' : ''}
              </span>
              <button onClick={copyResultsAsTSV} style={{
    background: 'none',
    border: 'none',
    cursor: 'pointer',
    color: mutedColor,
    fontSize: '11px',
    padding: '2px 6px',
    borderRadius: '3px'
  }} onMouseEnter={e => e.target.style.color = textColor} onMouseLeave={e => e.target.style.color = mutedColor}>
                ⧉ Copy TSV
              </button>
            </div>}
          </div>
        </div>}
    </div>;
};

export const Image = ({img, alt, size}) => {
  return <Frame>
      <img src={img} alt={alt} />
    </Frame>;
};

<div id="what-are-table-partitions-in-clickhouse">
  ## Что такое партиции таблиц в ClickHouse?
</div>

<br />

Партиции объединяют [части данных](/ru/concepts/core-concepts/parts) таблицы из [семейства движков MergeTree](/ru/reference/engines/table-engines/mergetree-family) в упорядоченные логические единицы. Это способ организации данных в соответствии с определёнными критериями, такими как временные диапазоны, категории или другие ключевые атрибуты. Такие логические единицы упрощают управление данными, выполнение запросов и оптимизацию.

<div id="partition-by">
  ### PARTITION BY
</div>

Партиционирование можно включить при первоначальном определении таблицы с помощью [секции PARTITION BY](/ru/reference/engines/table-engines/mergetree-family/custom-partitioning-key). Эта секция может содержать SQL-выражение над любыми столбцами, результат которого определяет, к какой партиции принадлежит строка.

Чтобы проиллюстрировать это, мы [дополним](https://sql.clickhouse.com/?query=U0hPVyBDUkVBVEUgVEFCTEUgdWsudWtfcHJpY2VfcGFpZF9zaW1wbGVfcGFydGl0aW9uZWQ\&run_query=true\&tab=results) пример таблицы [Что такое части таблицы](/ru/concepts/core-concepts/parts), добавив секцию `PARTITION BY toStartOfMonth(date)`, которая распределяет части таблицы по месяцам продажи недвижимости:

```sql theme={null}
CREATE TABLE uk.uk_price_paid_simple_partitioned
(
    date Date,
    town LowCardinality(String),
    street LowCardinality(String),
    price UInt32
)
ENGINE = MergeTree
ORDER BY (town, street)
PARTITION BY toStartOfMonth(date);
```

Вы можете [запросить данные из этой таблицы](https://sql.clickhouse.com/?query=U0VMRUNUICogRlJPTSB1ay51a19wcmljZV9wYWlkX3NpbXBsZV9wYXJ0aXRpb25lZA\&run_query=true\&tab=results) в нашей Песочнице ClickHouse.

<div id="structure-on-disk">
  ### Структура на диске
</div>

Каждый раз, когда в таблицу вставляется набор строк, ClickHouse вместо создания одной-единственной части данных, содержащей все вставленные строки (как описано [здесь](/ru/concepts/core-concepts/parts)), создает как минимум одну новую часть данных для каждого уникального значения ключа партиционирования среди вставленных строк (не менее [одной](/ru/reference/settings/session-settings#max_insert_block_size)):

<Image img="https://mintcdn.com/private-7c7dfe99-fix-nav-issues/0xkAyEEn8ANRFZGQ/images/managing-data/core-concepts/partitions.png?fit=max&auto=format&n=0xkAyEEn8ANRFZGQ&q=85&s=a87e1b05a21ab962ae2be0eaaab9623b" size="lg" alt="ОБРАБОТКА INSERT" width="3324" height="2270" data-path="images/managing-data/core-concepts/partitions.png" />

<br />

Сначала сервер ClickHouse разделяет строки из примера вставки с 4 строками, схематично показанного на диаграмме выше, по значению ключа партиционирования `toStartOfMonth(date)`.
Затем для каждой выявленной партиции строки обрабатываются [как обычно](/ru/concepts/core-concepts/parts) в несколько последовательных шагов (① Сортировка, ② Разбиение на столбцы, ③ Сжатие, ④ Запись на диск).

Обратите внимание: при включенном партиционировании ClickHouse автоматически создает [индексы MinMax](https://github.com/ClickHouse/ClickHouse/blob/dacc8ebb0dac5bbfce5a7541e7fc70f26f7d5065/src/Storages/MergeTree/IMergeTreeDataPart.h#L341) для каждой части данных. Это просто файлы для каждого столбца таблицы, используемого в выражении ключа партиционирования, которые содержат минимальное и максимальное значения этого столбца в пределах части данных.

<div id="per-partition-merges">
  ### Слияния внутри партиций
</div>

При включенном партиционировании ClickHouse выполняет [слияния](/ru/concepts/core-concepts/merges) частей данных только в пределах одной партиции, но не между разными партициями. Покажем это на примере таблицы выше:

<Image img="https://mintcdn.com/private-7c7dfe99-fix-nav-issues/0xkAyEEn8ANRFZGQ/images/managing-data/core-concepts/merges_with_partitions.png?fit=max&auto=format&n=0xkAyEEn8ANRFZGQ&q=85&s=188809caacf2d0a5e1a86337d89fc906" size="lg" alt="PART MERGES" width="2480" height="1870" data-path="images/managing-data/core-concepts/merges_with_partitions.png" />

<br />

Как показано на диаграмме выше, части, принадлежащие разным партициям, никогда не сливаются. Если выбран ключ партиционирования с высокой мощностью, то части, распределенные по тысячам партиций, никогда не будут кандидатами на слияние, что приведет к превышению заранее заданных ограничений и вызовет пресловутую ошибку `Too many parts`. Решение этой проблемы простое: выберите разумный ключ партиционирования с [мощностью менее 1000..10000](https://github.com/ClickHouse/ClickHouse/blob/ffc5b2c56160b53cf9e5b16cfb73ba1d956f7ce4/src/Storages/MergeTree/MergeTreeDataWriter.cpp#L121).

<div id="monitoring-partitions">
  ## Мониторинг партиций
</div>

Вы можете [выполнить запрос](https://sql.clickhouse.com/?query=U0VMRUNUIERJU1RJTkNUIF9wYXJ0aXRpb25fdmFsdWUgQVMgcGFydGl0aW9uCkZST00gdWsudWtfcHJpY2VfcGFpZF9zaW1wbGVfcGFydGl0aW9uZWQKT1JERVIgQlkgcGFydGl0aW9uIEFTQw\&run_query=true\&tab=results), чтобы получить список всех существующих уникальных партиций нашей тестовой таблицы, используя [виртуальный столбец](/ru/reference/engines/table-engines#table_engines-virtual_columns) `_partition_value`:

<RunnableCode>
  ```sql theme={null}
  SELECT DISTINCT _partition_value AS partition
  FROM uk.uk_price_paid_simple_partitioned
  ORDER BY partition ASC;
  ```
</RunnableCode>

Кроме того, ClickHouse отслеживает все части и партиции всех таблиц в системной таблице [system.parts](/ru/reference/system-tables/parts), а следующий запрос [возвращает](https://sql.clickhouse.com/?query=U0VMRUNUCiAgICBwYXJ0aXRpb24sCiAgICBjb3VudCgpIEFTIHBhcnRzLAogICAgc3VtKHJvd3MpIEFTIHJvd3MKRlJPTSBzeXN0ZW0ucGFydHMKV0hFUkUgKGRhdGFiYXNlID0gJ3VrJykgQU5EIChgdGFibGVgID0gJ3VrX3ByaWNlX3BhaWRfc2ltcGxlX3BhcnRpdGlvbmVkJykgQU5EIGFjdGl2ZQpHUk9VUCBCWSBwYXJ0aXRpb24KT1JERVIgQlkgcGFydGl0aW9uIEFTQzs\&run_query=true\&tab=results) для приведённой выше тестовой таблицы список всех партиций, а также текущее количество активных частей и суммарное число строк в этих частях для каждой партиции:

<RunnableCode>
  ```sql theme={null}
  SELECT
      partition,
      count() AS parts,
      sum(rows) AS rows
  FROM system.parts
  WHERE (database = 'uk') AND (`table` = 'uk_price_paid_simple_partitioned') AND active
  GROUP BY partition
  ORDER BY partition ASC;
  ```
</RunnableCode>

<div id="what-are-table-partitions-used-for">
  ## Для чего нужны партиции таблиц?
</div>

<div id="data-management">
  ### Управление данными
</div>

В ClickHouse партиционирование в первую очередь используется для управления данными. Благодаря логической организации данных на основе выражения партиционирования каждой партицией можно управлять независимо. Например, схема партиционирования в приведённой выше таблице позволяет реализовать сценарии, в которых в основной таблице хранятся только данные за последние 12 месяцев, а более старые данные автоматически удаляются с помощью [правила TTL](/ru/concepts/features/operations/delete/ttl) (см. добавленную последнюю строку DDL-оператора):

```sql theme={null}
CREATE TABLE uk.uk_price_paid_simple_partitioned
(
    date Date,
    town LowCardinality(String),
    street LowCardinality(String),
    price UInt32
)
ENGINE = MergeTree
PARTITION BY toStartOfMonth(date)
ORDER BY (town, street)
TTL date + INTERVAL 12 MONTH DELETE;
```

Поскольку таблица разбита на партиции по `toStartOfMonth(date)`, целые партиции (наборы [частей таблицы](/ru/concepts/core-concepts/parts)), подпадающие под условие TTL, будут удаляться, что повышает эффективность очистки [без переписывания частей](/ru/reference/statements/alter#mutations).

Аналогично, вместо удаления старых данных их можно автоматически и эффективно перемещать на более экономичный [уровень хранилища](/ru/integrations/connectors/data-ingestion/AWS/integrating-s3-with-clickhouse#storage-tiers):

```sql theme={null}
CREATE TABLE uk.uk_price_paid_simple_partitioned
(
    date Date,
    town LowCardinality(String),
    street LowCardinality(String),
    price UInt32
)
ENGINE = MergeTree
PARTITION BY toStartOfMonth(date)
ORDER BY (town, street)
TTL date + INTERVAL 12 MONTH TO VOLUME 'slow_but_cheap';
```

<div id="query-optimization">
  ### Оптимизация запросов
</div>

Партиции могут повысить производительность запросов, но это сильно зависит от характера доступа к данным. Если запросы обращаются только к нескольким партициям (в идеале — к одной), производительность может улучшиться. На практике это обычно полезно только тогда, когда ключ партиционирования не входит в первичный ключ, а фильтрация выполняется именно по нему, как показано в примере запроса ниже.

<RunnableCode>
  ```sql theme={null}
  SELECT MAX(price) AS highest_price
  FROM uk.uk_price_paid_simple_partitioned
  WHERE date >= '2020-12-01'
    AND date <= '2020-12-31'
    AND town = 'LONDON';
  ```
</RunnableCode>

Этот запрос выполняется для таблицы из примера выше и [вычисляет](https://sql.clickhouse.com/?query=U0VMRUNUIE1BWChwcmljZSkgQVMgaGlnaGVzdF9wcmljZQpGUk9NIHVrLnVrX3ByaWNlX3BhaWRfc2ltcGxlX3BhcnRpdGlvbmVkCldIRVJFIGRhdGUgPj0gJzIwMjAtMTItMDEnCiAgQU5EIGRhdGUgPD0gJzIwMjAtMTItMzEnCiAgQU5EIHRvd24gPSAnTE9ORE9OJzs\&run_query=true\&tab=results) максимальную цену среди всех проданных объектов недвижимости в Лондоне за декабрь 2020 года, используя фильтрацию как по столбцу (`date`), который входит в ключ партиционирования таблицы, так и по столбцу (`town`), который входит в первичный ключ таблицы (при этом `date` не является частью первичного ключа).

ClickHouse обрабатывает этот запрос, применяя последовательность методов отсеивания данных, чтобы не обрабатывать нерелевантные данные:

<Image img="https://mintcdn.com/private-7c7dfe99-fix-nav-issues/0xkAyEEn8ANRFZGQ/images/managing-data/core-concepts/partition-pruning.png?fit=max&auto=format&n=0xkAyEEn8ANRFZGQ&q=85&s=0d3c86dd0bbcb386336c1b8a61f6d2bb" size="lg" alt="PART MERGES 2" width="2478" height="1886" data-path="images/managing-data/core-concepts/partition-pruning.png" />

<br />

① **Отсечение партиций**: [MinMax indexes](/ru/concepts/core-concepts/partitions#what-are-table-partitions-in-clickhouse) используются, чтобы игнорировать целые партиции (наборы частей), которые заведомо не могут соответствовать фильтру запроса по столбцам, используемым в ключе партиционирования таблицы.

② **Отсечение гранул**: Для оставшихся частей данных после шага ① используется их [первичный индекс](/ru/guides/clickhouse/data-modelling/sparse-primary-indexes), чтобы игнорировать все [гранулы](/ru/guides/clickhouse/data-modelling/sparse-primary-indexes#data-is-organized-into-granules-for-parallel-data-processing) (блоки строк), которые заведомо не могут соответствовать фильтру запроса по столбцам, используемым в первичном ключе таблицы.

Мы можем увидеть эти шаги отсеивания данных, [изучив](https://sql.clickhouse.com/?query=RVhQTEFJTiBpbmRleGVzID0gMQpTRUxFQ1QgTUFYKHByaWNlKSBBUyBoaWdoZXN0X3ByaWNlCkZST00gdWsudWtfcHJpY2VfcGFpZF9zaW1wbGVfcGFydGl0aW9uZWQKV0hFUkUgZGF0ZSA-PSAnMjAyMC0xMi0wMScKICBBTkQgZGF0ZSA8PSAnMjAyMC0xMi0zMScKICBBTkQgdG93biA9ICdMT05ET04nOw\&run_query=true\&tab=results) физический план выполнения для нашего примера запроса выше с помощью оператора [EXPLAIN](/ru/reference/statements/explain) :

```sql style="fontSize:13px" theme={null}
EXPLAIN indexes = 1
SELECT MAX(price) AS highest_price
FROM uk.uk_price_paid_simple_partitioned
WHERE date >= '2020-12-01'
  AND date <= '2020-12-31'
  AND town = 'LONDON';
```

```response theme={null}
    ┌─explain──────────────────────────────────────────────────────────────────────────────────────────────────────┐
 1. │ Expression ((Project names + Projection))                                                                    │
 2. │   Aggregating                                                                                                │
 3. │     Expression (Before GROUP BY)                                                                             │
 4. │       Expression                                                                                             │
 5. │         ReadFromMergeTree (uk.uk_price_paid_simple_partitioned)                                              │
 6. │         Indexes:                                                                                             │
 7. │           MinMax                                                                                             │
 8. │             Keys:                                                                                            │
 9. │               date                                                                                           │
10. │             Condition: and((date in (-Inf, 18627]), (date in [18597, +Inf)))                                 │
11. │             Parts: 1/436                                                                                     │
12. │             Granules: 11/3257                                                                                │
13. │           Partition                                                                                          │
14. │             Keys:                                                                                            │
15. │               toStartOfMonth(date)                                                                           │
16. │             Condition: and((toStartOfMonth(date) in (-Inf, 18597]), (toStartOfMonth(date) in [18597, +Inf))) │
17. │             Parts: 1/1                                                                                       │
18. │             Granules: 11/11                                                                                  │
19. │           PrimaryKey                                                                                         │
20. │             Keys:                                                                                            │
21. │               town                                                                                           │
22. │             Condition: (town in ['LONDON', 'LONDON'])                                                        │
23. │             Parts: 1/1                                                                                       │
24. │             Granules: 1/11                                                                                   │
    └──────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
```

Вывод выше показывает:

① Отсечение партиций: строки 7–18 приведённого выше вывода EXPLAIN показывают, что ClickHouse сначала использует [индекс MinMax](/ru/concepts/core-concepts/partitions#what-are-table-partitions-in-clickhouse) поля `date`, чтобы выявить 11 из 3257 существующих [гранул](/ru/guides/clickhouse/data-modelling/sparse-primary-indexes#data-is-organized-into-granules-for-parallel-data-processing) (блоков строк), хранящихся в 1 из 436 существующих активных частей данных, которые содержат строки, соответствующие фильтру `date` в запросе.

② Отсечение гранул: строки 19–24 приведённого выше вывода EXPLAIN показывают, что затем ClickHouse использует [первичный индекс](/ru/guides/clickhouse/data-modelling/sparse-primary-indexes) (созданный по полю `town`) части данных, определённой на шаге ①, чтобы ещё сильнее сократить число гранул (содержащих строки, которые потенциально также соответствуют фильтру `town` в запросе) — с 11 до 1. Это также отражено в выводе ClickHouse-client, который мы привели выше для выполненного запроса:

```response theme={null}
... Elapsed: 0.006 sec. Processed 8.19 thousand rows, 57.34 KB (1.36 million rows/s., 9.49 MB/s.)
Peak memory usage: 2.73 MiB.
```

Это означает, что ClickHouse просканировал и обработал 1 гранулу (блок из [8192](/ru/reference/settings/merge-tree-settings#index_granularity) строк) за 6 миллисекунд, чтобы вычислить результат запроса.

<div id="partitioning-is-primarily-a-data-management-feature">
  ### Партиционирование — это прежде всего средство управления данными
</div>

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

При партиционировании данные обычно распределяются по большему числу частей данных, из-за чего ClickHouse часто приходится сканировать и обрабатывать больший объем данных.

Это можно продемонстрировать, выполнив один и тот же запрос как для таблицы из примера [Что такое части таблицы](/ru/concepts/core-concepts/parts) (без партиционирования), так и для нашей текущей таблицы из примера выше (с партиционированием). Обе таблицы [содержат](https://sql.clickhouse.com/?query=U0VMRUNUCiAgICB0YWJsZSwKICAgIHN1bShyb3dzKSBBUyByb3dzCkZST00gc3lzdGVtLnBhcnRzCldIRVJFIChkYXRhYmFzZSA9ICd1aycpIEFORCAoYHRhYmxlYCBJTiBbJ3VrX3ByaWNlX3BhaWRfc2ltcGxlJywgJ3VrX3ByaWNlX3BhaWRfc2ltcGxlX3BhcnRpdGlvbmVkJ10pIEFORCBhY3RpdmUKR1JPVVAgQlkgdGFibGU7\&run_query=true\&tab=results) одни и те же данные и одинаковое количество строк:

<RunnableCode>
  ```sql theme={null}
  SELECT
      table,
      sum(rows) AS rows
  FROM system.parts
  WHERE (database = 'uk') AND (table IN ['uk_price_paid_simple', 'uk_price_paid_simple_partitioned']) AND active
  GROUP BY table;
  ```
</RunnableCode>

Однако в таблице с партиционированием [больше](https://sql.clickhouse.com/?query=U0VMRUNUCiAgICB0YWJsZSwKICAgIGNvdW50KCkgQVMgcGFydHMKRlJPTSBzeXN0ZW0ucGFydHMKV0hFUkUgKGRhdGFiYXNlID0gJ3VrJykgQU5EIChgdGFibGVgIElOIFsndWtfcHJpY2VfcGFpZF9zaW1wbGUnLCAndWtfcHJpY2VfcGFpZF9zaW1wbGVfcGFydGl0aW9uZWQnXSkgQU5EIGFjdGl2ZQpHUk9VUCBCWSB0YWJsZTs\&run_query=true\&tab=results) активных [частей данных](/ru/concepts/core-concepts/parts), потому что, как упоминалось выше, ClickHouse [выполняет слияние](/ru/concepts/core-concepts/parts) части данных только внутри партиций, но не между ними:

<RunnableCode>
  ```sql theme={null}
  SELECT
      table,
      count() AS parts
  FROM system.parts
  WHERE (database = 'uk') AND (table IN ['uk_price_paid_simple', 'uk_price_paid_simple_partitioned']) AND active
  GROUP BY table;

  ```
</RunnableCode>

Как показано выше, партиционированная таблица `uk_price_paid_simple_partitioned` имеет более 600 партиций и, следовательно, 600 306 активных частей данных. В то же время в таблице без партиционирования `uk_price_paid_simple` все [исходные](/ru/concepts/core-concepts/parts) части данных могли быть объединены фоновыми слияниями в одну активную часть.

Если [посмотреть](https://sql.clickhouse.com/?query=RVhQTEFJTiBpbmRleGVzID0gMQpTRUxFQ1QgTUFYKHByaWNlKSBBUyBoaWdoZXN0X3ByaWNlCkZST00gdWsudWtfcHJpY2VfcGFpZF9zaW1wbGVfcGFydGl0aW9uZWQKV0hFUkUgdG93biA9ICdMT05ET04nOw\&run_query=true\&tab=results) физический план выполнения нашего запроса из примера выше без фильтра по партиции для партиционированной таблицы с помощью оператора [EXPLAIN](/ru/reference/statements/explain), то в строках 19 и 20 приведенного ниже вывода видно, что ClickHouse определил 671 из 3257 существующих [гранул](/ru/guides/clickhouse/data-modelling/sparse-primary-indexes#data-is-organized-into-granules-for-parallel-data-processing) (блоков строк), распределенных по 431 из 436 существующих активных частей данных, которые потенциально содержат строки, соответствующие фильтру запроса, и поэтому будут просканированы и обработаны движком запросов:

```sql theme={null}
EXPLAIN indexes = 1
SELECT MAX(price) AS highest_price
FROM uk.uk_price_paid_simple_partitioned
WHERE town = 'LONDON';
```

```response theme={null}
    ┌─explain─────────────────────────────────────────────────────────┐
 1. │ Expression ((Project names + Projection))                       │
 2. │   Aggregating                                                   │
 3. │     Expression (Before GROUP BY)                                │
 4. │       Expression                                                │
 5. │         ReadFromMergeTree (uk.uk_price_paid_simple_partitioned) │
 6. │         Indexes:                                                │
 7. │           MinMax                                                │
 8. │             Condition: true                                     │
 9. │             Parts: 436/436                                      │
10. │             Granules: 3257/3257                                 │
11. │           Partition                                             │
12. │             Condition: true                                     │
13. │             Parts: 436/436                                      │
14. │             Granules: 3257/3257                                 │
15. │           PrimaryKey                                            │
16. │             Keys:                                               │
17. │               town                                              │
18. │             Condition: (town in ['LONDON', 'LONDON'])           │
19. │             Parts: 431/436                                      │
20. │             Granules: 671/3257                                  │
    └─────────────────────────────────────────────────────────────────┘
```

Физический план выполнения того же примерного запроса для таблицы без партиций [показывает](https://sql.clickhouse.com/?query=RVhQTEFJTiBpbmRleGVzID0gMQpTRUxFQ1QgTUFYKHByaWNlKSBBUyBoaWdoZXN0X3ByaWNlCkZST00gdWsudWtfcHJpY2VfcGFpZF9zaW1wbGUKV0hFUkUgdG93biA9ICdMT05ET04nOw\&run_query=true\&tab=results) в строках 11 и 12 приведённого ниже вывода, что ClickHouse выявил 241 из 3083 существующих блоков строк в единственной активной части данных таблицы, которые потенциально содержат строки, соответствующие фильтру запроса:

```sql theme={null}
EXPLAIN indexes = 1
SELECT MAX(price) AS highest_price
FROM uk.uk_price_paid_simple
WHERE town = 'LONDON';
```

```response theme={null}
    ┌─explain───────────────────────────────────────────────┐
 1. │ Expression ((Project names + Projection))             │
 2. │   Aggregating                                         │
 3. │     Expression (Before GROUP BY)                      │
 4. │       Expression                                      │
 5. │         ReadFromMergeTree (uk.uk_price_paid_simple)   │
 6. │         Indexes:                                      │
 7. │           PrimaryKey                                  │
 8. │             Keys:                                     │
 9. │               town                                    │
10. │             Condition: (town in ['LONDON', 'LONDON']) │
11. │             Parts: 1/1                                │
12. │             Granules: 241/3083                        │
    └───────────────────────────────────────────────────────┘
```

При [выполнении](https://sql.clickhouse.com/?query=U0VMRUNUIE1BWChwcmljZSkgQVMgaGlnaGVzdF9wcmljZQpGUk9NIHVrLnVrX3ByaWNlX3BhaWRfc2ltcGxlX3BhcnRpdGlvbmVkCldIRVJFIHRvd24gPSAnTE9ORE9OJzs\&run_query=true\&tab=results) запроса к партиционированной версии таблицы ClickHouse сканирует и обрабатывает 671 блок строк (\~ 5,5 миллиона строк) за 90 миллисекунд:

```sql theme={null}
SELECT MAX(price) AS highest_price
FROM uk.uk_price_paid_simple_partitioned
WHERE town = 'LONDON';
```

```response theme={null}
┌─highest_price─┐
│     594300000 │ -- 594.30 million
└───────────────┘

1 row in set. Elapsed: 0.090 sec. Processed 5.48 million rows, 27.95 MB (60.66 million rows/s., 309.51 MB/s.)
Peak memory usage: 163.44 MiB.
```

В то время как при [выполнении](https://sql.clickhouse.com/?query=U0VMRUNUIE1BWChwcmljZSkgQVMgaGlnaGVzdF9wcmljZQpGUk9NIHVrLnVrX3ByaWNlX3BhaWRfc2ltcGxlCldIRVJFIHRvd24gPSAnTE9ORE9OJzs\&run_query=true\&tab=results) запроса к непартиционированной таблице ClickHouse сканирует и обрабатывает 241 блок (\~ 2 миллиона строк) за 12 миллисекунд:

```sql theme={null}
SELECT MAX(price) AS highest_price
FROM uk.uk_price_paid_simple
WHERE town = 'LONDON';
```

```response theme={null}
┌─highest_price─┐
│     594300000 │ -- 594,30 млн
└───────────────┘

1 row in set. Elapsed: 0.012 sec. Processed 1.97 million rows, 9.87 MB (162.23 million rows/s., 811.17 MB/s.)
Peak memory usage: 62.02 MiB.
```
