{
    "version": "https:\/\/jsonfeed.org\/version\/1.1",
    "title": "Yuriy Gavrilov: posts tagged SQL",
    "_rss_description": "Welcome to my personal place for love, peace and happiness 🤖 Yuiry Gavrilov",
    "_rss_language": "en",
    "_itunes_email": "yvgavrilov@gmail.com",
    "_itunes_categories_xml": "",
    "_itunes_image": "https:\/\/gavrilov.info\/pictures\/userpic\/userpic-square@2x.jpg?1643451008",
    "_itunes_explicit": "no",
    "home_page_url": "https:\/\/gavrilov.info\/tags\/sql\/",
    "feed_url": "https:\/\/gavrilov.info\/tags\/sql\/json\/",
    "icon": "https:\/\/gavrilov.info\/pictures\/userpic\/userpic@2x.jpg?1643451008",
    "authors": [
        {
            "name": "Yuriy Gavrilov - B[u]g - for charity.gavrilov.eth",
            "url": "https:\/\/gavrilov.info\/",
            "avatar": "https:\/\/gavrilov.info\/pictures\/userpic\/userpic@2x.jpg?1643451008"
        }
    ],
    "items": [
        {
            "id": "351",
            "url": "https:\/\/gavrilov.info\/all\/trino-otp-verifikaciya-zaprosov-v-bi-paranoya-mode-true\/",
            "title": "Trino + OTP верификация запросов в bi – параноя.mode = true",
            "content_html": "<p>Представьте: дашборд в BI видят все сотрудники. Закрыть его нельзя — так исторически сложилось. Но данные нужно скрыть. Решение — динамическая фильтрация на уровне SQL: пользователь вводит логин и одноразовый код (TOTP), а запрос показывает только те строки, которые ему разрешены.<\/p>\n<p>Это не полноценная система безопасности, но это работающий барьер, который можно внедрить без изменения BI-системы. В статье — пошаговая инструкция: от демо-таблиц до настройки прав в Trino, генерации QR-кодов и увеличения окна действия OTP.<\/p>\n<hr \/>\n<h3>Архитектура<\/h3>\n<ul>\n<li><b>`catalog.schema.dashboard_users`<\/b> — справочник сотрудников: логин, имя, отдел, должность, руководитель, TOTP-секрет, флаг активности.<\/li>\n<li><b>`catalog.schema.dashboard_data`<\/b> — данные дашборда. У каждой строки есть политика доступа: `ALL`, `OWNER`, `DEPARTMENT`, `MANAGER`, `DIRECTOR`.<\/li>\n<li><b>Функция `verify_totp`<\/b> — Python UDF, проверяет OTP-код по секрету и текущему времени.<\/li>\n<li><b>Итоговый запрос<\/b> — аутентифицирует пользователя и фильтрует строки по атрибутам.<\/li>\n<\/ul>\n<p>Пользователь вводит логин и OTP как параметры BI. Если они верны — получает свои данные. Если нет — пустой результат.<\/p>\n<hr \/>\n<h3>Шаг 1. Демо-таблицы<\/h3>\n<p>Создадим две таблицы в вымышленной схеме `catalog.schema`.<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">CREATE TABLE catalog.schema.dashboard_users (\n    login         VARCHAR,\n    employee_name VARCHAR,\n    department    VARCHAR,\n    position_name VARCHAR,\n    manager_login VARCHAR,\n    totp_secret   VARCHAR,\n    is_active     BOOLEAN\n);\n\nINSERT INTO catalog.schema.dashboard_users VALUES\n    ('vasya', 'Василий', 'sales',    'manager',  'ivan',  'JBSWY3DPEHPK3PXP', true),\n    ('petya', 'Пётр',    'sales',    'analyst',  'vasya', 'KRSXG5CTMVRXEZLU', true),\n    ('masha', 'Мария',   'finance',  'analyst',  'ivan',  'NB2W45DFOIZA',     true),\n    ('ivan',  'Иван',    'management','director', null,   'MFRGGZDFMZTWQ2LK', true);\n\nCREATE TABLE catalog.schema.dashboard_data (\n    row_id        BIGINT,\n    row_title     VARCHAR,\n    amount        DECIMAL(12,2),\n    owner_login   VARCHAR,\n    department    VARCHAR,\n    manager_login VARCHAR,\n    access_policy VARCHAR\n);\n\nINSERT INTO catalog.schema.dashboard_data VALUES\n    (1, 'Личная строка Васи',       1000.00, 'vasya', 'sales',   'ivan',  'OWNER'),\n    (2, 'Личная строка Пети',       2000.00, 'petya', 'sales',   'vasya', 'OWNER'),\n    (3, 'Общая строка отдела Sales',3000.00, null,    'sales',   null,    'DEPARTMENT'),\n    (4, 'Общая строка Finance',     4000.00, null,    'finance', null,    'DEPARTMENT'),\n    (5, 'Строка команды Васи',      5000.00, 'petya', 'sales',   'vasya', 'MANAGER'),\n    (6, 'Строка команды Ивана',     6000.00, 'masha', 'finance', 'ivan',  'MANAGER'),\n    (7, 'Директорская строка',      7000.00, null,    null,      null,    'DIRECTOR'),\n    (8, 'Публичная строка',         8000.00, null,    null,      null,    'ALL');<\/code><\/pre><hr \/>\n<p>Либо можно сгенерировать коды разово прямо в запросе.<\/p>\n<h3>Шаг 1.1. Массовая генерация TOTP-секретов и QR-кодов<\/h3>\n<p>Когда пользователей много, вручную придумывать Base32-секреты неудобно. Можно сгенерировать их прямо в Trino при вставке в таблицу `dashboard_users`. Ниже — готовый блок для демо и тестов.<\/p>\n<blockquote>\n<p><b>Важно:<\/b> `random()` в Trino не является криптостойким генератором. Для продакшена секреты лучше генерировать вне Trino (например, через `openssl rand -base64 20` или Python `pyotp.random_base32()`) и вставлять уже готовыми. Для демонстрации и параноидального режима «лишь бы закрыть дашборд» этого достаточно.<\/p>\n<\/blockquote>\n<h4>Вставка пользователей с автоматической генерацией секретов<\/h4>\n<pre class=\"e2-text-code\"><code class=\"\">INSERT INTO catalog.schema.dashboard_users (\n    login,\n    employee_name,\n    department,\n    position_name,\n    manager_login,\n    totp_secret,\n    is_active\n)\nSELECT\n    login,\n    employee_name,\n    department,\n    position_name,\n    manager_login,\n\n    -- Генерация Base32-секрета длиной 16 символов (80 бит)\n    array_join(\n        transform(\n            sequence(1, 16),\n            i -&gt; substr(\n                'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567',\n                CAST(floor(random() * 32) + 1 AS INTEGER),\n                1\n            )\n        ),\n        ''\n    ) AS totp_secret,\n\n    true AS is_active\nFROM (\n    VALUES\n        ('vasya', 'Василий', 'sales',     'manager',  'ivan'),\n        ('petya', 'Пётр',    'sales',     'analyst',  'vasya'),\n        ('masha', 'Мария',   'finance',   'analyst',  'ivan'),\n        ('ivan',  'Иван',    'management','director', CAST(NULL AS VARCHAR))\n) AS src (\n    login,\n    employee_name,\n    department,\n    position_name,\n    manager_login\n);<\/code><\/pre><h4>Как получить строку для QR-кода<\/h4>\n<p>После вставки можно сразу сформировать URI формата `otpauth:\/\/`, который останется только превратить в QR-код любым генератором:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">SELECT\n    login,\n    totp_secret,\n    'otpauth:\/\/totp\/DemoBI:' || login\n        || '?secret=' || totp_secret\n        || '&amp;issuer=DemoBI' AS otpauth_uri\nFROM catalog.schema.dashboard_users;<\/code><\/pre><p>Пример результата:<\/p>\n<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\" class=\"e2-text-table\">\n<tr>\n<td style=\"text-align: center\">login<\/td>\n<td style=\"text-align: center\">totp_secret<\/td>\n<td style=\"text-align: center\">otpauth_uri<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\">vasya<\/td>\n<td style=\"text-align: center\">JBSWY3DPEHPK3PXP<\/td>\n<td style=\"text-align: center\">otpauth:\/\/totp\/DemoBI:vasya?secret=JBSWY3DPEHPK3PXP&issuer=DemoBI<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\">petya<\/td>\n<td style=\"text-align: center\">KRSXG5CTMVRXEZLU<\/td>\n<td style=\"text-align: center\">otpauth:\/\/totp\/DemoBI:petya?secret=KRSXG5CTMVRXEZLU&issuer=DemoBI<\/td>\n<\/tr>\n<\/table>\n<p>Эту строку можно вставить в любой генератор QR-кодов (онлайн или офлайн), а затем отсканировать приложением:<\/p>\n<ul>\n<li>Google Authenticator,<\/li>\n<li>Yandex ID,<\/li>\n<li>KeePass (с плагином TOTP),<\/li>\n<li>и любым другим менеджером паролей, поддерживающим TOTP.<\/li>\n<\/ul>\n<h4>Настройка длины секрета<\/h4>\n<ul>\n<li>`sequence(1, 16)` — 16 символов Base32 = 80 бит. Этого достаточно для большинства случаев и именно такую длину часто используют по умолчанию.<\/li>\n<li>Если хотите более длинный секрет, замените `16` на `32`. Тогда получится 160 бит.<\/li>\n<li>Алфавит `’ABCDEFGHIJKLMNOPQRSTUVWXYZ234567’` — это стандартный Base32-алфавит (без цифр 0, 1, 8, 9). Менять его не нужно.<\/li>\n<\/ul>\n<h4>Если нужен детерминированный секрет (не рекомендуется)<\/h4>\n<p>Иногда для тестов хотят, чтобы секрет зависел от логина. Можно использовать хеш:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">substr(\n    upper(to_base32(from_utf8(login || 'some_salt'))),\n    1, 16\n) AS totp_secret<\/code><\/pre><p>Но это <b>небезопасно<\/b>: зная логин и соль, можно вычислить секрет. Для реальной эксплуатации используйте случайную генерацию вне Trino.<\/p>\n<hr \/>\n<h3>Шаг 2. Inline-функция для тестов в DBeaver<\/h3>\n<p>В DBeaver можно использовать `WITH FUNCTION` — Python-код прямо в запросе. Это удобно для отладки, но <b>не работает в BI<\/b>, потому что BI схлопывает запрос в одну строку и ломает форматирование Python.<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">WITH FUNCTION verify_totp(secret VARCHAR, code VARCHAR, time_step BIGINT)\nRETURNS BOOLEAN\nLANGUAGE PYTHON\nWITH (handler = 'verify_handler')\nAS $$\nimport hmac, hashlib, struct, base64\n\ndef verify_handler(secret_b32, code, time_step):\n    if secret_b32 is None or code is None or time_step is None:\n        return False\n\n    code = str(code).strip()\n    if not code.isdigit() or len(code) != 6:\n        return False\n\n    secret = str(secret_b32).strip().replace(&quot; &quot;, &quot;&quot;).upper()\n    padding = &quot;=&quot; * ((8 - len(secret) % 8) % 8)\n\n    try:\n        key = base64.b32decode(secret + padding, casefold=True)\n    except Exception:\n        return False\n\n    current = int(time_step \/\/ 30)   # 30 секунд — стандарт TOTP\n    for offset in (-1, 0, 1):\n        counter = current + offset\n        msg = struct.pack(&quot;&gt;Q&quot;, counter)\n        digest = hmac.new(key, msg, hashlib.sha1).digest()\n        o = digest[19] &amp; 15\n        expected = (struct.unpack(&quot;&gt;I&quot;, digest[o:o+4])[0] &amp; 0x7fffffff) % 1000000\n        expected_code = f&quot;{expected:06d}&quot;\n        if hmac.compare_digest(expected_code, code):\n            return True\n    return False\n$$\nSELECT verify_totp('JBSWY3DPEHPK3PXP', '123456', CAST(to_unixtime(now()) AS BIGINT));<\/code><\/pre><p><b>Важно:<\/b> тело функции должно начинаться с новой строки после `$$`. Если BI отправляет всё в одну строку — будет ошибка `Function definition must start with a newline after opening quotes`.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/Screenshot-from-2026-09-11-00-28-13.png\" width=\"1024\" height=\"691\" alt=\"\" \/>\n<\/div>\n<hr \/>\n<h3>Шаг 3. Регистрация функции администратором<\/h3>\n<p>Чтобы BI-запросы не содержали Python-код, функцию нужно создать один раз на стороне Trino. Это делает администратор или пользователь с правами на создание функций.<\/p>\n<p>Сразу еще скажу, что регистрация не во всех каталогах работает, в iceberg например не работает, а вот в iceberg + rest надо проверять. Такая фича давно в плане, но не уверен готова ли. Можно регать функции в каталоге memory конектора, но это придется делать каждую загрузку.<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">DROP FUNCTION IF EXISTS catalog.schema.verify_totp(VARCHAR, VARCHAR, BIGINT);\n\nCREATE FUNCTION catalog.schema.verify_totp(\n    secret VARCHAR,\n    code VARCHAR,\n    time_step BIGINT\n)\nRETURNS BOOLEAN\nLANGUAGE PYTHON\nWITH (handler = 'verify_handler')\nAS $$\nimport hmac, hashlib, struct, base64\n\ndef verify_handler(secret_b32, code, time_step):\n    if secret_b32 is None or code is None or time_step is None:\n        return False\n\n    code = str(code).strip()\n    if not code.isdigit() or len(code) != 6:\n        return False\n\n    secret = str(secret_b32).strip().replace(&quot; &quot;, &quot;&quot;).upper()\n    padding = &quot;=&quot; * ((8 - len(secret) % 8) % 8)\n\n    try:\n        key = base64.b32decode(secret + padding, casefold=True)\n    except Exception:\n        return False\n\n    current = int(time_step \/\/ 30)\n    for offset in (-1, 0, 1):\n        counter = current + offset\n        msg = struct.pack(&quot;&gt;Q&quot;, counter)\n        digest = hmac.new(key, msg, hashlib.sha1).digest()\n        o = digest[19] &amp; 15\n        expected = (struct.unpack(&quot;&gt;I&quot;, digest[o:o+4])[0] &amp; 0x7fffffff) % 1000000\n        expected_code = f&quot;{expected:06d}&quot;\n        if hmac.compare_digest(expected_code, code):\n            return True\n    return False\n$$;<\/code><\/pre><p>Проверка:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">SELECT catalog.schema.verify_totp(\n    'JBSWY3DPEHPK3PXP',\n    '465617',\n    CAST(to_unixtime(now()) AS BIGINT)\n) AS is_valid;<\/code><\/pre><blockquote>\n<p><b>Примечание:<\/b> в вашей среде создание функции может быть не разрешено без настройки прав. Об этом — следующий шаг.<\/p>\n<\/blockquote>\n<hr \/>\n<h3>Шаг 4. Настройка прав доступа<\/h3>\n<p>Нужно разрешить группе <b>admins<\/b> создавать функции в схеме `catalog.schema`, а группе <b>bi_users<\/b> — только вызывать `verify_totp`. В Trino это делается через system access control.<\/p>\n<h4>Вариант A. File-based ACL<\/h4>\n<p>Файл `\/etc\/trino\/access-control.properties`:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">access-control.name=file\nsecurity.config-file=\/etc\/trino\/rules\/access-control.json\nsecurity.refresh-period=1m<\/code><\/pre><p>Файл `access-control.json`:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">{\n  &quot;catalogs&quot;: [\n    {\n      &quot;group&quot;: &quot;admins&quot;,\n      &quot;catalog&quot;: &quot;catalog&quot;,\n      &quot;allow&quot;: &quot;all&quot;\n    },\n    {\n      &quot;group&quot;: &quot;bi_users&quot;,\n      &quot;catalog&quot;: &quot;catalog&quot;,\n      &quot;allow&quot;: &quot;read-only&quot;\n    }\n  ],\n  &quot;schemas&quot;: [\n    {\n      &quot;group&quot;: &quot;admins&quot;,\n      &quot;catalog&quot;: &quot;catalog&quot;,\n      &quot;schema&quot;: &quot;schema&quot;,\n      &quot;owner&quot;: true\n    },\n    {\n      &quot;group&quot;: &quot;bi_users&quot;,\n      &quot;catalog&quot;: &quot;catalog&quot;,\n      &quot;schema&quot;: &quot;schema&quot;,\n      &quot;owner&quot;: false\n    }\n  ],\n  &quot;functions&quot;: [\n    {\n      &quot;group&quot;: &quot;admins&quot;,\n      &quot;catalog&quot;: &quot;catalog&quot;,\n      &quot;schema&quot;: &quot;schema&quot;,\n      &quot;function&quot;: &quot;.*&quot;,\n      &quot;privileges&quot;: [&quot;EXECUTE&quot;, &quot;GRANT_EXECUTE&quot;, &quot;OWNERSHIP&quot;]\n    },\n    {\n      &quot;group&quot;: &quot;bi_users&quot;,\n      &quot;catalog&quot;: &quot;catalog&quot;,\n      &quot;schema&quot;: &quot;schema&quot;,\n      &quot;function&quot;: &quot;verify_totp&quot;,\n      &quot;privileges&quot;: [&quot;EXECUTE&quot;]\n    }\n  ]\n}<\/code><\/pre><h4>Вариант B. OPA (Rego)<\/h4>\n<p>Если Trino использует OPA, политика может выглядеть так:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">package trino\n\ndefault allow := false\n\nis_admin {\n    input.context.identity.groups[_] == &quot;admins&quot;\n}\n\nis_bi_user {\n    input.context.identity.groups[_] == &quot;bi_users&quot;\n}\n\nis_target_schema {\n    input.action.resource.catalog.name == &quot;catalog&quot;\n    input.action.resource.schema.name == &quot;schema&quot;\n}\n\nis_verify_totp {\n    input.action.resource.catalog.name == &quot;catalog&quot;\n    input.action.resource.schema.name == &quot;schema&quot;\n    input.action.resource.function.name == &quot;verify_totp&quot;\n}\n\nallow {\n    is_admin\n    is_target_schema\n    input.action.operation == &quot;CreateFunction&quot;\n}\n\nallow {\n    is_admin\n    is_target_schema\n    input.action.operation == &quot;DropFunction&quot;\n}\n\nallow {\n    is_admin\n    is_target_schema\n    input.action.operation == &quot;ExecuteFunction&quot;\n}\n\nallow {\n    is_bi_user\n    is_verify_totp\n    input.action.operation == &quot;ExecuteFunction&quot;\n}<\/code><\/pre><p>Точные названия операций (`CreateFunction`, `ExecuteFunction`) лучше уточнить в логах OPA при тестовом запросе.<\/p>\n<hr \/>\n<h3>Шаг 5. Запрос для BI (без Python)<\/h3>\n<p>После регистрации функции BI-запрос становится обычным SQL. Пользователь вводит `:login` и `:otp` как параметры.<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">WITH auth_user AS (\n    SELECT\n        login,\n        employee_name,\n        department,\n        position_name,\n        manager_login\n    FROM catalog.schema.dashboard_users\n    WHERE login = lower(trim(':login'))\n      AND is_active = true\n      AND catalog.schema.verify_totp(\n            totp_secret,\n            trim(':otp'),\n            CAST(to_unixtime(now()) AS BIGINT)\n          )\n    LIMIT 1\n)\nSELECT\n    d.row_id,\n    d.row_title,\n    d.amount,\n    d.owner_login,\n    d.department,\n    d.manager_login,\n    d.access_policy,\n    a.login AS current_login,\n    a.employee_name AS current_employee_name,\n    a.department AS current_department,\n    a.position_name AS current_position_name,\n    CASE\n        WHEN d.access_policy = 'ALL' THEN 'Видно всем авторизованным'\n        WHEN d.access_policy = 'OWNER' AND d.owner_login = a.login THEN 'Видно владельцу строки'\n        WHEN d.access_policy = 'DEPARTMENT' AND d.department = a.department THEN 'Видно сотруднику отдела'\n        WHEN d.access_policy = 'MANAGER' AND d.manager_login = a.login THEN 'Видно руководителю'\n        WHEN d.access_policy = 'DIRECTOR' AND a.position_name = 'director' THEN 'Видно директору'\n        ELSE null\n    END AS access_reason\nFROM catalog.schema.dashboard_data d\nCROSS JOIN auth_user a\nWHERE\n       d.access_policy = 'ALL'\n    OR (d.access_policy = 'OWNER' AND d.owner_login = a.login)\n    OR (d.access_policy = 'DEPARTMENT' AND d.department = a.department)\n    OR (d.access_policy = 'MANAGER' AND d.manager_login = a.login)\n    OR (d.access_policy = 'DIRECTOR' AND a.position_name = 'director');<\/code><\/pre><p><b>Важно про параметры:<\/b> если BI подставляет их как сырой текст, нужны кавычки: `’:login’`, `’:otp’`. Если BI использует bind-параметры — кавычки не нужны: `:login`, `:otp`. В предыдущих ошибках (`Column ‘jbswy3dpehpk3pxp’ cannot be resolved`) видно, что в вашем случае нужны именно кавычки.<\/p>\n<hr \/>\n<h3>Шаг 6. Логика доступа<\/h3>\n<ul>\n<li><b>`auth_user`<\/b> — возвращает 0 строк, если логин не найден, пользователь неактивен или OTP неверный.<\/li>\n<li><b>`CROSS JOIN auth_user`<\/b> — если `auth_user` пуст, результат всего запроса пуст.<\/li>\n<li><b>`WHERE` с `OR`<\/b> — оставляет только те строки, которые разрешены политикой:\n<ul>\n  <li>`ALL` — всем авторизованным;<\/li>\n  <li>`OWNER` — владельцу (`owner_login = a.login`);<\/li>\n  <li>`DEPARTMENT` — сотруднику того же отдела;<\/li>\n  <li>`MANAGER` — руководителю (`manager_login = a.login`);<\/li>\n  <li>`DIRECTOR` — директору (`position_name = ‘director’`).<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>Пример: `vasya` увидит свои строки, строки отдела `sales`, строки, где он руководитель, и публичные. `petya` — только свои, отдел `sales` и публичные.<\/p>\n<hr \/>\n<h3>Шаг 7. Генерация QR-кода для OTP<\/h3>\n<p>Каждому пользователю нужно выдать TOTP-секрет и удобно передать его в приложение-аутентификатор. Стандартный способ — использовать URI формата `otpauth:\/\/`.<\/p>\n<p>Пример для пользователя `vasya`:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">otpauth:\/\/totp\/DemoBI:vasya?secret=JBSWY3DPEHPK3PXP&amp;issuer=DemoBI<\/code><\/pre><p>Здесь:<\/p>\n<ul>\n<li>`DemoBI:vasya` — метка, которая отобразится в приложении. Можно заменить на название отчёта, отдела или что угодно, например `SalesDashboard:vasya`.<\/li>\n<li>`secret=JBSWY3DPEHPK3PXP` — Base32-секрет.<\/li>\n<li>`issuer=DemoBI` — имя издателя, обычно название компании или системы.<\/li>\n<\/ul>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/Screenshot-from-2026-09-11-00-31-21.png\" width=\"596\" height=\"538\" alt=\"\" \/>\n<\/div>\n<p>Эту строку можно вставить в любой генератор QR-кодов (онлайн или офлайн). После генерации QR-код сканируется приложением:<\/p>\n<ul>\n<li>Google Authenticator,<\/li>\n<li>Yandex ID,<\/li>\n<li>KeePass (с плагином TOTP),<\/li>\n<li>и любым другим менеджером паролей, поддерживающим TOTP.<\/li>\n<\/ul>\n<p>Это стандарт, поэтому совместимость широкая. QR-код можно распечатать или отправить пользователю.<\/p>\n<blockquote>\n<p><b>Совет:<\/b> если у вас много пользователей, сгенерируйте QR-коды автоматически из таблицы `dashboard_users`, подставляя логин и секрет.<\/p>\n<\/blockquote>\n<hr \/>\n<h3>Шаг 8. Увеличение окна действия OTP (2–5 минут)<\/h3>\n<p>По умолчанию TOTP-код действует 30 секунд. Это может быть неудобно: пользователь не успевает ввести код. Можно увеличить окно действия, изменив функцию.<\/p>\n<p>В Python-коде есть строка:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">current = int(time_step \/\/ 30)<\/code><\/pre><p>Здесь `30` — это количество секунд в одном временном шаге. Если заменить `30` на `300`, то код будет действителен 5 минут (300 секунд). При этом приложение-аутентификатор продолжит генерировать коды каждые 30 секунд, но сервер будет принимать любой код, сгенерированный в течение этих 5 минут. Это удобно и безопасно: окно не бесконечное, но достаточное, чтобы успеть ввести код.<\/p>\n<p>Пример изменённой функции:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">current = int(time_step \/\/ 300)   # 5 минут\nfor offset in (-1, 0, 1):\n    ...<\/code><\/pre><p>Если оставить смещения `-1, 0, 1`, то общее окно станет 15 минут (5 минут назад, текущие 5 минут, 5 минут вперёд). Это может быть избыточно. Лучше убрать смещения и проверять только текущий интервал:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">current = int(time_step \/\/ 300)\ncounter = current\nmsg = struct.pack(&quot;&gt;Q&quot;, counter)\n...\n# без цикла for<\/code><\/pre><p>Тогда код будет действителен ровно 5 минут.<\/p>\n<p>Если хотите 2 минуты — используйте `120` вместо `300`.<\/p>\n<blockquote>\n<p><b>Важно:<\/b> после изменения функции её нужно пересоздать ( `DROP FUNCTION` и `CREATE FUNCTION` ). Все пользователи автоматически начнут работать с новым окном.<\/p>\n<\/blockquote>\n<hr \/>\n<h3>Шаг 9. Безопасность<\/h3>\n<ol start=\"1\">\n<li><b>Секреты TOTP<\/b> нельзя хранить в открытой схеме. Лучше вынести `dashboard_users` в закрытую схему, например `security.users`.<\/li>\n<li><b>BI-пользователи<\/b> не должны иметь прямой доступ к таблице с секретами. Только к функции и к данным.<\/li>\n<li><b>OTP-код<\/b> попадает в логи Trino и BI. Логи могут быть доступны администраторам или храниться в системе. Это не смертельно, потому что код короткоживущий, но помните: любой, кто видит лог, может увидеть введённый OTP. Если окно действия увеличено до 5 минут, риск возрастает. По возможности используйте реального пользователя из аутентификации BI, а не ручной ввод логина.<\/li>\n<li><b>Права на создание функций<\/b> — только у группы `admins`. BI-пользователям — только `EXECUTE` на конкретную функцию.<\/li>\n<\/ol>\n<hr \/>\n<h3>Заключение<\/h3>\n<p>Мы построили параноидальный, но рабочий механизм: OTP-аутентификация + динамическая фильтрация строк на уровне SQL. Это не заменяет полноценный RBAC, но позволяет быстро закрыть дашборд от посторонних глаз без изменения BI-системы.<\/p>\n<p><b>Что проверено:<\/b> inline-функция в DBeaver, запрос с фильтрацией, логика доступа.<br \/>\n<b>Что требует настройки:<\/b> регистрация функции и права в Trino. После настройки прав (шаг 4) функция создаётся один раз, а BI-запросы становятся чистыми и безопасными.<\/p>\n<p>Удачи в паранойе! :)<\/p>\n<div class=\"e2-text-picture\">\n<div class=\"fotorama\" data-width=\"416\" data-ratio=\"1.0833333333333\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/Screenshot-from-2026-09-11-00-33-10.png\" width=\"416\" height=\"384\" alt=\"\" \/>\n<img src=\"https:\/\/gavrilov.info\/pictures\/Screenshot-from-2026-09-11-01-21-57.png\" width=\"362\" height=\"424\" alt=\"\" \/>\n<\/div>\n<\/div>\n",
            "date_published": "2026-09-11T01:17:04+03:00",
            "date_modified": "2026-09-16T19:03:41+03:00",
            "tags": [
                "SQL",
                "Trino"
            ],
            "image": "https:\/\/gavrilov.info\/pictures\/Screenshot-from-2026-09-11-00-28-13.png",
            "_date_published_rfc2822": "Fri, 11 Sep 2026 01:17:04 +0300",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "351",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "highlight\/highlight.js",
                    "highlight\/highlight.css",
                    "jquery\/jquery.js",
                    "fotorama\/fotorama.css",
                    "fotorama\/fotorama.js"
                ],
                "og_images": [
                    "https:\/\/gavrilov.info\/pictures\/Screenshot-from-2026-09-11-00-28-13.png",
                    "https:\/\/gavrilov.info\/pictures\/Screenshot-from-2026-09-11-00-31-21.png",
                    "https:\/\/gavrilov.info\/pictures\/Screenshot-from-2026-09-11-00-33-10.png",
                    "https:\/\/gavrilov.info\/pictures\/Screenshot-from-2026-09-11-01-21-57.png"
                ]
            }
        },
        {
            "id": "323",
            "url": "https:\/\/gavrilov.info\/all\/starrocks-arhitektura-praktika-i-mesto-v-sovremennom-data-stack\/",
            "title": "StarRocks: Архитектура, Практика и место в современном Data Stack",
            "content_html": "<p><b>StarRocks<\/b> — это аналитическая MPP-база данных нового поколения.<br \/>\nЕсли коротко, она пытается решить трилемму аналитики: объединить скорость <b>ClickHouse<\/b> (за счет векторизации и C++), гибкость <b>Trino<\/b> (поддержка сложных JOIN-ов) и простоту использования <b>MySQL<\/b> (совместимый протокол).<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/logo_starr.svg\" width=\"54\" height=\"62\" alt=\"\" \/>\n<\/div>\n<p>Это короткое руководство проведет вас от понимания архитектуры до построения простого конвейера загрузки данных (ETL) в домашнем продакшене.<\/p>\n<hr \/>\n<h3>Часть 1. Архитектура: FE и BE<\/h3>\n<p>В отличие от PostgreSQL (монолит) или ClickHouse (где узлы часто одноранговые), StarRocks имеет четкое разделение ролей. Это критически важно для понимания масштабирования и эксплуатации.<\/p>\n<h4>1. FE (Frontend) — “Мозг”<\/h4>\n<p>Написан на Java.<\/p>\n<ul>\n<li><b>Роль:<\/b> Управляющий слой.<\/li>\n<li><b>Функции:<\/b>\n<ul>\n  <li>Принимает подключения клиентов (по протоколу MySQL).<\/li>\n  <li>Хранит метаданные (схемы таблиц, права доступа).<\/li>\n  <li>Парсит SQL и строит план выполнения запроса (Query Plan).<\/li>\n  <li>Управляет транзакциями загрузки данных.<\/li>\n<\/ul>\n<\/li>\n<li><b>Масштабирование:<\/b> Обычно запускают 1 или 3 узла для обеспечения высокой доступности (HA).<\/li>\n<li><b>Важно:<\/b> Клиенты (DBeaver, BI, сurl) подключаются <b>только<\/b> к FE.<\/li>\n<\/ul>\n<h4>2. BE (Backend) — “Мускулы”<\/h4>\n<p>Написан на C++ (использует SIMD-инструкции процессора).<\/p>\n<ul>\n<li><b>Роль:<\/b> Слой хранения и вычислений.<\/li>\n<li><b>Функции:<\/b>\n<ul>\n  <li>Физически хранит данные (в колоночном формате).<\/li>\n  <li>Выполняет “тяжелую” работу: фильтрацию, агрегацию, JOIN-ы.<\/li>\n  <li>Управляет репликацией данных.<\/li>\n<\/ul>\n<\/li>\n<li><b>Масштабирование:<\/b> Можно добавлять узлы линейно. Чем больше BE, тем быстрее выполняются запросы и тем больше данных можно хранить.<\/li>\n<\/ul>\n<blockquote>\n<p><b>В Docker All-in-One:<\/b> Оба компонента упакованы в один контейнер для удобства, но слушают разные порты:<\/p>\n<ul>\n<li>`9030`: FE (SQL интерфейс, сюда идет DBeaver).<\/li>\n<li>`8030`: FE (HTTP API для загрузки Stream Load, сюда идет curl).<\/li>\n<li>`8040`: BE (HTTP API метрик и логов).<\/li>\n<\/ul>\n<\/blockquote>\n<hr \/>\n<h3>Часть 2. Быстрый старт (Docker Compose)<\/h3>\n<p>Мы поднимем стек StarRocks и MinIO (S3-совместимое хранилище), используя bridge-сеть для связности.<\/p>\n<p><b>Файл `docker-compose.yml`<\/b> (Полностью рабочий пример):<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">version: &quot;3.9&quot;\n\nnetworks:\n  starrocks-stack-network:\n    driver: bridge\n\nservices:\n  starrocks:\n    image: starrocks\/allin1-ubuntu:4.0-latest\n    container_name: starrocks\n    hostname: starrocks.local.com\n    platform: &quot;linux\/amd64&quot;\n    restart: unless-stopped\n    ports:\n      - &quot;9030:9030&quot; # MySQL Protocol (SQL клиенты)\n      - &quot;8030:8030&quot; # FE HTTP (Stream Load)\n      - &quot;8040:8040&quot; # BE HTTP (Logs\/Metrics)\n    environment:\n      - TZ=UTC\n    networks:\n      starrocks-stack-network:\n    volumes:\n      # Персистентность данных (чтобы данные не исчезли после рестарта)\n      - ${HOME}\/dv\/starrocks\/be\/storage:\/data\/deploy\/starrocks\/be\/storage\n      - ${HOME}\/dv\/starrocks\/be\/log:\/data\/deploy\/starrocks\/be\/log\n      - ${HOME}\/dv\/starrocks\/fe\/meta:\/data\/deploy\/starrocks\/fe\/meta\n      - ${HOME}\/dv\/starrocks\/fe\/log:\/data\/deploy\/starrocks\/fe\/log\n\n  minio:\n    image: quay.io\/minio\/minio\n    container_name: minio\n    platform: &quot;linux\/amd64&quot;\n    hostname: minio.local.com\n    restart: unless-stopped\n    ports:\n      - &quot;9000:9000&quot; # S3 API\n      - &quot;9001:9001&quot; # Web UI\n    networks:\n      starrocks-stack-network:\n    environment:\n      MINIO_ROOT_USER: root\n      MINIO_ROOT_PASSWORD: rootroot\n    volumes:\n      - ${HOME}\/dv\/minio\/data:\/data\n    command: server \/data --console-address &quot;:9001&quot;<\/code><\/pre><p>Запуск:<br \/>\n`docker-compose up -d`<\/p>\n<hr \/>\n<h3>Часть 3. Моделирование данных (Table Design)<\/h3>\n<p>В StarRocks нельзя просто “создать таблицу”. Нужно выбрать тип ключа (<b>Key Model<\/b>), который определит, как база будет хранить и обновлять данные.<\/p>\n<p>Подключение (DBeaver): `localhost:9030`, User: `root`, Password: (пусто).<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">CREATE DATABASE IF NOT EXISTS demo_db;\nUSE demo_db;<\/code><\/pre><h4>1. Primary Key Model (Для изменяемых данных)<\/h4>\n<p>Это “флагманская” возможность StarRocks. Она поддерживает быстрые <b>Upsert<\/b> (вставка новых или обновление старых записей по ID) в реальном времени.<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">CREATE TABLE IF NOT EXISTS users (\n    user_id INT NOT NULL,\n    username VARCHAR(50),\n    email VARCHAR(100),\n    register_date DATE, \n    city VARCHAR(50)\n)\nPRIMARY KEY (user_id) -- Уникальный ключ\nDISTRIBUTED BY HASH(user_id) -- Распределение данных\nPROPERTIES (\n    &quot;replication_num&quot; = &quot;1&quot; -- Для локального теста ставим 1 реплику\n);<\/code><\/pre><h4>2. Aggregate Key Model (Для витрин данных)<\/h4>\n<p>База автоматически агрегирует данные при вставке. Если вы вставите новую продажу с *существующими* датой и категорией, StarRocks не создаст новую строку, а прибавит суммы к уже существующей строке. Это экономит место и ускоряет `GROUP BY`.<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">CREATE TABLE IF NOT EXISTS daily_sales (\n    report_date DATE NOT NULL,\n    category VARCHAR(50) NOT NULL,\n    \n    -- Метрики с функцией агрегации:\n    total_amount BIGINT SUM DEFAULT &quot;0&quot;, \n    items_sold INT SUM DEFAULT &quot;0&quot;       \n)\nAGGREGATE KEY (report_date, category)\nDISTRIBUTED BY HASH(report_date) BUCKETS 3\nPROPERTIES (\n    &quot;replication_num&quot; = &quot;1&quot;\n);<\/code><\/pre><hr \/>\n<h3>Часть 4. загрузка данных users (Stream Load)<\/h3>\n<p>Для загрузки данных в продакшене мы используем <b>Service Account<\/b> (Техническую учетную запись). Это стандарт безопасности: мы не используем `root` и не используем токены в конфигах (так как они требуют перезагрузки кластера для смены).<\/p>\n<h4>Шаг 1. Создание сервисного пользователя (SQL)<\/h4>\n<p>Выполнять под `root`:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">-- 1. Создаем пользователя-бота\nCREATE USER IF NOT EXISTS 'etl_loader'@'%' IDENTIFIED BY 'SecretPass123!';\n\n-- 2. Даем права ТОЛЬКО на вставку и чтение в базе demo_db\nGRANT INSERT, SELECT ON demo_db.* TO 'etl_loader'@'%';\n\n-- Права применяются мгновенно.<\/code><\/pre><h4>Шаг 2. Загрузка сложного JSON через CURL<\/h4>\n<p>Stream Load — это самый быстрый способ загрузки (до 100 МБ\/сек на узел). Он поддерживает транзакционность (ACID).<\/p>\n<p><b>Пример файла `users.json`:<\/b><\/p>\n<pre class=\"e2-text-code\"><code class=\"\">{\n  &quot;users&quot;: [\n    {&quot;user_id&quot;: 101, &quot;username&quot;: &quot;alex&quot;, &quot;email&quot;: &quot;a@test.com&quot;, &quot;city&quot;: &quot;NY&quot;},\n    {&quot;user_id&quot;: 102, &quot;username&quot;: &quot;bob&quot;, &quot;email&quot;: &quot;b@test.com&quot;, &quot;city&quot;: &quot;LA&quot;}\n  ]\n}<\/code><\/pre><p><b>Команда загрузки (Terminal):<\/b><\/p>\n<pre class=\"e2-text-code\"><code class=\"\">curl --location-trusted \\\n    -u etl_loader:SecretPass123! \\\n    -H &quot;Expect: 100-continue&quot; \\\n    -H &quot;format: json&quot; \\\n    -H &quot;strip_outer_array: true&quot; \\\n    -H &quot;json_root: $.users&quot; \\\n    -H &quot;jsonpaths: [\\&quot;$.user_id\\&quot;, \\&quot;$.username\\&quot;, \\&quot;$.email\\&quot;, \\&quot;$.city\\&quot;]&quot; \\\n    -H &quot;columns: user_id, username, email, city&quot; \\\n    -T &quot;users.json&quot; \\\n    -XPUT http:\/\/localhost:8030\/api\/demo_db\/users\/_stream_load<\/code><\/pre><p>Ответ<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">{\n    &quot;TxnId&quot;: 9596,\n    &quot;Label&quot;: &quot;a9a37ab6-3678-4c08-95b7-2fd8b6ae973e&quot;,\n    &quot;Db&quot;: &quot;demo_db&quot;,\n    &quot;Table&quot;: &quot;users&quot;,\n    &quot;Status&quot;: &quot;Success&quot;,\n    &quot;Message&quot;: &quot;OK&quot;,\n    &quot;NumberTotalRows&quot;: 2,\n    &quot;NumberLoadedRows&quot;: 2,\n    &quot;NumberFilteredRows&quot;: 0,\n    &quot;NumberUnselectedRows&quot;: 0,\n    &quot;LoadBytes&quot;: 177,\n    &quot;LoadTimeMs&quot;: 153,\n    &quot;BeginTxnTimeMs&quot;: 2,\n    &quot;StreamLoadPlanTimeMs&quot;: 2,\n    &quot;ReadDataTimeMs&quot;: 0,\n    &quot;WriteDataTimeMs&quot;: 26,\n    &quot;CommitAndPublishTimeMs&quot;: 121\n}%<\/code><\/pre><h4>Шаг 3. Загрузка в Aggregate Table (Example)<\/h4>\n<p>Давайте “дольем” данные в таблицу продаж. Агрегация произойдет на лету.<br \/>\nФайл sales.json (простой список):<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">[\n    {&quot;dt&quot;: &quot;2023-11-01&quot;, &quot;cat&quot;: &quot;Electronics&quot;, &quot;amt&quot;: 100, &quot;qty&quot;: 1},\n    {&quot;dt&quot;: &quot;2023-11-01&quot;, &quot;cat&quot;: &quot;Electronics&quot;, &quot;amt&quot;: 50,  &quot;qty&quot;: 1}\n]\n\ncurl --location-trusted \\\n    -u etl_loader:SecretPass123! \\\n    -H &quot;format: json&quot; \\\n    -H &quot;Expect: 100-continue&quot; \\\n    -H &quot;strip_outer_array: true&quot; \\\n    -H &quot;jsonpaths: [\\&quot;$.dt\\&quot;, \\&quot;$.cat\\&quot;, \\&quot;$.amt\\&quot;, \\&quot;$.qty\\&quot;]&quot; \\\n    -H &quot;columns: report_date, category, total_amount, items_sold&quot; \\\n    -T &quot;sales.json&quot; \\\n    -XPUT http:\/\/localhost:8030\/api\/demo_db\/daily_sales\/_stream_load<\/code><\/pre><p>Ответ:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">{\n    &quot;TxnId&quot;: 9613,\n    &quot;Label&quot;: &quot;bce0721a-dc2d-4927-be93-e0979a57873d&quot;,\n    &quot;Db&quot;: &quot;demo_db&quot;,\n    &quot;Table&quot;: &quot;daily_sales&quot;,\n    &quot;Status&quot;: &quot;Success&quot;,\n    &quot;Message&quot;: &quot;OK&quot;,\n    &quot;NumberTotalRows&quot;: 2,\n    &quot;NumberLoadedRows&quot;: 2,\n    &quot;NumberFilteredRows&quot;: 0,\n    &quot;NumberUnselectedRows&quot;: 0,\n    &quot;LoadBytes&quot;: 143,\n    &quot;LoadTimeMs&quot;: 52,\n    &quot;BeginTxnTimeMs&quot;: 3,\n    &quot;StreamLoadPlanTimeMs&quot;: 2,\n    &quot;ReadDataTimeMs&quot;: 0,\n    &quot;WriteDataTimeMs&quot;: 24,\n    &quot;CommitAndPublishTimeMs&quot;: 20\n}%<\/code><\/pre><p><b>Разбор заголовков:<\/b><\/p>\n<ul>\n<li>`-u ...`: Авторизация сервисным пользователем.<\/li>\n<li>`Expect: 100-continue`: Критически важно для надежности передачи больших файлов.<\/li>\n<li>`json_root: $.users`: Указывает базе, что данные лежат внутри ключа `users`.<\/li>\n<li>`strip_outer_array: true`: Говорит базе, что внутри лежит массив `[...]` и его нужно “развернуть” в отдельные строки.<\/li>\n<\/ul>\n<hr \/>\n<h3>Часть 5. Совместимость и Trino Dialect<\/h3>\n<p>Одна из сильных сторон StarRocks — способность “притворяться” другими базами данных для облегчения миграции.<\/p>\n<p>Если у вас есть дашборды, написанные на диалекте <b>Trino (Presto)<\/b>, вам не нужно переписывать все SQL-запросы.<\/p>\n<p><b>Пример трансляции функций:<\/b><\/p>\n<pre class=\"e2-text-code\"><code class=\"\">-- Функция Trino, которой нет в StarRocks\nSELECT doy(date '2022-03-06'); \n-- Ошибка: No matching function...\n\n-- Проверяем, как StarRocks переведет этот запрос\nTRANSLATE TRINO select doy(date '2022-03-06');\n-- Результат: SELECT dayofyear('2022-03-06')\n\n-- Включаем режим автоматической трансляции в сессии\nSET sql_dialect = 'trino'; \n\n-- Теперь запрос выполняется корректно, но это не правда. а вот так SELECT dayofyear('2022-03-06') работает. Может бага или у меня версия не та. \nSELECT doy(date '2022-03-06');   \n\n-- Возвращаем нативный режим\nSET sql_dialect = 'starrocks';<\/code><\/pre><p>*(Примечание: Поддержка диалекта постоянно расширяется, но некоторые специфические функции могут требовать ручной замены).*<\/p>\n<hr \/>\n<h3>Итог: Сравнение и Выбор решения ( грубо )<\/h3>\n<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\" class=\"e2-text-table\">\n<tr>\n<td style=\"text-align: center\">Характеристика<\/td>\n<td style=\"text-align: center\"><b>StarRocks<\/b><\/td>\n<td style=\"text-align: center\"><b>ClickHouse<\/b><\/td>\n<td style=\"text-align: center\"><b>Trino (Presto)<\/b><\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\"><b>Основной сценарий<\/b><\/td>\n<td style=\"text-align: center\">OLAP-витрины с JOIN-ами и обновлениями данных<\/td>\n<td style=\"text-align: center\">Сбор логов, событий, метрик (Append-only)<\/td>\n<td style=\"text-align: center\">Федерация данных (запрос к S3 + Postgres + Kafka одновременно)<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\"><b>JOIN производительность<\/b><\/td>\n<td style=\"text-align: center\">⭐⭐⭐ (Excellent, CBO оптимизатор)<\/td>\n<td style=\"text-align: center\">⭐ (Слабо, требует денормализации)<\/td>\n<td style=\"text-align: center\">⭐⭐⭐ (Excellent)<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\"><b>Обновление (UPDATE)<\/b><\/td>\n<td style=\"text-align: center\">⭐⭐⭐ (Работает как в OLTP, Primary Key)<\/td>\n<td style=\"text-align: center\">⭐ (Тяжелые асинхронные ALTER)<\/td>\n<td style=\"text-align: center\">❌ (Обычно только полная перезапись партиций), iceberg не в счёт :)<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\"><b>Язык Engine<\/b><\/td>\n<td style=\"text-align: center\">C++ (SIMD Vectorized)<\/td>\n<td style=\"text-align: center\">C++ (SIMD Vectorized)<\/td>\n<td style=\"text-align: center\">Java (JVM)<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\"><b>Место в стеке<\/b><\/td>\n<td style=\"text-align: center\"><b>Serving Layer<\/b> (Быстрый доступ для BI)<\/td>\n<td style=\"text-align: center\"><b>Storage Layer<\/b> (Хранение логов)<\/td>\n<td style=\"text-align: center\"><b>Query Engine<\/b> (Ad-hoc запросы к Data Lake)<\/td>\n<\/tr>\n<\/table>\n<p><b>Выбирайте StarRocks, если:<\/b><\/p>\n<ol start=\"1\">\n<li>Вам нужна “витрина” для BI (Superset\/Tableau), где данные должны быть всегда свежими (Real-time updates).<\/li>\n<li>Ваш бизнес требует сложных аналитических запросов с множеством JOIN-ов, и ClickHouse не справляется\/падает по памяти.<\/li>\n<li>Вы хотите использовать стандартный протокол MySQL без установки проприетарных драйверов.<\/li>\n<\/ol>\n",
            "date_published": "2026-03-15T19:06:01+03:00",
            "date_modified": "2026-03-15T19:05:56+03:00",
            "tags": [
                "big data",
                "Data",
                "Data Engineer",
                "SQL"
            ],
            "image": "https:\/\/gavrilov.info\/pictures\/logo_starr.svg",
            "_date_published_rfc2822": "Sun, 15 Mar 2026 19:06:01 +0300",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "323",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "highlight\/highlight.js",
                    "highlight\/highlight.css"
                ],
                "og_images": [
                    "https:\/\/gavrilov.info\/pictures\/logo_starr.svg"
                ]
            }
        },
        {
            "id": "215",
            "url": "https:\/\/gavrilov.info\/all\/nastroyka-ldap-v-querybook\/",
            "title": "Настройка ldap в Querybook",
            "content_html": "<p>Казалось не очевидная задача, но победа случилась на второй день.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2025-03-27-v-23.39.30.png\" width=\"1384\" height=\"308\" alt=\"\" \/>\n<\/div>\n<pre class=\"e2-text-code\"><code class=\"\">querybook_web            | 127.0.0.1 - - [2025-03-27 20:36:47] &quot;GET \/ds\/user\/me\/ HTTP\/1.1&quot; 401 285 0.000000\nquerybook_web            | 127.0.0.1 - - [2025-03-27 20:36:47] &quot;GET \/ds\/user\/login_method\/ HTTP\/1.1&quot; 200 273 0.000000\nquerybook_web            | [2025-03-27 Thu 20:36:56] - \/opt\/querybook\/querybook\/server\/app\/auth\/ldap_auth.py - DEBUG   &quot;LDAP bind TRY with username: 'uid=jduke,ou=Users,dc=ldap,dc=example'&quot;\nquerybook_web            | [2025-03-27 Thu 20:36:56] - \/opt\/querybook\/querybook\/server\/app\/auth\/ldap_auth.py - DEBUG   &quot;LDAP bind SUCCESS with username: 'uid=jduke,ou=Users,dc=ldap,dc=example'&quot;\nquerybook_worker         | [2025-03-27 20:36:56,786: INFO\/MainProcess] Task celery.local.sync_elasticsearch__debouncer[f407a87d-ae47-450b-95b1-c44df07eb1e7] received\nquerybook_worker         | [2025-03-27 20:36:56,787: DEBUG\/MainProcess] TaskPool: Apply &lt;function fast_trace_task at 0x7fb3791b0d30&gt; (args:('celery.local.sync_elasticsearch__debouncer', 'f407a87d-ae47-450b-95b1-c44df07eb1e7', {'lang': 'py', 'task': 'celery.local.sync_elasticsearch__debouncer', 'id': 'f407a87d-ae47-450b-95b1-c44df07eb1e7', 'shadow': None, 'eta': None, 'expires': None, 'group': None, 'group_index': None, 'retries': 0, 'timelimit': [None, None], 'root_id': 'f407a87d-ae47-450b-95b1-c44df07eb1e7', 'parent_id': None, 'argsrepr': &quot;['users', 1]&quot;, 'kwargsrepr': '{}', 'origin': 'gen100@6ede59afb0cf', 'ignore_result': False, 'properties': {'correlation_id': 'f407a87d-ae47-450b-95b1-c44df07eb1e7', 'reply_to': '70ed7e91-4699-31e4-8f50-86d1a850d89a', 'delivery_mode': 2, 'delivery_info': {'exchange': '', 'routing_key': 'celery'}, 'priority': 0, 'body_encoding': 'base64', 'delivery_tag': '2d6aecef-9aea-4564-9bd0-994731c8eba5'}, 'reply_to': '70ed7e91-4699-31e4-8f50-86d1a850d89a', 'correlation_id': 'f407a87d-ae47-450b-95b1-c44df07eb1e7', 'hostname': 'celery@05a545667216', 'delivery_info': {'exchange': '', 'routing_key': 'celery', 'priority': 0,... kwargs:{})\nquerybook_web            | 127.0.0.1 - - [2025-03-27 20:36:56] &quot;POST \/ds\/login\/ HTTP\/1.1&quot; 200 655 0.282608\nquerybook_web            | 127.0.0.1 - - [2025-03-27 20:36:56] &quot;GET \/ds\/user\/me\/ HTTP\/1.1&quot; 200 490 0.011824\nquerybook_web            | 127.0.0.1 - - [2025-03-27 20:36:56] &quot;GET \/ds\/user\/setting\/ HTTP\/1.1&quot; 200 215 0.020317\nquerybook_web            | 127.0.0.1 - - [2025-03-27 20:36:56] &quot;GET \/ds\/announcement\/ HTTP\/1.1&quot; 200 215 0.011983\nquerybook_web            | 127.0.0.1 - - [2025-03-27 20:36:57] &quot;GET \/ds\/query\/transpile\/ HTTP\/1.1&quot; 200 419 0.017954\nquerybook_web            | 127.0.0.1 - - [2025-03-27 20:36:57] &quot;GET \/ds\/user\/notifiers\/ HTTP\/1.1&quot; 200 348 0.018368\nquerybook_web            | 127.0.0.1 - - [2025-03-27 20:36:57] &quot;GET \/ds\/query_execution_exporter\/ HTTP\/1.1&quot; 200 215 0.023621\nquerybook_web            | 127.0.0.1 - - [2025-03-27 20:37:02] &quot;GET \/ds\/user\/environment\/ HTTP\/1.1&quot; 200 233 5.029392\nquerybook_web            | 127.0.0.1 - - [2025-03-27 20:37:05] &quot;GET \/static\/favicon\/querybook.svg HTTP\/1.1&quot; 200 5544 0.017975\nquerybook_web            | 127.0.0.1 - - [2025-03-27 20:37:05] &quot;GET \/ds\/admin\/query_metastore\/ HTTP\/1.1&quot; 200 215 0.010979\nquerybook_web            | 127.0.0.1 - - [2025-03-27 20:37:05] &quot;GET \/ds\/admin\/environment\/ HTTP\/1.1&quot; 200 215 0.029958\nquerybook_web            | 127.0.0.1 - - [2025-03-27 20:37:05] &quot;GET \/ds\/admin\/query_engine\/ HTTP\/1.1&quot; 200 215 0.017463\nquerybook_worker         | [2025-03-27 20:37:06,821: INFO\/MainProcess] Task celery.local.sync_elasticsearch[185cb78a-0b66-47de-a3db-71e6bea1e53c] received\nquerybook_worker         | [2025-03-27 20:37:06,821: DEBUG\/MainProcess] basic.qos: prefetch_count-&gt;6\nquerybook_worker         | [2025-03-27 20:37:06,823: INFO\/ForkPoolWorker-3] Task celery.local.sync_elasticsearch__debouncer[f407a87d-ae47-450b-95b1-c44df07eb1e7] succeeded in 5.021026143018389s: None\nquerybook_elasticsearch  | [2025-03-27T20:37:19,071][INFO ][o.o.i.i.ManagedIndexCoordinator] [d8d3bb7a5243] Cancel background move metadata process.\nquerybook_elasticsearch  | [2025-03-27T20:37:19,073][INFO ][o.o.i.i.ManagedIndexCoordinator] [d8d3bb7a5243] Performing move cluster state metadata.\nquerybook_elasticsearch  | [2025-03-27T20:37:19,074][INFO ][o.o.i.i.MetadataService  ] [d8d3bb7a5243] Move metadata has finished.<\/code><\/pre><p>Мистер Дюк теперь может заходить в Querybook<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2025-03-27-v-23.40.48.png.jpg\" width=\"2560\" height=\"619\" alt=\"\" \/>\n<\/div>\n<p>Тот кто первый входит будет являться админом<\/p>\n<p>Далее сделал конвектор к Trino и один eviroment, и добавил конектор в него и юзера<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2025-03-27-v-23.45.19.png\" width=\"1322\" height=\"1470\" alt=\"\" \/>\n<\/div>\n<p>Можно писать запросики<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2025-03-27-v-23.47.03.png\" width=\"1744\" height=\"522\" alt=\"\" \/>\n<\/div>\n<p>Красивый прогресс<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2025-03-27-v-23.47.44.png\" width=\"2260\" height=\"498\" alt=\"\" \/>\n<\/div>\n<p>Тоже красиво<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2025-03-27-v-23.51.53.png\" width=\"1218\" height=\"1522\" alt=\"\" \/>\n<\/div>\n<p>Работает и даже на русском.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2025-03-27-v-23.54.19.png\" width=\"640\" height=\"1064\" alt=\"\" \/>\n<\/div>\n<p>PS: Откуда взялся Ldap?<\/p>\n<p>Вот этот под руку попался <a href=\"https:\/\/github.com\/intoolswetrust\/ldap-server\">https:\/\/github.com\/intoolswetrust\/ldap-server<\/a><\/p>\n<p>Он на базе Apache Directory <a href=\"https:\/\/directory.apache.org\/apacheds\/downloads.html\">https:\/\/directory.apache.org\/apacheds\/downloads.html<\/a><br \/>\nСделан как раз для тестов, то что нужно.<\/p>\n<p>Вот так делаем:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">docker pull kwart\/ldap-server\ndocker run -it --rm kwart\/ldap-server<\/code><\/pre><p>Потом так:<\/p>\n<p>..\/containers\/bundled_querybook_config.yaml<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">AUTH_BACKEND: 'app.auth.ldap_auth' # Same as import path when running Python\nLDAP_CONN: 'ldap:\/\/192.168.215.2:389'\nLDAP_USER_DN: 'uid={},ou=Users,dc=ldap,dc=example'<\/code><\/pre><p>И так<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">LDAP_CONN: &quot;ldap:\/\/192.168.215.2:389&quot;   #  без сертификатов, и так сойдет,  но они нужны и корневые особенно для ldaps. \nLDAP_USER_DN: &quot;uid={},ou=Users,dc=ldap,dc=example&quot;\nLDAP_UID_FIELD: &quot;uid&quot;\nLDAP_EMAIL_FIELD: &quot;uid&quot;  # У меня нет почты в ldap поэтому схитрил, но надо бы добавить атрибут mail в схему LDAP\nLDAP_LASTNAME_FIELD: &quot;sn&quot;\nLDAP_FIRSTNAME_FIELD: &quot;cn&quot;  # Используйте cn, если givenName отсутствует\nLDAP_FULLNAME_FIELD: &quot;cn&quot;\nLDAP_BIND_DN: &quot;uid=admin,ou=system&quot;\nLDAP_BIND_PASSWORD: &quot;secret&quot;\nLDAP_SEARCH_BASE: &quot;dc=ldap,dc=example&quot;<\/code><\/pre><p>Теперь осталось придумать как всунуть туда hive matastore – это будет чуть сложнее, чем казалось.<br \/>\nОдин всунуть можно, но одного мало, а много лень. Может сделать еще один как все и уже его одного добавлять? пока не знаю 🧐<\/p>\n",
            "date_published": "2025-03-28T00:09:54+03:00",
            "date_modified": "2025-03-28T00:13:04+03:00",
            "tags": [
                "big data",
                "Data",
                "Open Source",
                "SQL"
            ],
            "image": "https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2025-03-27-v-23.39.30.png",
            "_date_published_rfc2822": "Fri, 28 Mar 2025 00:09:54 +0300",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "215",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "highlight\/highlight.js",
                    "highlight\/highlight.css"
                ],
                "og_images": [
                    "https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2025-03-27-v-23.39.30.png",
                    "https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2025-03-27-v-23.40.48.png.jpg",
                    "https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2025-03-27-v-23.45.19.png",
                    "https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2025-03-27-v-23.47.03.png",
                    "https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2025-03-27-v-23.47.44.png",
                    "https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2025-03-27-v-23.51.53.png",
                    "https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2025-03-27-v-23.54.19.png"
                ]
            }
        },
        {
            "id": "166",
            "url": "https:\/\/gavrilov.info\/all\/plan-zaprosov-analiziruem-proizvoditelnost-v-trino\/",
            "title": "План запросов — Анализируем производительность в Trino",
            "content_html": "<p>Оригинал: <a href=\"https:\/\/medium.com\/@simon.thelin90\/query-plans-analyse-sql-performance-in-trino-97ac1e8f8044\">https:\/\/medium.com\/@simon.thelin90\/query-plans-analyse-sql-performance-in-trino-97ac1e8f8044<\/a><\/p>\n<p>Или тут: <a href=\"\"><a href=\"https:\/\/a.gavrilov.info\/data\/posts\/Query%20Plans%20\">https:\/\/a.gavrilov.info\/data\/posts\/Query%20Plans%20<\/a>—%20Analyse%20SQL%20Performance%20In%20Trino%20%7C%20by%20Simon%20Thelin%20%7C%20Medium.pdf Query Plans — Analyse SQL Performance In Trino<\/a><\/p>\n<p>Ещё одно воскресенье и ещё одна #датаболь для обсуждения.<br \/>\nСегодня я хочу углубиться в то, как мы можем понять план запроса в Trino.<\/p>\n<p>Исследование плана запроса<br \/>\nПорядок выполнения SQL — Давайте вспомним<br \/>\nПрежде чем мы начнем рассматривать это, давайте вспомним порядок выполнения в SQL-запросе.<br \/>\nFROM, JOIN<br \/>\nWHERE<br \/>\nGROUP BY<br \/>\nHAVING<br \/>\nSELECT<br \/>\nDISTINCT<br \/>\nORDER BY<br \/>\nLIMIT<br \/>\nЭто поможет нам, когда будем читать план запроса.<br \/>\nКак определить, является ли ваш SQL производительным?⚡<br \/>\nПрежде чем запрос может быть запланирован, движок также должен:<br \/>\nИдентифицировать таблицы<br \/>\nИдентифицировать столбцы, использованные в запросе<br \/>\nSQL, простой подсчёт названий должностей, где мы группируем по департаменту.<br \/>\nEXPLAIN ANALYZE WITH<\/p>\n<p>count_titles AS (<br \/>\nSELECT<br \/>\ndepartment,<br \/>\nCOUNT(job_title) AS count_job_titles<br \/>\nFROM lakehouse.bronze.jobs<br \/>\nGROUP BY 1<br \/>\n)<\/p>\n<p>SELECT * FROM count_titles<br \/>\nSQL-запрос использует Общую Табличную Выражение (CTE) под именем count_titles для упрощения структуры и улучшения читаемости запроса.<br \/>\nОн начинается с выбора данных из таблицы lakehouse.bronze.jobs.<br \/>\nВнутри CTE данные группируются по столбцу department.<br \/>\nДля каждой группы департаментов подсчитывается количество вхождений job_title и обозначается как count_job_titles.<br \/>\nПосле определения CTE основной запрос выбирает все столбцы из count_titles CTE.<br \/>\nОсновная цель запроса — получить количество названий должностей для каждого департамента из таблицы lakehouse.bronze.jobs.<br \/>\nПлан запроса 📣<br \/>\nВ очереди: 1.84ms, Анализ: 85.69ms, Планирование: 58.35ms, Выполнение: 450.11ms<br \/>\nФрагмент 1 [HASH]<br \/>\nCPU: 7.57ms, Запланировано: 11.12ms, Заблокировано: 1.80s (Вход: 933.53ms, Выход: 0.00ns), Вход: 8 строк (176B); на задачу: ср.: 4.00, отклонение: 2.00, Выход: 3 строки (67B)<br \/>\nКоличество входных данных, обработанных рабочими для этого этапа, может быть перекошено<br \/>\nВыходная структура: [department, count]<br \/>\nРазделение выхода: SINGLE []<br \/>\nАгрегат[тип = FINAL, ключи = [department]]<br \/>\n│   Расклад: [department:varchar, count:bigint]<br \/>\n│   Оценки: {строк: 3 (68B), cpu: 226, память: 68B, сеть: 0B}<br \/>\n│   CPU: 3.00ms (15.00%), Запланировано: 3.00ms (6.52%), Заблокировано: 0.00ns (0.00%), Выход: 3 строки (67B)<br \/>\n│   Ср. вход: 1.00 строки, стандартное отклонение входа: 132.29%<br \/>\n│   count := count(count_0)<br \/>\n└─ LocalExchange[разделение = HASH, аргументы = [department::varchar]]<br \/>\n│   Расклад: [department:varchar, count_0:bigint]<br \/>\n│   Оценки: {строк: 10 (226B), cpu: 226, память: 0B, сеть: 0B}<br \/>\n│   CPU: 1.00ms (5.00%), Запланировано: 1.00ms (2.17%), Заблокировано: 716.00ms (31.49%), Выход: 8 строк (176B)<br \/>\n│   Ср. вход: 1.00 строки, стандартное отклонение входа: 86.60%<br \/>\n└─ RemoteSource[идентификаторы источников = [2]]<br \/>\nРасклад: [department:varchar, count_0:bigint]<br \/>\nCPU: 0.00ns (0.00%), Запланировано: 1.00ms (2.17%), Заблокировано: 933.00ms (41.03%), Выход: 8 строк (176B)<br \/>\nСр. вход: 1.00 строки, стандартное отклонение входа: 86.60%<\/p>\n<p>Фрагмент 2 [HASH]<br \/>\nCPU: 11.24ms, Запланировано: 19.46ms, Заблокировано: 860.54ms (Вход: 444.24ms, Выход: 0.00ns), Вход: 10 строк (362B); на задачу: ср.: 5.00, отклонение: 2.00, Выход: 8 строк (176B)<br \/>\nКоличество входных данных, обработанных рабочими для этого этапа, может быть перекошено<br \/>\nВыходная структура: [department, count_0]<br \/>\nРазделение выхода: HASH [department]<br \/>\nАгрегат[тип = PARTIAL, ключи = [department]]<br \/>\n│   Расклад: [department:varchar, count_0:bigint]<br \/>\n│   Оценки: {строк: 10 (226B), cpu: ?, память: ?, сеть: ?}<br \/>\n│   CPU: 4.00ms (20.00%), Запланировано: 7.00ms (15.22%), Заблокировано: 0.00ns (0.00%), Выход: 8 строк (176B)<br \/>\n│   Ср. вход: 1.25 строки, стандартное отклонение входа: 118.32%<br \/>\n│   count_0 := count(job_title)<br \/>\n└─ Агрегат[тип = FINAL, ключи = [department, job_title]]<br \/>\n│   Расклад: [department:varchar, job_title:varchar]<br \/>\n│   Оценки: {строк: 10 (362B), cpu: 362, память: 362B, сеть: 0B}<br \/>\n│   CPU: 1.00ms (5.00%), Запланировано: 2.00ms (4.35%), Заблокировано: 0.00ns (0.00%), Выход: 10 строк (362B)<br \/>\n│   Ср. вход: 1.25 строки, стандартное отклонение входа: 118.32%<br \/>\n└─ LocalExchange[разделение = HASH, аргументы = [department::varchar, job_title::varchar]]<br \/>\n│   Расклад: [department:varchar, job_title:varchar]<br \/>\n│   Оценки: {строк: 10 (362B), cpu: 362, память: 0B, сеть: 0B}<br \/>\n│   CPU: 0.00ns (0.00%), Запланировано: 0.00ns (0.00%), Заблокировано: 181.00ms (7.96%), Выход: 10 строк (362B)<br \/>\n│   Ср. вход: 1.25 строки, стандартное отклонение входа: 190.79%<br \/>\n└─ RemoteSource[идентификаторы источников = [3]]<br \/>\nРасклад: [department:varchar, job_title:varchar]<br \/>\nCPU: 0.00ns (0.00%), Запланировано: 0.00ns (0.00%), Заблокировано: 444.00ms (19.53%), Выход: 10 строк (362B)<br \/>\nСр. вход: 1.25 строки, стандартное отклонение входа: 190.79%<\/p>\n<p>Фрагмент 3 [SOURCE]<br \/>\nCPU: 11.49ms, Запланировано: 32.68ms, Заблокировано: 0.00ns (Вход: 0.00ns, Выход: 0.00ns), Вход: 10 строк (382B); на задачу: ср.: 10.00, std.dev.: 0.00, Выход: 10 строк (362B)<br \/>\nВыходная структура: [department, job_title]<br \/>\nРазделение выхода: HASH [department, job_title]<br \/>\nАгрегат[тип = PARTIAL, ключи = [department, job_title]]<br \/>\n│   Расклад: [department:varchar, job_title:varchar]<br \/>\n│   Оценки: {строк: 10 (362B), cpu: ?, память: ?, сеть: ?}<br \/>\n│   CPU: 1.00ms (5.00%), Запланировано: 3.00ms (6.52%), Заблокировано: 0.00ns (0.00%), Выход: 10 строк (362B)<br \/>\n│   Ср. вход: 10.00 строк, std.dev.: 0.00%<br \/>\n└─ Сканирование таблицы[таблица = lakehouse:bronze.jobs]<br \/>\nРасклад: [job_title:varchar, department:varchar]<br \/>\nОценки: {строк: 10 (362B), cpu: 362, память: 0B, сеть: 0B}<br \/>\nCPU: 10.00ms (50.00%), Запланировано: 29.00ms (63.04%), Заблокировано: 0.00ns (0.00%), Выход: 10 строк (382B)<br \/>\nСр. вход: 10.00 строк, std.dev.: 0.00%<br \/>\njob_title := job_title:varchar:ОБЫЧНО<br \/>\ndepartment := department:varchar:ОБЫЧНО<br \/>\nВход: 10 строк (382B), Физический вход: 1.32kB, Время физического входа: 7.50ms<br \/>\nОбщая информация о выводе и плане запроса<\/p>\n<p>Высокоуровневый вывод выполнения.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/plan-zaprosov-analiziruem-proizvoditelnost-v-trino.png\" width=\"1174\" height=\"832\" alt=\"\" \/>\n<\/div>\n<p>В очереди: 1.84ms<br \/>\nАнализ: 85.69ms<br \/>\nПланирование: 58.35ms<br \/>\nВыполнение: 467.68ms<br \/>\nРазделение на фрагменты<br \/>\nВыполнение запроса делится на три главные фрагмента. Каждый фрагмент представляет этап в процессе выполнения.<br \/>\nФрагмент 1 [HASH]<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/plan-zaprosov-analiziruem-proizvoditelnost-v-trino-1.png\" width=\"742\" height=\"1042\" alt=\"\" \/>\n<\/div>\n<p>HASH<br \/>\nРоль: Этот фрагмент обрабатывает окончательную агрегацию результатов.<br \/>\nПроизводительность:<br \/>\nCPU: 7.57ms<br \/>\nЗапланировано: 11.12ms<br \/>\nЗаблокировано: 1.80s (главным образом ожидание данных от других фрагментов)<br \/>\nВход\/Выход:<br \/>\nВход: 8 строк (176B)<br \/>\nВыход: 3 строки (67B)<br \/>\nВыходная структура: [department, count]<br \/>\nОперации:<br \/>\nАгрегация: Окончательная агрегация по департаменту для вычисления общего количества названий должностей.<br \/>\nЛокальный обмен: Перераспределение данных по департаменту для подготовки к окончательной агрегации.<br \/>\nФрагмент 2 [HASH]<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/plan-zaprosov-analiziruem-proizvoditelnost-v-trino-2.png\" width=\"632\" height=\"1032\" alt=\"\" \/>\n<\/div>\n<p>Роль: Этот фрагмент выполняет частичную агрегацию названий должностей по департаменту.<br \/>\nПроизводительность:<br \/>\nCPU: 11.24ms<br \/>\nЗапланировано: 19.46ms<br \/>\nЗаблокировано: 860.54ms<br \/>\nВход\/Выход:<br \/>\nВход: 10 строк (362B)<br \/>\nВыход: 8 строк (176B)<br \/>\nВыходная структура: [department, count_0]<br \/>\nОперации:<br \/>\nЧастичная агрегация: Подсчитывает названия должностей по департаменту.<br \/>\nЛокальный обмен: Перераспределение данных по департаменту, job_title для дальнейшей агрегации.<br \/>\nФрагмент 3 [SOURCE]<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/plan-zaprosov-analiziruem-proizvoditelnost-v-trino-3.png\" width=\"876\" height=\"1084\" alt=\"\" \/>\n<\/div>\n<p>SOURCE — Сканирование таблицы<br \/>\nРоль: Этот фрагмент читает данные из исходной таблицы.<br \/>\nПроизводительность:<br \/>\nCPU: 11.49ms<br \/>\nЗапланировано: 32.68ms<br \/>\nВход\/Выход:<br \/>\nВход: 10 строк (382B)<br \/>\nВыход: 10 строк (362B)<br \/>\nВыходная структура: [department, job_title]<\/p>\n<p>Операции:<br \/>\nСканирование таблицы: Чтение столбцов department и job_title из таблицы lakehouse.bronze.jobs.<br \/>\nЧастичная агрегация: Группировка данных по департаменту и job_title и подготовка их к дальнейшей обработке.<\/p>\n<p>Ключевые выводы<br \/>\nЧтение плана запроса: Этапы представлены в порядке от наименьшего значения этапа к последнему шагу, и от наибольшего значения этапа к первому шагу. Это также связывается с порядком выполнения, который мы упомянули выше.<\/p>\n<p>Время блокировки: Значительное время блокировки указывает на ожидание передачи данных между фрагментами, особенно в Фрагменте 1 и Фрагменте 2. Это часто является признаком задержек передачи данных или неравенства во времени обработки между фрагментами.<br \/>\nПерекос данных: Обратите внимание на перекос в количестве данных, обрабатываемых рабочими на различных этапах, что приводит к тому, что некоторые рабочие обрабатывают больше данных, чем другие. Это может вызывать неэффективность.<br \/>\nШаги агрегации: Запрос включает несколько этапов частичных и окончательных агрегаций, которые можно оптимизировать, если возможно, уменьшив переброс данных между фрагментами.<br \/>\nCPU и планирование: Время работы CPU и планирования относительно низкое по сравнению с заблокированным временем, что предполагает, что ресурсы CPU не являются узким местом.<br \/>\nНастройка SQL: Измените ваш SQL и изучите план запроса, чтобы найти правильный баланс между этими факторами.<\/p>\n<p>Предложения по оптимизации<br \/>\nМодель данных: Убедитесь, что у вас есть правильная модель данных, например, звездная схема, и выполните расчет OBT на основе звездной схемы.<br \/>\nМатериализованное представление: Предварительно посчитайте частые агрегации. Однако я бы предпочел предыдущий шаг этому, это может быть полезно, если у вас нет много времени или вы работаете над первым шагом параллельно, но будьте осторожны, чтобы избежать большого технического долга.<br \/>\nУпростите агрегации: Минимизируйте сложные шаги агрегации, чтобы уменьшить переброс данных.<\/p>\n",
            "date_published": "2024-10-04T19:50:00+03:00",
            "date_modified": "2024-10-05T20:57:24+03:00",
            "tags": [
                "SQL",
                "Trino"
            ],
            "image": "https:\/\/gavrilov.info\/pictures\/plan-zaprosov-analiziruem-proizvoditelnost-v-trino.png",
            "_date_published_rfc2822": "Fri, 04 Oct 2024 19:50:00 +0300",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "166",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": [],
                "og_images": [
                    "https:\/\/gavrilov.info\/pictures\/plan-zaprosov-analiziruem-proizvoditelnost-v-trino.png",
                    "https:\/\/gavrilov.info\/pictures\/plan-zaprosov-analiziruem-proizvoditelnost-v-trino-1.png",
                    "https:\/\/gavrilov.info\/pictures\/plan-zaprosov-analiziruem-proizvoditelnost-v-trino-2.png",
                    "https:\/\/gavrilov.info\/pictures\/plan-zaprosov-analiziruem-proizvoditelnost-v-trino-3.png"
                ]
            }
        },
        {
            "id": "152",
            "url": "https:\/\/gavrilov.info\/all\/introduction-to-bash-and-sql\/",
            "title": "Introduction to Bash and SQL, Git and Docker",
            "content_html": "<p>Хорошие книги от Bobby Iliev <a href=\"https:\/\/bobbyiliev.com\/\">https:\/\/bobbyiliev.com\/<\/a><\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/introduction-to-bash-and-sql.png\" width=\"1410\" height=\"2250\" alt=\"\" \/>\n<\/div>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/introduction-to-bash-and-sql-1.png\" width=\"1410\" height=\"2250\" alt=\"\" \/>\n<\/div>\n<p><a href=\"https:\/\/a.gavrilov.info\/data\/posts\/introduction-to-bash-scripting-dark.pdf\">https:\/\/a.gavrilov.info\/data\/posts\/introduction-to-bash-scripting-dark.pdf<\/a><br \/>\n<a href=\"https:\/\/a.gavrilov.info\/data\/posts\/introduction-to-bash-scripting-light.pdf\">https:\/\/a.gavrilov.info\/data\/posts\/introduction-to-bash-scripting-light.pdf<\/a><br \/>\n<a href=\"https:\/\/a.gavrilov.info\/data\/posts\/introduction-to-sql-dark.pdf\">https:\/\/a.gavrilov.info\/data\/posts\/introduction-to-sql-dark.pdf<\/a><br \/>\n<a href=\"https:\/\/a.gavrilov.info\/data\/posts\/introduction-to-sql-light.pdf\">https:\/\/a.gavrilov.info\/data\/posts\/introduction-to-sql-light.pdf<\/a><\/p>\n<p><a href=\"https:\/\/github.com\/bobbyiliev\/introduction-to-bash-scripting\">https:\/\/github.com\/bobbyiliev\/introduction-to-bash-scripting<\/a><br \/>\n<a href=\"https:\/\/github.com\/bobbyiliev\/introduction-to-sql\">https:\/\/github.com\/bobbyiliev\/introduction-to-sql<\/a><\/p>\n<p>и есть еще про гит<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/introduction-to-bash-and-sql-2.png\" width=\"1410\" height=\"2250\" alt=\"\" \/>\n<\/div>\n<p><a href=\"https:\/\/a.gavrilov.info\/data\/posts\/introduction-to-git-and-github-dark.pdf\">https:\/\/a.gavrilov.info\/data\/posts\/introduction-to-git-and-github-dark.pdf<\/a><br \/>\n<a href=\"https:\/\/a.gavrilov.info\/data\/posts\/introduction-to-git-and-github-light.pdf\">https:\/\/a.gavrilov.info\/data\/posts\/introduction-to-git-and-github-light.pdf<\/a><\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/introduction-to-bash-and-sql-3.png\" width=\"2240\" height=\"1260\" alt=\"\" \/>\n<\/div>\n<p><a href=\"https:\/\/a.gavrilov.info\/data\/posts\/introduction-to-docker-dark.pdf\">https:\/\/a.gavrilov.info\/data\/posts\/introduction-to-docker-dark.pdf<\/a><br \/>\n<a href=\"https:\/\/a.gavrilov.info\/data\/posts\/introduction-to-docker-light.pdf\">https:\/\/a.gavrilov.info\/data\/posts\/introduction-to-docker-light.pdf<\/a><\/p>\n",
            "date_published": "2024-07-28T22:23:10+03:00",
            "date_modified": "2024-07-28T22:36:06+03:00",
            "tags": [
                "bash",
                "Books",
                "SQL"
            ],
            "image": "https:\/\/gavrilov.info\/pictures\/introduction-to-bash-and-sql.png",
            "_date_published_rfc2822": "Sun, 28 Jul 2024 22:23:10 +0300",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "152",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": [],
                "og_images": [
                    "https:\/\/gavrilov.info\/pictures\/introduction-to-bash-and-sql.png",
                    "https:\/\/gavrilov.info\/pictures\/introduction-to-bash-and-sql-1.png",
                    "https:\/\/gavrilov.info\/pictures\/introduction-to-bash-and-sql-2.png",
                    "https:\/\/gavrilov.info\/pictures\/introduction-to-bash-and-sql-3.png"
                ]
            }
        },
        {
            "id": "47",
            "url": "https:\/\/gavrilov.info\/all\/zagruzka-dannyh-iceberg-v-trino\/",
            "title": "Загрузка данных iceberg в Trino",
            "content_html": "<h2>Создание схем<\/h2>\n<p>CREATE SCHEMA delta.my_schema_d<br \/>\nWITH (location = ‘s3a:\/\/test\/delta_p’);<\/p>\n<p>CREATE SCHEMA hive.my_schema_h<br \/>\nWITH (location = ‘s3a:\/\/test\/hive_p’);<\/p>\n<p>CREATE SCHEMA iceberg.my_schema_i<br \/>\nWITH (location = ‘s3a:\/\/test\/iceberg_p’);<\/p>\n<h2>Создание таблицы на базе CSV<\/h2>\n<p>create table hive.my_schema_h.taxi_p (<br \/>\nVendorID bigint,<br \/>\ntpep_pickup_datetime varchar,<br \/>\ntpep_dropoff_datetime varchar,<br \/>\npassenger_count bigint,<br \/>\ntrip_distance decimal(20,2),<br \/>\nRatecodeID bigint,<br \/>\nstore_and_fwd_flag varchar,<br \/>\nPULocationID bigint,<br \/>\nDOLocationID bigint,<br \/>\npayment_type bigint,<br \/>\nfare_amount decimal(20,2),<br \/>\nextra decimal(20,2),<br \/>\nmta_tax decimal(20,2),<br \/>\ntip_amount decimal(20,2),<br \/>\ntolls_amount decimal(20,2),<br \/>\nimprovement_surcharge decimal(20,2),<br \/>\ntotal_amount decimal(20,2))<br \/>\nWITH (<br \/>\nformat = ‘TEXTFILE’,<br \/>\ntextfile_field_separator = ‘,’,<br \/>\nskip_header_line_count=1<br \/>\n);<\/p>\n<h2>Создание таблицы iceberg<\/h2>\n<p>create table iceberg.my_schema_i.taxi_i (<br \/>\nVendorID bigint,<br \/>\ntpep_pickup_datetime varchar,<br \/>\ntpep_dropoff_datetime varchar,<br \/>\npassenger_count bigint,<br \/>\ntrip_distance decimal(20,2),<br \/>\nRatecodeID bigint,<br \/>\nstore_and_fwd_flag varchar,<br \/>\nPULocationID bigint,<br \/>\nDOLocationID bigint,<br \/>\npayment_type bigint,<br \/>\nfare_amount decimal(20,2),<br \/>\nextra decimal(20,2),<br \/>\nmta_tax decimal(20,2),<br \/>\ntip_amount decimal(20,2),<br \/>\ntolls_amount decimal(20,2),<br \/>\nimprovement_surcharge decimal(20,2),<br \/>\ntotal_amount decimal(20,2))<\/p>\n<h2>Запись таблицы iceberg<\/h2>\n<p>insert into iceberg.my_schema_i.taxi_i select * from hive.my_schema_h.taxi_p<\/p>\n",
            "date_published": "2023-02-28T20:30:19+03:00",
            "date_modified": "2023-02-28T20:30:10+03:00",
            "tags": [
                "SQL"
            ],
            "_date_published_rfc2822": "Tue, 28 Feb 2023 20:30:19 +0300",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "47",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": [],
                "og_images": []
            }
        },
        {
            "id": "46",
            "url": "https:\/\/gavrilov.info\/all\/klassifikaciya-v-trino\/",
            "title": "Классификация в Trino",
            "content_html": "<p>CREATE SCHEMA delta.my_schema_d<br \/>\nWITH (location = ‘s3a:\/\/test\/delta_p’);<\/p>\n<p>CREATE TABLE delta.my_schema_d.iris (<br \/>\nid int<br \/>\n, sepal_length double<br \/>\n, sepal_width double<br \/>\n, petal_length double<br \/>\n, petal_width double<br \/>\n, species varchar<br \/>\n);<\/p>\n<p>INSERT INTO delta.my_schema_d.iris VALUES<br \/>\n(1, 5.1, 3.5, 1.4, 0.2, ‘Iris-setosa’),<br \/>\n(2, 4.9, 3, 1.4, 0.2, ‘Iris-setosa’),<br \/>\n(3, 4.7, 3.2, 1.3, 0.2, ‘Iris-setosa’),<br \/>\n(4, 4.6, 3.1, 1.5, 0.2, ‘Iris-setosa’),<br \/>\n(5, 5, 3.6, 1.4, 0.2, ‘Iris-setosa’),<br \/>\n(6, 5.4, 3.9, 1.7, 0.4, ‘Iris-setosa’),<br \/>\n(7, 4.6, 3.4, 1.4, 0.3, ‘Iris-setosa’),<br \/>\n(8, 5, 3.4, 1.5, 0.2, ‘Iris-setosa’),<br \/>\n(9, 4.4, 2.9, 1.4, 0.2, ‘Iris-setosa’),<br \/>\n(10, 4.9, 3.1, 1.5, 0.1, ‘Iris-setosa’),<br \/>\n(11, 5.4, 3.7, 1.5, 0.2, ‘Iris-setosa’),<br \/>\n(12, 4.8, 3.4, 1.6, 0.2, ‘Iris-setosa’),<br \/>\n(13, 4.8, 3, 1.4, 0.1, ‘Iris-setosa’),<br \/>\n(14, 4.3, 3, 1.1, 0.1, ‘Iris-setosa’),<br \/>\n(15, 5.8, 4, 1.2, 0.2, ‘Iris-setosa’),<br \/>\n(16, 5.7, 4.4, 1.5, 0.4, ‘Iris-setosa’),<br \/>\n(17, 5.4, 3.9, 1.3, 0.4, ‘Iris-setosa’),<br \/>\n(18, 5.1, 3.5, 1.4, 0.3, ‘Iris-setosa’),<br \/>\n(19, 5.7, 3.8, 1.7, 0.3, ‘Iris-setosa’),<br \/>\n(20, 5.1, 3.8, 1.5, 0.3, ‘Iris-setosa’),<br \/>\n(21, 5.4, 3.4, 1.7, 0.2, ‘Iris-setosa’),<br \/>\n(22, 5.1, 3.7, 1.5, 0.4, ‘Iris-setosa’),<br \/>\n(23, 4.6, 3.6, 1, 0.2, ‘Iris-setosa’),<br \/>\n(24, 5.1, 3.3, 1.7, 0.5, ‘Iris-setosa’),<br \/>\n(25, 4.8, 3.4, 1.9, 0.2, ‘Iris-setosa’),<br \/>\n(26, 5, 3, 1.6, 0.2, ‘Iris-setosa’),<br \/>\n(27, 5, 3.4, 1.6, 0.4, ‘Iris-setosa’),<br \/>\n(28, 5.2, 3.5, 1.5, 0.2, ‘Iris-setosa’),<br \/>\n(29, 5.2, 3.4, 1.4, 0.2, ‘Iris-setosa’),<br \/>\n(30, 4.7, 3.2, 1.6, 0.2, ‘Iris-setosa’),<br \/>\n(31, 4.8, 3.1, 1.6, 0.2, ‘Iris-setosa’),<br \/>\n(32, 5.4, 3.4, 1.5, 0.4, ‘Iris-setosa’),<br \/>\n(33, 5.2, 4.1, 1.5, 0.1, ‘Iris-setosa’),<br \/>\n(34, 5.5, 4.2, 1.4, 0.2, ‘Iris-setosa’),<br \/>\n(35, 4.9, 3.1, 1.5, 0.1, ‘Iris-setosa’),<br \/>\n(36, 5, 3.2, 1.2, 0.2, ‘Iris-setosa’),<br \/>\n(37, 5.5, 3.5, 1.3, 0.2, ‘Iris-setosa’),<br \/>\n(38, 4.9, 3.1, 1.5, 0.1, ‘Iris-setosa’),<br \/>\n(39, 4.4, 3, 1.3, 0.2, ‘Iris-setosa’),<br \/>\n(40, 5.1, 3.4, 1.5, 0.2, ‘Iris-setosa’),<br \/>\n(41, 5, 3.5, 1.3, 0.3, ‘Iris-setosa’),<br \/>\n(42, 4.5, 2.3, 1.3, 0.3, ‘Iris-setosa’),<br \/>\n(43, 4.4, 3.2, 1.3, 0.2, ‘Iris-setosa’),<br \/>\n(44, 5, 3.5, 1.6, 0.6, ‘Iris-setosa’),<br \/>\n(45, 5.1, 3.8, 1.9, 0.4, ‘Iris-setosa’),<br \/>\n(46, 4.8, 3, 1.4, 0.3, ‘Iris-setosa’),<br \/>\n(47, 5.1, 3.8, 1.6, 0.2, ‘Iris-setosa’),<br \/>\n(48, 4.6, 3.2, 1.4, 0.2, ‘Iris-setosa’),<br \/>\n(49, 5.3, 3.7, 1.5, 0.2, ‘Iris-setosa’),<br \/>\n(50, 5, 3.3, 1.4, 0.2, ‘Iris-setosa’),<br \/>\n(51, 7, 3.2, 4.7, 1.4, ‘Iris-versicolor’),<br \/>\n(52, 6.4, 3.2, 4.5, 1.5, ‘Iris-versicolor’),<br \/>\n(53, 6.9, 3.1, 4.9, 1.5, ‘Iris-versicolor’),<br \/>\n(54, 5.5, 2.3, 4, 1.3, ‘Iris-versicolor’),<br \/>\n(55, 6.5, 2.8, 4.6, 1.5, ‘Iris-versicolor’),<br \/>\n(56, 5.7, 2.8, 4.5, 1.3, ‘Iris-versicolor’),<br \/>\n(57, 6.3, 3.3, 4.7, 1.6, ‘Iris-versicolor’),<br \/>\n(58, 4.9, 2.4, 3.3, 1, ‘Iris-versicolor’),<br \/>\n(59, 6.6, 2.9, 4.6, 1.3, ‘Iris-versicolor’),<br \/>\n(60, 5.2, 2.7, 3.9, 1.4, ‘Iris-versicolor’),<br \/>\n(61, 5, 2, 3.5, 1, ‘Iris-versicolor’),<br \/>\n(62, 5.9, 3, 4.2, 1.5, ‘Iris-versicolor’),<br \/>\n(63, 6, 2.2, 4, 1, ‘Iris-versicolor’),<br \/>\n(64, 6.1, 2.9, 4.7, 1.4, ‘Iris-versicolor’),<br \/>\n(65, 5.6, 2.9, 3.6, 1.3, ‘Iris-versicolor’),<br \/>\n(66, 6.7, 3.1, 4.4, 1.4, ‘Iris-versicolor’),<br \/>\n(67, 5.6, 3, 4.5, 1.5, ‘Iris-versicolor’),<br \/>\n(68, 5.8, 2.7, 4.1, 1, ‘Iris-versicolor’),<br \/>\n(69, 6.2, 2.2, 4.5, 1.5, ‘Iris-versicolor’),<br \/>\n(70, 5.6, 2.5, 3.9, 1.1, ‘Iris-versicolor’),<br \/>\n(71, 5.9, 3.2, 4.8, 1.8, ‘Iris-versicolor’),<br \/>\n(72, 6.1, 2.8, 4, 1.3, ‘Iris-versicolor’),<br \/>\n(73, 6.3, 2.5, 4.9, 1.5, ‘Iris-versicolor’),<br \/>\n(74, 6.1, 2.8, 4.7, 1.2, ‘Iris-versicolor’),<br \/>\n(75, 6.4, 2.9, 4.3, 1.3, ‘Iris-versicolor’),<br \/>\n(76, 6.6, 3, 4.4, 1.4, ‘Iris-versicolor’),<br \/>\n(77, 6.8, 2.8, 4.8, 1.4, ‘Iris-versicolor’),<br \/>\n(78, 6.7, 3, 5, 1.7, ‘Iris-versicolor’),<br \/>\n(79, 6, 2.9, 4.5, 1.5, ‘Iris-versicolor’),<br \/>\n(80, 5.7, 2.6, 3.5, 1, ‘Iris-versicolor’),<br \/>\n(81, 5.5, 2.4, 3.8, 1.1, ‘Iris-versicolor’),<br \/>\n(82, 5.5, 2.4, 3.7, 1, ‘Iris-versicolor’),<br \/>\n(83, 5.8, 2.7, 3.9, 1.2, ‘Iris-versicolor’),<br \/>\n(84, 6, 2.7, 5.1, 1.6, ‘Iris-versicolor’),<br \/>\n(85, 5.4, 3, 4.5, 1.5, ‘Iris-versicolor’),<br \/>\n(86, 6, 3.4, 4.5, 1.6, ‘Iris-versicolor’),<br \/>\n(87, 6.7, 3.1, 4.7, 1.5, ‘Iris-versicolor’),<br \/>\n(88, 6.3, 2.3, 4.4, 1.3, ‘Iris-versicolor’),<br \/>\n(89, 5.6, 3, 4.1, 1.3, ‘Iris-versicolor’),<br \/>\n(90, 5.5, 2.5, 4, 1.3, ‘Iris-versicolor’),<br \/>\n(91, 5.5, 2.6, 4.4, 1.2, ‘Iris-versicolor’),<br \/>\n(92, 6.1, 3, 4.6, 1.4, ‘Iris-versicolor’),<br \/>\n(93, 5.8, 2.6, 4, 1.2, ‘Iris-versicolor’),<br \/>\n(94, 5, 2.3, 3.3, 1, ‘Iris-versicolor’),<br \/>\n(95, 5.6, 2.7, 4.2, 1.3, ‘Iris-versicolor’),<br \/>\n(96, 5.7, 3, 4.2, 1.2, ‘Iris-versicolor’),<br \/>\n(97, 5.7, 2.9, 4.2, 1.3, ‘Iris-versicolor’),<br \/>\n(98, 6.2, 2.9, 4.3, 1.3, ‘Iris-versicolor’),<br \/>\n(99, 5.1, 2.5, 3, 1.1, ‘Iris-versicolor’),<br \/>\n(100, 5.7, 2.8, 4.1, 1.3, ‘Iris-versicolor’),<br \/>\n(101, 6.3, 3.3, 6, 2.5, ‘Iris-virginica’),<br \/>\n(102, 5.8, 2.7, 5.1, 1.9, ‘Iris-virginica’),<br \/>\n(103, 7.1, 3, 5.9, 2.1, ‘Iris-virginica’),<br \/>\n(104, 6.3, 2.9, 5.6, 1.8, ‘Iris-virginica’),<br \/>\n(105, 6.5, 3, 5.8, 2.2, ‘Iris-virginica’),<br \/>\n(106, 7.6, 3, 6.6, 2.1, ‘Iris-virginica’),<br \/>\n(107, 4.9, 2.5, 4.5, 1.7, ‘Iris-virginica’),<br \/>\n(108, 7.3, 2.9, 6.3, 1.8, ‘Iris-virginica’),<br \/>\n(109, 6.7, 2.5, 5.8, 1.8, ‘Iris-virginica’),<br \/>\n(110, 7.2, 3.6, 6.1, 2.5, ‘Iris-virginica’),<br \/>\n(111, 6.5, 3.2, 5.1, 2, ‘Iris-virginica’),<br \/>\n(112, 6.4, 2.7, 5.3, 1.9, ‘Iris-virginica’),<br \/>\n(113, 6.8, 3, 5.5, 2.1, ‘Iris-virginica’),<br \/>\n(114, 5.7, 2.5, 5, 2, ‘Iris-virginica’),<br \/>\n(115, 5.8, 2.8, 5.1, 2.4, ‘Iris-virginica’),<br \/>\n(116, 6.4, 3.2, 5.3, 2.3, ‘Iris-virginica’),<br \/>\n(117, 6.5, 3, 5.5, 1.8, ‘Iris-virginica’),<br \/>\n(118, 7.7, 3.8, 6.7, 2.2, ‘Iris-virginica’),<br \/>\n(119, 7.7, 2.6, 6.9, 2.3, ‘Iris-virginica’),<br \/>\n(120, 6, 2.2, 5, 1.5, ‘Iris-virginica’),<br \/>\n(121, 6.9, 3.2, 5.7, 2.3, ‘Iris-virginica’),<br \/>\n(122, 5.6, 2.8, 4.9, 2, ‘Iris-virginica’),<br \/>\n(123, 7.7, 2.8, 6.7, 2, ‘Iris-virginica’),<br \/>\n(124, 6.3, 2.7, 4.9, 1.8, ‘Iris-virginica’),<br \/>\n(125, 6.7, 3.3, 5.7, 2.1, ‘Iris-virginica’),<br \/>\n(126, 7.2, 3.2, 6, 1.8, ‘Iris-virginica’),<br \/>\n(127, 6.2, 2.8, 4.8, 1.8, ‘Iris-virginica’),<br \/>\n(128, 6.1, 3, 4.9, 1.8, ‘Iris-virginica’),<br \/>\n(129, 6.4, 2.8, 5.6, 2.1, ‘Iris-virginica’),<br \/>\n(130, 7.2, 3, 5.8, 1.6, ‘Iris-virginica’),<br \/>\n(131, 7.4, 2.8, 6.1, 1.9, ‘Iris-virginica’),<br \/>\n(132, 7.9, 3.8, 6.4, 2, ‘Iris-virginica’),<br \/>\n(133, 6.4, 2.8, 5.6, 2.2, ‘Iris-virginica’),<br \/>\n(134, 6.3, 2.8, 5.1, 1.5, ‘Iris-virginica’),<br \/>\n(135, 6.1, 2.6, 5.6, 1.4, ‘Iris-virginica’),<br \/>\n(136, 7.7, 3, 6.1, 2.3, ‘Iris-virginica’),<br \/>\n(137, 6.3, 3.4, 5.6, 2.4, ‘Iris-virginica’),<br \/>\n(138, 6.4, 3.1, 5.5, 1.8, ‘Iris-virginica’),<br \/>\n(139, 6, 3, 4.8, 1.8, ‘Iris-virginica’),<br \/>\n(140, 6.9, 3.1, 5.4, 2.1, ‘Iris-virginica’),<br \/>\n(141, 6.7, 3.1, 5.6, 2.4, ‘Iris-virginica’),<br \/>\n(142, 6.9, 3.1, 5.1, 2.3, ‘Iris-virginica’),<br \/>\n(143, 5.8, 2.7, 5.1, 1.9, ‘Iris-virginica’),<br \/>\n(144, 6.8, 3.2, 5.9, 2.3, ‘Iris-virginica’),<br \/>\n(145, 6.7, 3.3, 5.7, 2.5, ‘Iris-virginica’),<br \/>\n(146, 6.7, 3, 5.2, 2.3, ‘Iris-virginica’),<br \/>\n(147, 6.3, 2.5, 5, 1.9, ‘Iris-virginica’),<br \/>\n(148, 6.5, 3, 5.2, 2, ‘Iris-virginica’),<br \/>\n(149, 6.2, 3.4, 5.4, 2.3, ‘Iris-virginica’),<br \/>\n(150, 5.9, 3, 5.1, 1.8, ‘Iris-virginica’);<\/p>\n<p>select classify(features(5.8, 3, 5.1, 1.8), model) AS prediction<br \/>\nFROM (<br \/>\nSELECT learn_classifier(species, features(sepal_length, sepal_width, petal_length, petal_width)) AS model FROM delta.my_schema_d.iris<br \/>\n) t;<\/p>\n<p>Iris-virginica<\/p>\n",
            "date_published": "2023-02-28T20:25:27+03:00",
            "date_modified": "2023-02-28T20:26:49+03:00",
            "tags": [
                "SQL"
            ],
            "_date_published_rfc2822": "Tue, 28 Feb 2023 20:25:27 +0300",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "46",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": [],
                "og_images": []
            }
        }
    ],
    "_e2_version": 4171,
    "_e2_ua_string": "Aegea 11.4 (v4171e)"
}