> ## Documentation Index
> Fetch the complete documentation index at: https://private-7c7dfe99-parallel-read-in-order-multi-part.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

> Изучите ClickHouse на наборе данных о миллиардах поездок на такси и автомобилях с водителем по вызову (Uber, Lyft и т. д.), совершённых в Нью-Йорке с 2009 года

# Геопространственный анализ на наборе данных о такси Нью-Йорка

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 || "Ошибка выполнения запроса");
    }
    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 ? "▼ Скрыть результаты" : "▶ Показать результаты"}
              </button>}
            {showStats && stats && <span style={{
    fontSize: "11px",
    color: mutedColor,
    fontStyle: "italic"
  }}>
                Прочитано {formatRows(stats.rows_read)} строк, {formatBytes(stats.bytes_read)} за {stats.elapsed.toFixed(3)}с
              </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>Выполняется...</span> : <>
                <span style={{
    fontSize: "10px"
  }}>▶</span>
                <span>Выполнить</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
  }}>Выполнение запроса...</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,
    ...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} {results.rows === 1 ? "строка" : results.rows >= 2 && results.rows <= 4 ? "строки" : "строк"}
                </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}>
                  ⧉ Копировать TSV
                </button>
              </div>}
          </div>
        </div>}
    </div>;
};

<View title="Cloud">
  В этом руководстве вы узнаете, как с помощью ClickHouse выполнять аналитические запросы к большим объёмам данных.
  Кроме того, вы научитесь обогащать данные с помощью словаря и писать запросы с JOIN.

  ## Предварительные требования

  Для работы с этим руководством вам понадобится:

  * [Аккаунт ClickHouse Cloud](https://clickhouse.cloud/signUp?loc=docs-sample-datasets-nyc-taxi) (при регистрации начисляется 300 \$ в виде бесплатных кредитов)
  * [Сервис ClickHouse Cloud](/ru/get-started/setup/cloud#1-create-a-clickhouse-service)

  <Steps titleSize="h2">
    <Step title="Создайте таблицу" id="create-a-new-table">
      В этом руководстве используется набор данных о поездках на такси в Нью-Йорке. Он содержит сведения о миллионах поездок и включает такие столбцы, как размер чаевых, дорожные сборы, способ оплаты и другие.

      1. Выберите **SQL console** в меню слева
      2. Нажмите на вкладку **+** рядом со значком домашней страницы, чтобы создать новый запрос
      3. Введите следующий запрос в редакторе SQL и нажмите **Run**:

      ```sql Expandable theme={null}
      CREATE TABLE trips
      (
          `trip_id` UInt32,
          `vendor_id` Enum8('1' = 1, '2' = 2, '3' = 3, '4' = 4, 'CMT' = 5, 'VTS' = 6, 'DDS' = 7, 'B02512' = 10, 'B02598' = 11, 'B02617' = 12, 'B02682' = 13, 'B02764' = 14, '' = 15),
          `pickup_date` Date,
          `pickup_datetime` DateTime,
          `dropoff_date` Date,
          `dropoff_datetime` DateTime,
          `store_and_fwd_flag` UInt8,
          `rate_code_id` UInt8,
          `pickup_longitude` Float64,
          `pickup_latitude` Float64,
          `dropoff_longitude` Float64,
          `dropoff_latitude` Float64,
          `passenger_count` UInt8,
          `trip_distance` Float64,
          `fare_amount` Float32,
          `extra` Float32,
          `mta_tax` Float32,
          `tip_amount` Float32,
          `tolls_amount` Float32,
          `ehail_fee` Float32,
          `improvement_surcharge` Float32,
          `total_amount` Float32,
          `payment_type` Enum8('UNK' = 0, 'CSH' = 1, 'CRE' = 2, 'NOC' = 3, 'DIS' = 4),
          `trip_type` UInt8,
          `pickup` FixedString(25),
          `dropoff` FixedString(25),
          `cab_type` Enum8('yellow' = 1, 'green' = 2, 'uber' = 3),
          `pickup_nyct2010_gid` Int8,
          `pickup_ctlabel` Float32,
          `pickup_borocode` Int8,
          `pickup_ct2010` String,
          `pickup_boroct2010` String,
          `pickup_cdeligibil` String,
          `pickup_ntacode` FixedString(4),
          `pickup_ntaname` String,
          `pickup_puma` UInt16,
          `dropoff_nyct2010_gid` UInt8,
          `dropoff_ctlabel` Float32,
          `dropoff_borocode` UInt8,
          `dropoff_ct2010` String,
          `dropoff_boroct2010` String,
          `dropoff_cdeligibil` String,
          `dropoff_ntacode` FixedString(4),
          `dropoff_ntaname` String,
          `dropoff_puma` UInt16
      )
      ENGINE = MergeTree
      PARTITION BY toYYYYMM(pickup_date)
      ORDER BY pickup_datetime;
      ```
    </Step>

    <Step title="Вставьте данные" id="add-the-dataset">
      Теперь, когда таблица создана, загрузите в неё данные о поездках на такси в Нью-Йорке из CSV-файлов в S3.

      Следующая команда вставляет около 2 000 000 строк в таблицу trips из двух файлов в S3 — `trips_1.tsv.gz` и `trips_2.tsv.gz`:

      ```sql Expandable theme={null}
      INSERT INTO trips
      SELECT * FROM s3(
          'https://datasets-documentation.s3.eu-west-3.amazonaws.com/nyc-taxi/trips_{1..2}.gz',
          'TabSeparatedWithNames', "
          `trip_id` UInt32,
          `vendor_id` Enum8('1' = 1, '2' = 2, '3' = 3, '4' = 4, 'CMT' = 5, 'VTS' = 6, 'DDS' = 7, 'B02512' = 10, 'B02598' = 11, 'B02617' = 12, 'B02682' = 13, 'B02764' = 14, '' = 15),
          `pickup_date` Date,
          `pickup_datetime` DateTime,
          `dropoff_date` Date,
          `dropoff_datetime` DateTime,
          `store_and_fwd_flag` UInt8,
          `rate_code_id` UInt8,
          `pickup_longitude` Float64,
          `pickup_latitude` Float64,
          `dropoff_longitude` Float64,
          `dropoff_latitude` Float64,
          `passenger_count` UInt8,
          `trip_distance` Float64,
          `fare_amount` Float32,
          `extra` Float32,
          `mta_tax` Float32,
          `tip_amount` Float32,
          `tolls_amount` Float32,
          `ehail_fee` Float32,
          `improvement_surcharge` Float32,
          `total_amount` Float32,
          `payment_type` Enum8('UNK' = 0, 'CSH' = 1, 'CRE' = 2, 'NOC' = 3, 'DIS' = 4),
          `trip_type` UInt8,
          `pickup` FixedString(25),
          `dropoff` FixedString(25),
          `cab_type` Enum8('yellow' = 1, 'green' = 2, 'uber' = 3),
          `pickup_nyct2010_gid` Int8,
          `pickup_ctlabel` Float32,
          `pickup_borocode` Int8,
          `pickup_ct2010` String,
          `pickup_boroct2010` String,
          `pickup_cdeligibil` String,
          `pickup_ntacode` FixedString(4),
          `pickup_ntaname` String,
          `pickup_puma` UInt16,
          `dropoff_nyct2010_gid` UInt8,
          `dropoff_ctlabel` Float32,
          `dropoff_borocode` UInt8,
          `dropoff_ct2010` String,
          `dropoff_boroct2010` String,
          `dropoff_cdeligibil` String,
          `dropoff_ntacode` FixedString(4),
          `dropoff_ntaname` String,
          `dropoff_puma` UInt16
      ") SETTINGS input_format_try_infer_datetimes = 0
      ```

      Дождитесь окончания вставки данных. Будет загружено около 150 МБ данных.
      Когда вставка завершится, проверьте количество строк в таблице `trips`:

      ```sql theme={null}
      SELECT count() FROM trips
      ```

      В результате должно получиться 1 999 657 строк
    </Step>

    <Step title="Проанализируйте данные" id="analyze-the-data">
      После загрузки данных можно выполнить несколько запросов для их анализа.

      * Рассчитайте средний размер чаевых:
        ```sql theme={null}
        SELECT round(avg(tip_amount), 2) FROM trips
        ```

      * Рассчитайте среднюю стоимость поездки в зависимости от количества пассажиров:
        ```sql theme={null}
        SELECT
            passenger_count,
            ceil(avg(total_amount),2) AS average_total_amount
        FROM trips
        GROUP BY passenger_count
        ```

      * Рассчитайте ежедневное количество посадок пассажиров по районам:
        ```sql theme={null}
        SELECT
          pickup_date,
          pickup_ntaname,
          SUM(1) AS number_of_trips
        FROM trips
        GROUP BY pickup_date, pickup_ntaname
        ORDER BY pickup_date ASC
        ```

      * Рассчитайте продолжительность каждой поездки в минутах и сгруппируйте результаты по продолжительности:
        ```sql theme={null}
        SELECT
          avg(tip_amount) AS avg_tip,
          avg(fare_amount) AS avg_fare,
          avg(passenger_count) AS avg_passenger,
          count() AS count,
          truncate(date_diff('second', pickup_datetime, dropoff_datetime)/60) as trip_minutes
        FROM trips
        WHERE trip_minutes > 0
        GROUP BY trip_minutes
        ORDER BY trip_minutes DESC
        ```

      * Выведите количество посадок пассажиров в каждом районе с разбивкой по часам суток:
        ```sql theme={null}
        SELECT
            pickup_ntaname,
            toHour(pickup_datetime) as pickup_hour,
            SUM(1) AS pickups
        FROM trips
        WHERE pickup_ntaname != ''
        GROUP BY pickup_ntaname, pickup_hour
        ORDER BY pickup_ntaname, pickup_hour
        ```
    </Step>

    <Step title="Создайте словарь" id="create-a-dictionary">
      Далее вы создадите словарь (набор пар ключ-значение, хранящийся в памяти) с именем `taxi_zone_dictionary`, который сопоставляет идентификаторы местоположений с названиями боро Нью-Йорка. Источником данных служит CSV-файл со списком всех районов Нью-Йорка.
      Эти идентификаторы соответствуют столбцам `pickup_nyct2010_gid` и `dropoff_nyct2010_gid` в таблице trips.

      Ниже приведён фрагмент используемого CSV-файла в табличном виде. Столбец `LocationID` в файле соответствует столбцам `pickup_nyct2010_gid` и `dropoff_nyct2010_gid` в вашей таблице `trips`:

      | LocationID | Borough | Zone | service\_zone |
      | - | - | - | - |
      | 1 | EWR | Newark Airport | EWR |
      | 2 | Queens | Jamaica Bay | Boro Zone |
      | 3 | Bronx | Allerton/Pelham Gardens | Boro Zone |
      | 4 | Manhattan | Alphabet City | Yellow Zone |
      | 5 | Staten Island | Arden Heights | Boro Zone |

      Выполните следующую SQL-команду: она создаёт словарь с именем `taxi_zone_dictionary` и заполняет его данными из CSV-файла в S3. URL файла: `https://datasets-documentation.s3.eu-west-3.amazonaws.com/nyc-taxi/taxi_zone_lookup.csv`.

      ```sql theme={null}
      CREATE DICTIONARY taxi_zone_dictionary
      (
        `LocationID` UInt16 DEFAULT 0,
        `Borough` String,
        `Zone` String,
        `service_zone` String
      )
      PRIMARY KEY LocationID
      SOURCE(HTTP(URL 'https://datasets-documentation.s3.eu-west-3.amazonaws.com/nyc-taxi/taxi_zone_lookup.csv' FORMAT 'CSVWithNames'))
      LIFETIME(MIN 0 MAX 0)
      LAYOUT(HASHED_ARRAY())
      ```

      <Note>
        Если задать для `LIFETIME` значение 0, автоматические обновления отключаются, что позволяет избежать лишнего трафика к нашему S3 бакету. В других случаях вы можете настроить этот параметр иначе. Подробнее см. в разделе [Обновление данных словаря с помощью LIFETIME](/ru/reference/statements/create/dictionary/lifetime).
      </Note>

      Убедитесь, что всё работает. Следующий запрос должен вернуть 265 строк — по одной для каждого района:

      ```sql theme={null}
      SELECT * FROM taxi_zone_dictionary
      ```
    </Step>

    <Step title="Выполняйте запросы с помощью словаря">
      Чтобы получить значение из словаря, используйте функцию `dictGet` ([или её разновидности](/ru/reference/functions/regular-functions/ext-dict-functions)).
      Передайте ей имя словаря, имя нужного значения и ключ (в нашем примере это столбец `LocationID` словаря `taxi_zone_dictionary`) — и функция вернёт соответствующее значение.
      Например, следующий запрос возвращает `Borough` для `LocationID`, равного 132 (это аэропорт JFK):

      ```sql theme={null}
      SELECT dictGet('taxi_zone_dictionary', 'Borough', 132)
      ```

      JFK находится в Куинсе. Обратите внимание, что время получения значения практически нулевое:

      ```response theme={null}
      ┌─dictGet('taxi_zone_dictionary', 'Borough', 132)─┐
      │ Queens                                          │
      └─────────────────────────────────────────────────┘

      1 rows in set. Elapsed: 0.004 sec.
      ```

      Чтобы проверить, есть ли ключ в словаре, используйте функцию `dictHas`. Например, следующий запрос возвращает `1` (в ClickHouse это означает "true"):

      ```sql theme={null}
      SELECT dictHas('taxi_zone_dictionary', 132)
      ```

      Следующий запрос возвращает 0, так как в словаре нет значения `LocationID`, равного 4567:

      ```sql theme={null}
      SELECT dictHas('taxi_zone_dictionary', 4567)
      ```

      Чтобы получить название боро в запросе, используйте функцию `dictGet`. Например:

      ```sql theme={null}
      SELECT
        count(1) AS total,
        dictGetOrDefault('taxi_zone_dictionary','Borough', toUInt64(pickup_nyct2010_gid), 'Unknown') AS borough_name
      FROM trips
      WHERE dropoff_nyct2010_gid = 132 OR dropoff_nyct2010_gid = 138
      GROUP BY borough_name
      ORDER BY total DESC
      ```

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

      ```response theme={null}
      ┌─total─┬─borough_name──┐
      │ 23683 │ Unknown       │
      │  7053 │ Manhattan     │
      │  6828 │ Brooklyn      │
      │  4458 │ Queens        │
      │  2670 │ Bronx         │
      │   554 │ Staten Island │
      │    53 │ EWR           │
      └───────┴───────────────┘

      7 rows in set. Elapsed: 0.019 sec. Processed 2.00 million rows, 4.00 MB (105.70 million rows/s., 211.40 MB/s.)
      ```
    </Step>

    <Step title="Выполнить JOIN" id="perform-a-join">
      Наконец, напишите несколько запросов, которые выполняют JOIN словаря `taxi_zone_dictionary` с вашей таблицей `trips`.

      Начните с простого `JOIN`, работающего аналогично предыдущему запросу по аэропортам:

      ```sql theme={null}
      SELECT
          count(1) AS total,
          Borough
      FROM trips
      JOIN taxi_zone_dictionary ON toUInt64(trips.pickup_nyct2010_gid) = taxi_zone_dictionary.LocationID
      WHERE dropoff_nyct2010_gid = 132 OR dropoff_nyct2010_gid = 138
      GROUP BY Borough
      ORDER BY total DESC
      ```

      Результат выглядит так же, как и для запроса с `dictGet`:

      ```response theme={null}
      ┌─total─┬─Borough───────┐
      │  7053 │ Manhattan     │
      │  6828 │ Brooklyn      │
      │  4458 │ Queens        │
      │  2670 │ Bronx         │
      │   554 │ Staten Island │
      │    53 │ EWR           │
      └───────┴───────────────┘

      6 rows in set. Elapsed: 0.034 sec. Processed 2.00 million rows, 4.00 MB (59.14 million rows/s., 118.29 MB/s.)
      ```

      <Note>
        Обратите внимание, что результат приведённого выше запроса с `JOIN` совпадает с результатом предыдущего запроса, в котором использовалась функция `dictGetOrDefault` (за исключением того, что значения `Unknown` в него не попали).
        На самом деле под капотом ClickHouse вызывает функцию `dictGet` для словаря `taxi_zone_dictionary`, однако синтаксис `JOIN` привычнее для SQL-разработчиков.
      </Note>

      Этот запрос возвращает строки для 1000 поездок с наибольшей суммой чаевых, а затем выполняет внутреннее соединение (INNER JOIN) каждой строки со словарём:

      ```sql theme={null}
      SELECT *
      FROM trips
      JOIN taxi_zone_dictionary
      ON trips.dropoff_nyct2010_gid = taxi_zone_dictionary.LocationID
      WHERE tip_amount > 0
      ORDER BY tip_amount DESC
      LIMIT 1000
      ```

      <Tip>
        Вместо SELECT \* лучше перечислять только те столбцы, которые действительно нужны запросу. Поскольку ClickHouse хранит данные по столбцам, чем меньше столбцов вы выбираете, тем меньше данных приходится читать, распаковывать и обрабатывать.
      </Tip>
    </Step>
  </Steps>

  ## Дальнейшие шаги

  Подробнее о ClickHouse читайте в следующих разделах документации:

  * [Введение в первичные индексы в ClickHouse](/ru/guides/clickhouse/data-modelling/sparse-primary-indexes): узнайте, как ClickHouse использует разреженные первичные индексы для эффективного поиска нужных данных при выполнении запросов.
  * [Интеграция внешнего источника данных](/ru/integrations/home): ознакомьтесь с вариантами интеграции источников данных, включая файлы, Kafka, PostgreSQL, конвейеры данных и многое другое.
  * [Визуализация данных в ClickHouse](/ru/integrations/connectors/data-visualization/index): подключите к ClickHouse свой любимый инструмент визуализации или BI.
  * [Справочник по ClickHouse SQL](/ru/reference/home): изучите SQL-функции ClickHouse для преобразования, обработки и анализа данных.
</View>

<View title="С открытым исходным кодом">
  Набор данных о такси Нью-Йорка содержит более 3 миллиардов поездок на такси и автомобилях по вызову (Uber, Lyft и др.), начавшихся в Нью-Йорке начиная с 2009 года. В этом руководстве «Начало работы» используется выборка из 3 млн строк.

  Полный набор данных можно получить несколькими способами:

  * вставить данные напрямую в ClickHouse Cloud из S3 или GCS
  * скачать подготовленные партиции
  * Также можно выполнять запросы к полному набору данных в нашей демонстрационной среде по адресу [sql.clickhouse.com](https://sql.clickhouse.com/?query=U0VMRUNUIGNvdW50KCkgRlJPTSBueWNfdGF4aS50cmlwcw\&chart=eyJ0eXBlIjoibGluZSIsImNvbmZpZyI6eyJ0aXRsZSI6IlRlbXBlcmF0dXJlIGJ5IGNvdW50cnkgYW5kIHllYXIiLCJ4YXhpcyI6InllYXIiLCJ5YXhpcyI6ImNvdW50KCkiLCJzZXJpZXMiOiJDQVNUKHBhc3Nlbmdlcl9jb3VudCwgJ1N0cmluZycpIn19).

  <Note>
    Приведённые ниже примеры запросов были выполнены на инстансе ClickHouse Cloud уровня **Production**. Подробнее см. в разделе
    ["Характеристики Песочницы ClickHouse"](/ru/get-started/sample-datasets/playground#specifications).
  </Note>

  ## Создайте таблицу trips

  Для начала создайте таблицу для поездок на такси:

  ```sql theme={null}

  CREATE DATABASE nyc_taxi;

  CREATE TABLE nyc_taxi.trips_small (
      trip_id             UInt32,
      pickup_datetime     DateTime,
      dropoff_datetime    DateTime,
      pickup_longitude    Nullable(Float64),
      pickup_latitude     Nullable(Float64),
      dropoff_longitude   Nullable(Float64),
      dropoff_latitude    Nullable(Float64),
      passenger_count     UInt8,
      trip_distance       Float32,
      fare_amount         Float32,
      extra               Float32,
      tip_amount          Float32,
      tolls_amount        Float32,
      total_amount        Float32,
      payment_type        Enum('CSH' = 1, 'CRE' = 2, 'NOC' = 3, 'DIS' = 4, 'UNK' = 5),
      pickup_ntaname      LowCardinality(String),
      dropoff_ntaname     LowCardinality(String)
  )
  ENGINE = MergeTree
  PRIMARY KEY (pickup_datetime, dropoff_datetime);
  ```

  ## Загрузка данных напрямую из объектного хранилища

  Для знакомства с данными пользователи могут загрузить их небольшую часть (3 миллиона строк). Данные хранятся в файлах в формате TSV в объектном хранилище, откуда их легко потоково загрузить в
  ClickHouse Cloud с помощью табличной функции `s3`.

  Одни и те же данные хранятся и в S3, и в GCS, поэтому можно выбрать любую вкладку.

  <Tabs>
    <Tab title="S3">
      Следующая команда в потоковом режиме загружает три файла из S3 бакета в таблицу `trips_small` (синтаксис `{0..2}` — это шаблон подстановки для значений 0, 1 и 2):

      ```sql theme={null}
      INSERT INTO nyc_taxi.trips_small
      SELECT
          trip_id,
          pickup_datetime,
          dropoff_datetime,
          pickup_longitude,
          pickup_latitude,
          dropoff_longitude,
          dropoff_latitude,
          passenger_count,
          trip_distance,
          fare_amount,
          extra,
          tip_amount,
          tolls_amount,
          total_amount,
          payment_type,
          pickup_ntaname,
          dropoff_ntaname
      FROM s3(
          'https://datasets-documentation.s3.eu-west-3.amazonaws.com/nyc-taxi/trips_{0..2}.gz',
          'TabSeparatedWithNames'
      );
      ```
    </Tab>

    <Tab title="GCS">
      Следующая команда в потоковом режиме загружает три файла из бакета GCS в таблицу `trips` (синтаксис `{0..2}` — это шаблон подстановки для значений 0, 1 и 2):

      ```sql theme={null}
      INSERT INTO nyc_taxi.trips_small
      SELECT
          trip_id,
          pickup_datetime,
          dropoff_datetime,
          pickup_longitude,
          pickup_latitude,
          dropoff_longitude,
          dropoff_latitude,
          passenger_count,
          trip_distance,
          fare_amount,
          extra,
          tip_amount,
          tolls_amount,
          total_amount,
          payment_type,
          pickup_ntaname,
          dropoff_ntaname
      FROM gcs(
          'https://storage.googleapis.com/clickhouse-public-datasets/nyc-taxi/trips_{0..2}.gz',
          'TabSeparatedWithNames'
      );
      ```
    </Tab>
  </Tabs>

  ## Примеры запросов

  Следующие запросы выполняются на выборке, описанной выше. Вы можете запускать эти примеры запросов на полном наборе данных в [sql.clickhouse.com](https://sql.clickhouse.com/?query=U0VMRUNUIGNvdW50KCkgRlJPTSBueWNfdGF4aS50cmlwcw\&chart=eyJ0eXBlIjoibGluZSIsImNvbmZpZyI6eyJ0aXRsZSI6IlRlbXBlcmF0dXJlIGJ5IGNvdW50cnkgYW5kIHllYXIiLCJ4YXhpcyI6InllYXIiLCJ5YXhpcyI6ImNvdW50KCkiLCJzZXJpZXMiOiJDQVNUKHBhc3Nlbmdlcl9jb3VudCwgJ1N0cmluZycpIn19), изменив приведённые ниже запросы так, чтобы они использовали таблицу `nyc_taxi.trips`.

  Давайте посмотрим, сколько строк было вставлено:

  <RunnableCode>
    ```sql theme={null}
    SELECT count()
    FROM nyc_taxi.trips_small;
    ```
  </RunnableCode>

  Каждый файл в формате TSV содержит около 1 млн строк, а всего в трёх файлах — 3,000,317 строк. Давайте посмотрим на несколько строк:

  <RunnableCode>
    ```sql theme={null}
    SELECT *
    FROM nyc_taxi.trips_small
    LIMIT 10;
    ```
  </RunnableCode>

  Обратите внимание: здесь есть столбцы с датами посадки и высадки, географическими координатами, сведениями о тарифах, районами Нью-Йорка и другими данными.

  Давайте выполним несколько запросов. Этот запрос показывает 10 районов, где посадки происходят чаще всего:

  <RunnableCode>
    ```sql theme={null}
    SELECT
       pickup_ntaname,
       count(*) AS count
    FROM nyc_taxi.trips_small WHERE pickup_ntaname != ''
    GROUP BY pickup_ntaname
    ORDER BY count DESC
    LIMIT 10;
    ```
  </RunnableCode>

  Этот запрос показывает среднюю стоимость поездки в зависимости от количества пассажиров:

  ```sql runnable view='chart' chart_config='eyJ0eXBlIjoiYmFyIiwiY29uZmlnIjp7InhheGlzIjoicGFzc2VuZ2VyX2NvdW50IiwieWF4aXMiOiJhdmcodG90YWxfYW1vdW50KSIsInRpdGxlIjoiQXZlcmFnZSBmYXJlIGJ5IHBhc3NlbmdlciBjb3VudCJ9fQ' theme={null}
  SELECT
     passenger_count,
     avg(total_amount)
  FROM nyc_taxi.trips_small
  WHERE passenger_count < 10
  GROUP BY passenger_count;
  ```

  Вот корреляция между количеством пассажиров и расстоянием поездки:

  ```sql runnable chart_config='eyJ0eXBlIjoiaG9yaXpvbnRhbCBiYXIiLCJjb25maWciOnsieGF4aXMiOiJwYXNzZW5nZXJfY291bnQiLCJ5YXhpcyI6ImRpc3RhbmNlIiwic2VyaWVzIjoiY291bnRyeSIsInRpdGxlIjoiQXZnIGZhcmUgYnkgcGFzc2VuZ2VyIGNvdW50In19' theme={null}
  SELECT
      passenger_count,
      avg(trip_distance) AS distance,
      count() AS c
  FROM nyc_taxi.trips_small
  GROUP BY passenger_count
  ORDER BY passenger_count ASC
  ```

  ## Скачивание подготовленных партиций

  <Note>
    В следующих шагах приведены сведения об исходном наборе данных и описан способ загрузки подготовленных партиций в среду самоуправляемого сервера ClickHouse.
  </Note>

  Описание набора данных и инструкции по его загрузке см. по ссылкам [https://github.com/toddwschneider/nyc-taxi-data](https://github.com/toddwschneider/nyc-taxi-data) и [http://tech.marksblogg.com/billion-nyc-taxi-rides-redshift.html](http://tech.marksblogg.com/billion-nyc-taxi-rides-redshift.html).

  В результате загрузки вы получите около 227 ГБ несжатых данных в CSV-файлах. При соединении 1 Гбит/с загрузка занимает около часа (параллельная загрузка с s3.amazonaws.com задействует не менее половины канала 1 Гбит/с).
  Некоторые файлы могут загрузиться не полностью. Проверьте размеры файлов и загрузите повторно те, что вызывают сомнения.

  ```bash theme={null}
  $ curl -O https://datasets.clickhouse.com/trips_mergetree/partitions/trips_mergetree.tar
  # Validate the checksum
  $ md5sum trips_mergetree.tar
  # Checksum should be equal to: f3b8d469b41d9a82da064ded7245d12c
  $ tar xvf trips_mergetree.tar -C /var/lib/clickhouse # path to ClickHouse data directory
  $ # check permissions of unpacked data, fix if required
  $ sudo service clickhouse-server restart
  $ clickhouse-client --query "select count(*) from datasets.trips_mergetree"
  ```

  <Info>
    Если вы будете выполнять приведённые ниже запросы, используйте полное имя таблицы — `datasets.trips_mergetree`.
  </Info>

  ## Результаты на одном сервере

  Q1:

  ```sql theme={null}
  SELECT cab_type, count(*) FROM trips_mergetree GROUP BY cab_type;
  ```

  0,490 секунды.

  Q2:

  ```sql theme={null}
  SELECT passenger_count, avg(total_amount) FROM trips_mergetree GROUP BY passenger_count;
  ```

  1,224 секунды.

  Q3:

  ```sql theme={null}
  SELECT passenger_count, toYear(pickup_date) AS year, count(*) FROM trips_mergetree GROUP BY passenger_count, year;
  ```

  2,104 секунды.

  Q4:

  ```sql theme={null}
  SELECT passenger_count, toYear(pickup_date) AS year, round(trip_distance) AS distance, count(*)
  FROM trips_mergetree
  GROUP BY passenger_count, year, distance
  ORDER BY year, count(*) DESC;
  ```

  3,593 секунды.

  Использовался следующий сервер:

  Два процессора Intel(R) Xeon(R) CPU E5-2650 v2 @ 2.60GHz, всего 16 физических ядер, 128 GiB оперативной памяти, 8 жёстких дисков по 6 TB в аппаратном RAID-5

  Время выполнения — лучший результат из трёх запусков. Однако начиная со второго запуска запросы читают данные из кэша файловой системы. Никакого другого кэширования не происходит: при каждом запуске данные считываются и обрабатываются заново.

  Создание таблицы на трёх серверах:

  На каждом сервере:

  ```sql theme={null}
  CREATE TABLE default.trips_mergetree_third ( trip_id UInt32,  vendor_id Enum8('1' = 1, '2' = 2, 'CMT' = 3, 'VTS' = 4, 'DDS' = 5, 'B02512' = 10, 'B02598' = 11, 'B02617' = 12, 'B02682' = 13, 'B02764' = 14),  pickup_date Date,  pickup_datetime DateTime,  dropoff_date Date,  dropoff_datetime DateTime,  store_and_fwd_flag UInt8,  rate_code_id UInt8,  pickup_longitude Float64,  pickup_latitude Float64,  dropoff_longitude Float64,  dropoff_latitude Float64,  passenger_count UInt8,  trip_distance Float64,  fare_amount Float32,  extra Float32,  mta_tax Float32,  tip_amount Float32,  tolls_amount Float32,  ehail_fee Float32,  improvement_surcharge Float32,  total_amount Float32,  payment_type_ Enum8('UNK' = 0, 'CSH' = 1, 'CRE' = 2, 'NOC' = 3, 'DIS' = 4),  trip_type UInt8,  pickup FixedString(25),  dropoff FixedString(25),  cab_type Enum8('yellow' = 1, 'green' = 2, 'uber' = 3),  pickup_nyct2010_gid UInt8,  pickup_ctlabel Float32,  pickup_borocode UInt8,  pickup_boroname Enum8('' = 0, 'Manhattan' = 1, 'Bronx' = 2, 'Brooklyn' = 3, 'Queens' = 4, 'Staten Island' = 5),  pickup_ct2010 FixedString(6),  pickup_boroct2010 FixedString(7),  pickup_cdeligibil Enum8(' ' = 0, 'E' = 1, 'I' = 2),  pickup_ntacode FixedString(4),  pickup_ntaname Enum16('' = 0, 'Airport' = 1, 'Allerton-Pelham Gardens' = 2, 'Annadale-Huguenot-Prince\'s Bay-Eltingville' = 3, 'Arden Heights' = 4, 'Astoria' = 5, 'Auburndale' = 6, 'Baisley Park' = 7, 'Bath Beach' = 8, 'Battery Park City-Lower Manhattan' = 9, 'Bay Ridge' = 10, 'Bayside-Bayside Hills' = 11, 'Bedford' = 12, 'Bedford Park-Fordham North' = 13, 'Bellerose' = 14, 'Belmont' = 15, 'Bensonhurst East' = 16, 'Bensonhurst West' = 17, 'Borough Park' = 18, 'Breezy Point-Belle Harbor-Rockaway Park-Broad Channel' = 19, 'Briarwood-Jamaica Hills' = 20, 'Brighton Beach' = 21, 'Bronxdale' = 22, 'Brooklyn Heights-Cobble Hill' = 23, 'Brownsville' = 24, 'Bushwick North' = 25, 'Bushwick South' = 26, 'Cambria Heights' = 27, 'Canarsie' = 28, 'Carroll Gardens-Columbia Street-Red Hook' = 29, 'Central Harlem North-Polo Grounds' = 30, 'Central Harlem South' = 31, 'Charleston-Richmond Valley-Tottenville' = 32, 'Chinatown' = 33, 'Claremont-Bathgate' = 34, 'Clinton' = 35, 'Clinton Hill' = 36, 'Co-op City' = 37, 'College Point' = 38, 'Corona' = 39, 'Crotona Park East' = 40, 'Crown Heights North' = 41, 'Crown Heights South' = 42, 'Cypress Hills-City Line' = 43, 'DUMBO-Vinegar Hill-Downtown Brooklyn-Boerum Hill' = 44, 'Douglas Manor-Douglaston-Little Neck' = 45, 'Dyker Heights' = 46, 'East Concourse-Concourse Village' = 47, 'East Elmhurst' = 48, 'East Flatbush-Farragut' = 49, 'East Flushing' = 50, 'East Harlem North' = 51, 'East Harlem South' = 52, 'East New York' = 53, 'East New York (Pennsylvania Ave)' = 54, 'East Tremont' = 55, 'East Village' = 56, 'East Williamsburg' = 57, 'Eastchester-Edenwald-Baychester' = 58, 'Elmhurst' = 59, 'Elmhurst-Maspeth' = 60, 'Erasmus' = 61, 'Far Rockaway-Bayswater' = 62, 'Flatbush' = 63, 'Flatlands' = 64, 'Flushing' = 65, 'Fordham South' = 66, 'Forest Hills' = 67, 'Fort Greene' = 68, 'Fresh Meadows-Utopia' = 69, 'Ft. Totten-Bay Terrace-Clearview' = 70, 'Georgetown-Marine Park-Bergen Beach-Mill Basin' = 71, 'Glen Oaks-Floral Park-New Hyde Park' = 72, 'Glendale' = 73, 'Gramercy' = 74, 'Grasmere-Arrochar-Ft. Wadsworth' = 75, 'Gravesend' = 76, 'Great Kills' = 77, 'Greenpoint' = 78, 'Grymes Hill-Clifton-Fox Hills' = 79, 'Hamilton Heights' = 80, 'Hammels-Arverne-Edgemere' = 81, 'Highbridge' = 82, 'Hollis' = 83, 'Homecrest' = 84, 'Hudson Yards-Chelsea-Flatiron-Union Square' = 85, 'Hunters Point-Sunnyside-West Maspeth' = 86, 'Hunts Point' = 87, 'Jackson Heights' = 88, 'Jamaica' = 89, 'Jamaica Estates-Holliswood' = 90, 'Kensington-Ocean Parkway' = 91, 'Kew Gardens' = 92, 'Kew Gardens Hills' = 93, 'Kingsbridge Heights' = 94, 'Laurelton' = 95, 'Lenox Hill-Roosevelt Island' = 96, 'Lincoln Square' = 97, 'Lindenwood-Howard Beach' = 98, 'Longwood' = 99, 'Lower East Side' = 100, 'Madison' = 101, 'Manhattanville' = 102, 'Marble Hill-Inwood' = 103, 'Mariner\'s Harbor-Arlington-Port Ivory-Graniteville' = 104, 'Maspeth' = 105, 'Melrose South-Mott Haven North' = 106, 'Middle Village' = 107, 'Midtown-Midtown South' = 108, 'Midwood' = 109, 'Morningside Heights' = 110, 'Morrisania-Melrose' = 111, 'Mott Haven-Port Morris' = 112, 'Mount Hope' = 113, 'Murray Hill' = 114, 'Murray Hill-Kips Bay' = 115, 'New Brighton-Silver Lake' = 116, 'New Dorp-Midland Beach' = 117, 'New Springville-Bloomfield-Travis' = 118, 'North Corona' = 119, 'North Riverdale-Fieldston-Riverdale' = 120, 'North Side-South Side' = 121, 'Norwood' = 122, 'Oakland Gardens' = 123, 'Oakwood-Oakwood Beach' = 124, 'Ocean Hill' = 125, 'Ocean Parkway South' = 126, 'Old Astoria' = 127, 'Old Town-Dongan Hills-South Beach' = 128, 'Ozone Park' = 129, 'Park Slope-Gowanus' = 130, 'Parkchester' = 131, 'Pelham Bay-Country Club-City Island' = 132, 'Pelham Parkway' = 133, 'Pomonok-Flushing Heights-Hillcrest' = 134, 'Port Richmond' = 135, 'Prospect Heights' = 136, 'Prospect Lefferts Gardens-Wingate' = 137, 'Queens Village' = 138, 'Queensboro Hill' = 139, 'Queensbridge-Ravenswood-Long Island City' = 140, 'Rego Park' = 141, 'Richmond Hill' = 142, 'Ridgewood' = 143, 'Rikers Island' = 144, 'Rosedale' = 145, 'Rossville-Woodrow' = 146, 'Rugby-Remsen Village' = 147, 'Schuylerville-Throgs Neck-Edgewater Park' = 148, 'Seagate-Coney Island' = 149, 'Sheepshead Bay-Gerritsen Beach-Manhattan Beach' = 150, 'SoHo-TriBeCa-Civic Center-Little Italy' = 151, 'Soundview-Bruckner' = 152, 'Soundview-Castle Hill-Clason Point-Harding Park' = 153, 'South Jamaica' = 154, 'South Ozone Park' = 155, 'Springfield Gardens North' = 156, 'Springfield Gardens South-Brookville' = 157, 'Spuyten Duyvil-Kingsbridge' = 158, 'St. Albans' = 159, 'Stapleton-Rosebank' = 160, 'Starrett City' = 161, 'Steinway' = 162, 'Stuyvesant Heights' = 163, 'Stuyvesant Town-Cooper Village' = 164, 'Sunset Park East' = 165, 'Sunset Park West' = 166, 'Todt Hill-Emerson Hill-Heartland Village-Lighthouse Hill' = 167, 'Turtle Bay-East Midtown' = 168, 'University Heights-Morris Heights' = 169, 'Upper East Side-Carnegie Hill' = 170, 'Upper West Side' = 171, 'Van Cortlandt Village' = 172, 'Van Nest-Morris Park-Westchester Square' = 173, 'Washington Heights North' = 174, 'Washington Heights South' = 175, 'West Brighton' = 176, 'West Concourse' = 177, 'West Farms-Bronx River' = 178, 'West New Brighton-New Brighton-St. George' = 179, 'West Village' = 180, 'Westchester-Unionport' = 181, 'Westerleigh' = 182, 'Whitestone' = 183, 'Williamsbridge-Olinville' = 184, 'Williamsburg' = 185, 'Windsor Terrace' = 186, 'Woodhaven' = 187, 'Woodlawn-Wakefield' = 188, 'Woodside' = 189, 'Yorkville' = 190, 'park-cemetery-etc-Bronx' = 191, 'park-cemetery-etc-Brooklyn' = 192, 'park-cemetery-etc-Manhattan' = 193, 'park-cemetery-etc-Queens' = 194, 'park-cemetery-etc-Staten Island' = 195),  pickup_puma UInt16,  dropoff_nyct2010_gid UInt8,  dropoff_ctlabel Float32,  dropoff_borocode UInt8,  dropoff_boroname Enum8('' = 0, 'Manhattan' = 1, 'Bronx' = 2, 'Brooklyn' = 3, 'Queens' = 4, 'Staten Island' = 5),  dropoff_ct2010 FixedString(6),  dropoff_boroct2010 FixedString(7),  dropoff_cdeligibil Enum8(' ' = 0, 'E' = 1, 'I' = 2),  dropoff_ntacode FixedString(4),  dropoff_ntaname Enum16('' = 0, 'Airport' = 1, 'Allerton-Pelham Gardens' = 2, 'Annadale-Huguenot-Prince\'s Bay-Eltingville' = 3, 'Arden Heights' = 4, 'Astoria' = 5, 'Auburndale' = 6, 'Baisley Park' = 7, 'Bath Beach' = 8, 'Battery Park City-Lower Manhattan' = 9, 'Bay Ridge' = 10, 'Bayside-Bayside Hills' = 11, 'Bedford' = 12, 'Bedford Park-Fordham North' = 13, 'Bellerose' = 14, 'Belmont' = 15, 'Bensonhurst East' = 16, 'Bensonhurst West' = 17, 'Borough Park' = 18, 'Breezy Point-Belle Harbor-Rockaway Park-Broad Channel' = 19, 'Briarwood-Jamaica Hills' = 20, 'Brighton Beach' = 21, 'Bronxdale' = 22, 'Brooklyn Heights-Cobble Hill' = 23, 'Brownsville' = 24, 'Bushwick North' = 25, 'Bushwick South' = 26, 'Cambria Heights' = 27, 'Canarsie' = 28, 'Carroll Gardens-Columbia Street-Red Hook' = 29, 'Central Harlem North-Polo Grounds' = 30, 'Central Harlem South' = 31, 'Charleston-Richmond Valley-Tottenville' = 32, 'Chinatown' = 33, 'Claremont-Bathgate' = 34, 'Clinton' = 35, 'Clinton Hill' = 36, 'Co-op City' = 37, 'College Point' = 38, 'Corona' = 39, 'Crotona Park East' = 40, 'Crown Heights North' = 41, 'Crown Heights South' = 42, 'Cypress Hills-City Line' = 43, 'DUMBO-Vinegar Hill-Downtown Brooklyn-Boerum Hill' = 44, 'Douglas Manor-Douglaston-Little Neck' = 45, 'Dyker Heights' = 46, 'East Concourse-Concourse Village' = 47, 'East Elmhurst' = 48, 'East Flatbush-Farragut' = 49, 'East Flushing' = 50, 'East Harlem North' = 51, 'East Harlem South' = 52, 'East New York' = 53, 'East New York (Pennsylvania Ave)' = 54, 'East Tremont' = 55, 'East Village' = 56, 'East Williamsburg' = 57, 'Eastchester-Edenwald-Baychester' = 58, 'Elmhurst' = 59, 'Elmhurst-Maspeth' = 60, 'Erasmus' = 61, 'Far Rockaway-Bayswater' = 62, 'Flatbush' = 63, 'Flatlands' = 64, 'Flushing' = 65, 'Fordham South' = 66, 'Forest Hills' = 67, 'Fort Greene' = 68, 'Fresh Meadows-Utopia' = 69, 'Ft. Totten-Bay Terrace-Clearview' = 70, 'Georgetown-Marine Park-Bergen Beach-Mill Basin' = 71, 'Glen Oaks-Floral Park-New Hyde Park' = 72, 'Glendale' = 73, 'Gramercy' = 74, 'Grasmere-Arrochar-Ft. Wadsworth' = 75, 'Gravesend' = 76, 'Great Kills' = 77, 'Greenpoint' = 78, 'Grymes Hill-Clifton-Fox Hills' = 79, 'Hamilton Heights' = 80, 'Hammels-Arverne-Edgemere' = 81, 'Highbridge' = 82, 'Hollis' = 83, 'Homecrest' = 84, 'Hudson Yards-Chelsea-Flatiron-Union Square' = 85, 'Hunters Point-Sunnyside-West Maspeth' = 86, 'Hunts Point' = 87, 'Jackson Heights' = 88, 'Jamaica' = 89, 'Jamaica Estates-Holliswood' = 90, 'Kensington-Ocean Parkway' = 91, 'Kew Gardens' = 92, 'Kew Gardens Hills' = 93, 'Kingsbridge Heights' = 94, 'Laurelton' = 95, 'Lenox Hill-Roosevelt Island' = 96, 'Lincoln Square' = 97, 'Lindenwood-Howard Beach' = 98, 'Longwood' = 99, 'Lower East Side' = 100, 'Madison' = 101, 'Manhattanville' = 102, 'Marble Hill-Inwood' = 103, 'Mariner\'s Harbor-Arlington-Port Ivory-Graniteville' = 104, 'Maspeth' = 105, 'Melrose South-Mott Haven North' = 106, 'Middle Village' = 107, 'Midtown-Midtown South' = 108, 'Midwood' = 109, 'Morningside Heights' = 110, 'Morrisania-Melrose' = 111, 'Mott Haven-Port Morris' = 112, 'Mount Hope' = 113, 'Murray Hill' = 114, 'Murray Hill-Kips Bay' = 115, 'New Brighton-Silver Lake' = 116, 'New Dorp-Midland Beach' = 117, 'New Springville-Bloomfield-Travis' = 118, 'North Corona' = 119, 'North Riverdale-Fieldston-Riverdale' = 120, 'North Side-South Side' = 121, 'Norwood' = 122, 'Oakland Gardens' = 123, 'Oakwood-Oakwood Beach' = 124, 'Ocean Hill' = 125, 'Ocean Parkway South' = 126, 'Old Astoria' = 127, 'Old Town-Dongan Hills-South Beach' = 128, 'Ozone Park' = 129, 'Park Slope-Gowanus' = 130, 'Parkchester' = 131, 'Pelham Bay-Country Club-City Island' = 132, 'Pelham Parkway' = 133, 'Pomonok-Flushing Heights-Hillcrest' = 134, 'Port Richmond' = 135, 'Prospect Heights' = 136, 'Prospect Lefferts Gardens-Wingate' = 137, 'Queens Village' = 138, 'Queensboro Hill' = 139, 'Queensbridge-Ravenswood-Long Island City' = 140, 'Rego Park' = 141, 'Richmond Hill' = 142, 'Ridgewood' = 143, 'Rikers Island' = 144, 'Rosedale' = 145, 'Rossville-Woodrow' = 146, 'Rugby-Remsen Village' = 147, 'Schuylerville-Throgs Neck-Edgewater Park' = 148, 'Seagate-Coney Island' = 149, 'Sheepshead Bay-Gerritsen Beach-Manhattan Beach' = 150, 'SoHo-TriBeCa-Civic Center-Little Italy' = 151, 'Soundview-Bruckner' = 152, 'Soundview-Castle Hill-Clason Point-Harding Park' = 153, 'South Jamaica' = 154, 'South Ozone Park' = 155, 'Springfield Gardens North' = 156, 'Springfield Gardens South-Brookville' = 157, 'Spuyten Duyvil-Kingsbridge' = 158, 'St. Albans' = 159, 'Stapleton-Rosebank' = 160, 'Starrett City' = 161, 'Steinway' = 162, 'Stuyvesant Heights' = 163, 'Stuyvesant Town-Cooper Village' = 164, 'Sunset Park East' = 165, 'Sunset Park West' = 166, 'Todt Hill-Emerson Hill-Heartland Village-Lighthouse Hill' = 167, 'Turtle Bay-East Midtown' = 168, 'University Heights-Morris Heights' = 169, 'Upper East Side-Carnegie Hill' = 170, 'Upper West Side' = 171, 'Van Cortlandt Village' = 172, 'Van Nest-Morris Park-Westchester Square' = 173, 'Washington Heights North' = 174, 'Washington Heights South' = 175, 'West Brighton' = 176, 'West Concourse' = 177, 'West Farms-Bronx River' = 178, 'West New Brighton-New Brighton-St. George' = 179, 'West Village' = 180, 'Westchester-Unionport' = 181, 'Westerleigh' = 182, 'Whitestone' = 183, 'Williamsbridge-Olinville' = 184, 'Williamsburg' = 185, 'Windsor Terrace' = 186, 'Woodhaven' = 187, 'Woodlawn-Wakefield' = 188, 'Woodside' = 189, 'Yorkville' = 190, 'park-cemetery-etc-Bronx' = 191, 'park-cemetery-etc-Brooklyn' = 192, 'park-cemetery-etc-Manhattan' = 193, 'park-cemetery-etc-Queens' = 194, 'park-cemetery-etc-Staten Island' = 195),  dropoff_puma UInt16) ENGINE = MergeTree(pickup_date, pickup_datetime, 8192);
  ```

  На исходном сервере:

  ```sql theme={null}
  CREATE TABLE trips_mergetree_x3 AS trips_mergetree_third ENGINE = Distributed(perftest, default, trips_mergetree_third, rand());
  ```

  Следующий запрос перераспределяет данные:

  ```sql theme={null}
  INSERT INTO trips_mergetree_x3 SELECT * FROM trips_mergetree;
  ```

  Это занимает 2454 секунды.

  На трёх серверах:

  Q1: 0.212 секунды.
  Q2: 0.438 секунды.
  Q3: 0.733 секунды.
  Q4: 1.241 секунды.

  Ничего удивительного: запросы масштабируются линейно.

  Также приводим результаты, полученные на кластере из 140 серверов:

  Q1: 0.028 с
  Q2: 0.043 с
  Q3: 0.051 с
  Q4: 0.072 с

  В данном случае время выполнения запроса определяется прежде всего сетевой задержкой.
  Мы выполняли запросы с клиента, расположенного в другом датацентре, нежели кластер, из-за чего задержка увеличивалась примерно на 20 мс.

  ## Итоги

  | серверы | Q1 | Q2 | Q3 | Q4 |
  | - | - | - | - | - |
  | 1, E5-2650v2 | 0.490 | 1.224 | 2.104 | 3.593 |
  | 3, E5-2650v2 | 0.212 | 0.438 | 0.733 | 1.241 |
  | 1, AWS c5n.4xlarge | 0.249 | 1.279 | 1.738 | 3.527 |
  | 1, AWS c5n.9xlarge | 0.130 | 0.584 | 0.777 | 1.811 |
  | 3, AWS c5n.9xlarge | 0.057 | 0.231 | 0.285 | 0.641 |
  | 140, E5-2650v2 | 0.028 | 0.043 | 0.051 | 0.072 |
</View>
